@thednp/rpc 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/CHANGELOG.md +38 -0
- package/dist/config/config.d.mts +8 -2
- package/dist/config/config.d.mts.map +1 -1
- package/dist/config/config.mjs.map +1 -1
- package/dist/express/express.d.mts +12 -12
- package/dist/express/express.d.mts.map +1 -1
- package/dist/express/express.mjs.map +1 -1
- package/dist/fastify/fastify.d.mts +25 -7
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +27 -1
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +7 -0
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/h3/h3.d.mts +8 -8
- package/dist/h3/h3.d.mts.map +1 -1
- package/dist/h3/h3.mjs +1 -1
- package/dist/h3/h3.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +27 -5
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs +31 -1
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts +8 -8
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +14 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +6 -5
- package/dist/index.mjs.map +1 -1
- package/dist/koa/koa.d.mts +7 -7
- package/dist/koa/koa.d.mts.map +1 -1
- package/dist/koa/koa.mjs.map +1 -1
- package/dist/server/server.d.mts +37 -31
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs +1 -1
- package/dist/server/server.mjs.map +1 -1
- package/llms.txt +6 -4
- package/package.json +10 -10
package/dist/helpers/helpers.mjs
CHANGED
|
@@ -21,6 +21,36 @@ const handleResponse = async (response) => {
|
|
|
21
21
|
return result.data;
|
|
22
22
|
};
|
|
23
23
|
/**
|
|
24
|
+
* Unwraps the `{ data }` envelope from a parsed RPC response body.
|
|
25
|
+
*
|
|
26
|
+
* Error handling matches `handleResponse` so both helpers in this module agree
|
|
27
|
+
* on the contract: a **top-level** `error` key (which the server only emits for
|
|
28
|
+
* 404/405/415/400/500) throws, while a `{ data: { error } }` body resolves
|
|
29
|
+
* normally — that shape is the documented "validation-as-data" contract where a
|
|
30
|
+
* 200 carries the validation outcome as its result.
|
|
31
|
+
*
|
|
32
|
+
* Discriminating on `error` present **and** `data` absent is what keeps those
|
|
33
|
+
* two cases apart. Checking `res.ok` first is still recommended, since this
|
|
34
|
+
* helper is status-code agnostic by design.
|
|
35
|
+
* @param json - Parsed JSON response body
|
|
36
|
+
* @returns The unwrapped response data
|
|
37
|
+
* @throws When the body carries a top-level `error` and no `data`
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const response = await fetch("/__rpc/greet", { method: "POST", ... });
|
|
41
|
+
* const body = await response.json();
|
|
42
|
+
* const greeting = unwrapEnvelope<string>(body); // "Hello, world!"
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
const unwrapEnvelope = (json) => {
|
|
46
|
+
if (json !== null && typeof json === "object") {
|
|
47
|
+
const envelope = json;
|
|
48
|
+
if (!("data" in envelope) && "error" in envelope) throw new Error(String(envelope.error));
|
|
49
|
+
if ("data" in envelope) return envelope.data;
|
|
50
|
+
}
|
|
51
|
+
return json;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
24
54
|
* Low-level stub factory used by both `getClientStub` and the auto-generated
|
|
25
55
|
* modules (`src/getClientModules.ts:73`). Keeps body/header mapping in one
|
|
26
56
|
* place so `innerModule` stays thin.
|
|
@@ -115,6 +145,6 @@ const innerModule = (body, headers, credentials, prefix, name, method) => {
|
|
|
115
145
|
};
|
|
116
146
|
};
|
|
117
147
|
//#endregion
|
|
118
|
-
export { getClientStub, handleResponse, innerModule };
|
|
148
|
+
export { getClientStub, handleResponse, innerModule, unwrapEnvelope };
|
|
119
149
|
|
|
120
150
|
//# sourceMappingURL=helpers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/client-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 UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const BAD_REQUEST = \"Bad Request\";\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\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. This module is bundled into the generated client modules — keep it free of server-only code. */\nimport type {\n ClientFunction,\n Credentials,\n InnerModReturn,\n JsonArray,\n JsonValue,\n StubOptions,\n} 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 <R extends JsonValue>(\n response: Response,\n): Promise<R | 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 as R;\n};\n\n/**\n * Low-level stub factory used by both `getClientStub` and the auto-generated\n * modules (`src/getClientModules.ts:73`). Keeps body/header mapping in one\n * place so `innerModule` stays thin.\n */\nconst makeStub = <T extends JsonArray, R extends JsonValue>(\n prefix: string,\n name: string,\n options: Partial<StubOptions> = {},\n): ClientFunction<T, R> => {\n const method = (options.method ?? \"POST\") as \"GET\" | \"POST\";\n const credentials = (options.credentials ?? \"same-origin\") as Credentials;\n const contentType = (options.contentType ?? \"application/json\") as string;\n if (method === \"GET\") {\n const headers = {} as HeadersInit;\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> => {\n const json = JSON.stringify(args);\n return innerModule<Res>(\n json as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n );\n }) as ClientFunction<T, R>;\n }\n switch (contentType) {\n case \"text/plain\": {\n const headers = { \"Content-Type\": \"text/plain\" } as HeadersInit;\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n (args[0] as string) as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n case \"application/x-www-form-urlencoded\": {\n const headers: HeadersInit = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n };\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n new URLSearchParams(args[0] as Record<string, string>)\n .toString() as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n case \"multipart/form-data\": {\n const headers: HeadersInit = {};\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n args[0] as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n default: {\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n JSON.stringify(args) as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n }\n};\n\n/**\n * Creates a typed client stub for any prefix — the manual counterpart to the\n * auto-generated `public:rpc` stubs. Useful for privileged prefixes like\n * `admin:rpc` that are not emitted in the public bundle.\n * The stub has the same `{data,cancel}` shape and cancellation/error handling\n * as generated stubs, and is code-splittable: `const adminGetUser = getClientStub(\"admin:rpc\",\"get-user\")`\n * should be `await import`-ed only inside `/admin` routes so the `admin:rpc`\n * literal never appears in the public chunk.\n * @param prefix - RPC prefix (e.g. \"admin:rpc\")\n * @param name - Registered function name\n * @param options - Optional `method`, `credentials`, `contentType`\n * @returns Client stub `(...args) => {data,cancel}`\n * @example\n * import { getClientStub } from \"@thednp/rpc/helpers\";\n * const adminGetUser = getClientStub(\"admin:rpc\",\"get-user\");\n * const {data,cancel} = adminGetUser(\"123\");\n * @example\n * const adminStats = getClientStub(\"admin:rpc\",\"stats\", { method: \"GET\" });\n */\nexport function getClientStub<T extends JsonArray, R extends JsonValue>(\n prefix: string,\n name: string,\n options?: Partial<StubOptions>,\n): ClientFunction<T, R> {\n return makeStub<T, R>(prefix, name, options);\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 = <R extends JsonValue>(\n body: BodyInit,\n headers: HeadersInit,\n credentials: Credentials,\n prefix: string,\n name: string,\n method?: \"GET\" | \"POST\",\n): InnerModReturn<R> => {\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<R>(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;;;;;;;;;;;ACelC,MAAa,iBAAiB,OAC5B,aACsB;CACtB,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;;;;;;AAOA,MAAM,YACJ,QACA,MACA,UAAgC,CAAC,MACR;CACzB,MAAM,SAAU,QAAQ,UAAU;CAClC,MAAM,cAAe,QAAQ,eAAe;CAC5C,MAAM,cAAe,QAAQ,eAAe;CAC5C,IAAI,WAAW,OAAO;EACpB,MAAM,UAAU,CAAC;EACjB,SACE,GAAG,SACqB;GACxB,MAAM,OAAO,KAAK,UAAU,IAAI;GAChC,OAAO,YACL,MACA,SACA,aACA,QACA,MACA,MACF;EACF;CACF;CACA,QAAQ,aAAR;EACE,KAAK,cAAc;GACjB,MAAM,UAAU,EAAE,gBAAgB,aAAa;GAC/C,SACE,GAAG,SAEH,YACG,KAAK,IACN,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,KAAK,qCAAqC;GACxC,MAAM,UAAuB,EAC3B,gBAAgB,oCAClB;GACA,SACE,GAAG,SAEH,YACE,IAAI,gBAAgB,KAAK,EAA4B,CAAC,CACnD,SAAS,GACZ,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,KAAK,uBAAuB;GAC1B,MAAM,UAAuB,CAAC;GAC9B,SACE,GAAG,SAEH,YACE,KAAK,IACL,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,SAAS;GACP,MAAM,UAAuB,EAAE,gBAAgB,mBAAmB;GAClE,SACE,GAAG,SAEH,YACE,KAAK,UAAU,IAAI,GACnB,SACA,aACA,QACA,MACA,MACF;EACJ;CACF;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,cACd,QACA,MACA,SACsB;CACtB,OAAO,SAAe,QAAQ,MAAM,OAAO;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,eACX,MACA,SACA,aACA,QACA,MACA,WACsB;CACtB,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,eAAkB,QAAQ;EACzC,SAAS,KAAK;GACZ,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ;EACd;CACF;AACF"}
|
|
1
|
+
{"version":3,"file":"helpers.mjs","names":[],"sources":["../../src/constants.ts","../../src/client-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 UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const BAD_REQUEST = \"Bad Request\";\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 =\n ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\n","/** @module Client-side helper utilities. Exports `handleResponse` for processing fetch responses and `innerModule` for creating AbortController-bound RPC fetch calls. This module is bundled into the generated client modules — keep it free of server-only code. */\nimport type {\n ClientFunction,\n Credentials,\n InnerModReturn,\n JsonArray,\n JsonValue,\n StubOptions,\n} 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 <R extends JsonValue>(\n response: Response,\n): Promise<R | 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 as R;\n};\n\n/**\n * Unwraps the `{ data }` envelope from a parsed RPC response body.\n *\n * Error handling matches `handleResponse` so both helpers in this module agree\n * on the contract: a **top-level** `error` key (which the server only emits for\n * 404/405/415/400/500) throws, while a `{ data: { error } }` body resolves\n * normally — that shape is the documented \"validation-as-data\" contract where a\n * 200 carries the validation outcome as its result.\n *\n * Discriminating on `error` present **and** `data` absent is what keeps those\n * two cases apart. Checking `res.ok` first is still recommended, since this\n * helper is status-code agnostic by design.\n * @param json - Parsed JSON response body\n * @returns The unwrapped response data\n * @throws When the body carries a top-level `error` and no `data`\n * @example\n * ```ts\n * const response = await fetch(\"/__rpc/greet\", { method: \"POST\", ... });\n * const body = await response.json();\n * const greeting = unwrapEnvelope<string>(body); // \"Hello, world!\"\n * ```\n */\nexport const unwrapEnvelope = <T>(json: unknown): T => {\n if (json !== null && typeof json === \"object\") {\n const envelope = json as { data?: T; error?: unknown };\n if (!(\"data\" in envelope) && \"error\" in envelope) {\n throw new Error(String(envelope.error));\n }\n if (\"data\" in envelope) return envelope.data as T;\n }\n return json as T;\n};\n\n/**\n * Low-level stub factory used by both `getClientStub` and the auto-generated\n * modules (`src/getClientModules.ts:73`). Keeps body/header mapping in one\n * place so `innerModule` stays thin.\n */\nconst makeStub = <T extends JsonArray, R extends JsonValue>(\n prefix: string,\n name: string,\n options: Partial<StubOptions> = {},\n): ClientFunction<T, R> => {\n const method = (options.method ?? \"POST\") as \"GET\" | \"POST\";\n const credentials = (options.credentials ?? \"same-origin\") as Credentials;\n const contentType = (options.contentType ?? \"application/json\") as string;\n if (method === \"GET\") {\n const headers = {} as HeadersInit;\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> => {\n const json = JSON.stringify(args);\n return innerModule<Res>(\n json as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n );\n }) as ClientFunction<T, R>;\n }\n switch (contentType) {\n case \"text/plain\": {\n const headers = { \"Content-Type\": \"text/plain\" } as HeadersInit;\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n (args[0] as string) as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n case \"application/x-www-form-urlencoded\": {\n const headers: HeadersInit = {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n };\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n new URLSearchParams(args[0] as Record<string, string>)\n .toString() as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n case \"multipart/form-data\": {\n const headers: HeadersInit = {};\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n args[0] as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n default: {\n const headers: HeadersInit = { \"Content-Type\": \"application/json\" };\n return (<TArgs extends T, Res extends R>(\n ...args: TArgs\n ): InnerModReturn<Res> =>\n innerModule(\n JSON.stringify(args) as BodyInit,\n headers,\n credentials,\n prefix,\n name,\n method,\n )) as ClientFunction<T, R>;\n }\n }\n};\n\n/**\n * Creates a typed client stub for any prefix — the manual counterpart to the\n * auto-generated `public:rpc` stubs. Useful for privileged prefixes like\n * `admin:rpc` that are not emitted in the public bundle.\n * The stub has the same `{data,cancel}` shape and cancellation/error handling\n * as generated stubs, and is code-splittable: `const adminGetUser = getClientStub(\"admin:rpc\",\"get-user\")`\n * should be `await import`-ed only inside `/admin` routes so the `admin:rpc`\n * literal never appears in the public chunk.\n * @param prefix - RPC prefix (e.g. \"admin:rpc\")\n * @param name - Registered function name\n * @param options - Optional `method`, `credentials`, `contentType`\n * @returns Client stub `(...args) => {data,cancel}`\n * @example\n * import { getClientStub } from \"@thednp/rpc/helpers\";\n * const adminGetUser = getClientStub(\"admin:rpc\",\"get-user\");\n * const {data,cancel} = adminGetUser(\"123\");\n * @example\n * const adminStats = getClientStub(\"admin:rpc\",\"stats\", { method: \"GET\" });\n */\nexport function getClientStub<T extends JsonArray, R extends JsonValue>(\n prefix: string,\n name: string,\n options?: Partial<StubOptions>,\n): ClientFunction<T, R> {\n return makeStub<T, R>(prefix, name, options);\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 = <R extends JsonValue>(\n body: BodyInit,\n headers: HeadersInit,\n credentials: Credentials,\n prefix: string,\n name: string,\n method?: \"GET\" | \"POST\",\n): InnerModReturn<R> => {\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<R>(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;;;;;;;;;;;ACelC,MAAa,iBAAiB,OAC5B,aACsB;CACtB,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;;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,kBAAqB,SAAqB;CACrD,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;EAC7C,MAAM,WAAW;EACjB,IAAI,EAAE,UAAU,aAAa,WAAW,UACtC,MAAM,IAAI,MAAM,OAAO,SAAS,KAAK,CAAC;EAExC,IAAI,UAAU,UAAU,OAAO,SAAS;CAC1C;CACA,OAAO;AACT;;;;;;AAOA,MAAM,YACJ,QACA,MACA,UAAgC,CAAC,MACR;CACzB,MAAM,SAAU,QAAQ,UAAU;CAClC,MAAM,cAAe,QAAQ,eAAe;CAC5C,MAAM,cAAe,QAAQ,eAAe;CAC5C,IAAI,WAAW,OAAO;EACpB,MAAM,UAAU,CAAC;EACjB,SACE,GAAG,SACqB;GACxB,MAAM,OAAO,KAAK,UAAU,IAAI;GAChC,OAAO,YACL,MACA,SACA,aACA,QACA,MACA,MACF;EACF;CACF;CACA,QAAQ,aAAR;EACE,KAAK,cAAc;GACjB,MAAM,UAAU,EAAE,gBAAgB,aAAa;GAC/C,SACE,GAAG,SAEH,YACG,KAAK,IACN,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,KAAK,qCAAqC;GACxC,MAAM,UAAuB,EAC3B,gBAAgB,oCAClB;GACA,SACE,GAAG,SAEH,YACE,IAAI,gBAAgB,KAAK,EAA4B,CAAC,CACnD,SAAS,GACZ,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,KAAK,uBAAuB;GAC1B,MAAM,UAAuB,CAAC;GAC9B,SACE,GAAG,SAEH,YACE,KAAK,IACL,SACA,aACA,QACA,MACA,MACF;EACJ;EACA,SAAS;GACP,MAAM,UAAuB,EAAE,gBAAgB,mBAAmB;GAClE,SACE,GAAG,SAEH,YACE,KAAK,UAAU,IAAI,GACnB,SACA,aACA,QACA,MACA,MACF;EACJ;CACF;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,cACd,QACA,MACA,SACsB;CACtB,OAAO,SAAe,QAAQ,MAAM,OAAO;AAC7C;;;;;;;;;;;;;;AAeA,MAAa,eACX,MACA,SACA,aACA,QACA,MACA,WACsB;CACtB,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,eAAkB,QAAQ;EACzC,SAAS,KAAK;GACZ,MAAM;EACR;CACF;CAEA,OAAO;EACL,MAAM,QAAQ;EACd;CACF;AACF"}
|
package/dist/hono/hono.d.mts
CHANGED
|
@@ -37,7 +37,7 @@ type HonoMiddlewareFn = <A extends RpcPluginOptions["adapter"] = "hono">(initial
|
|
|
37
37
|
* @param initialOptions - Options for rpcPrefix, path matching, and the handler function
|
|
38
38
|
* @returns A Hono middleware function
|
|
39
39
|
*/
|
|
40
|
-
declare const createMiddleware: HonoMiddlewareFn;
|
|
40
|
+
export declare const createMiddleware: HonoMiddlewareFn;
|
|
41
41
|
/**
|
|
42
42
|
* Creates the Hono RPC middleware that routes incoming requests to registered server functions.
|
|
43
43
|
* Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches
|
|
@@ -45,7 +45,7 @@ declare const createMiddleware: HonoMiddlewareFn;
|
|
|
45
45
|
* @param initialOptions - Options including rpcPrefix for URL routing
|
|
46
46
|
* @returns A Hono middleware function
|
|
47
47
|
*/
|
|
48
|
-
declare const createRPCMiddleware: HonoMiddlewareFn;
|
|
48
|
+
export declare const createRPCMiddleware: HonoMiddlewareFn;
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/hono/helpers.d.ts
|
|
51
51
|
/**
|
|
@@ -53,14 +53,14 @@ declare const createRPCMiddleware: HonoMiddlewareFn;
|
|
|
53
53
|
* Dynamically imports loadRPCConfig and registers the middleware.
|
|
54
54
|
* @param app - Hono application instance
|
|
55
55
|
*/
|
|
56
|
-
declare function attachRPC(app: Hono$1): Promise<void>;
|
|
56
|
+
export declare function attachRPC(app: Hono$1): Promise<void>;
|
|
57
57
|
/**
|
|
58
58
|
* Attaches Vite's dev server middlewares to a Hono app for development mode.
|
|
59
59
|
* Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into Hono.
|
|
60
60
|
* @param app - Hono application instance
|
|
61
61
|
* @param vite - Running Vite dev server
|
|
62
62
|
*/
|
|
63
|
-
declare const attachVite: (app: Hono$1, vite: ViteDevServer) => void;
|
|
63
|
+
export declare const attachVite: (app: Hono$1, vite: ViteDevServer) => void;
|
|
64
64
|
/**
|
|
65
65
|
* Creates a Hono-compatible middleware from a Vite dev server middleware stack.
|
|
66
66
|
* Bridges the Connect/Express middleware interface to Hono's context-based request/response model.
|
|
@@ -69,7 +69,7 @@ declare const attachVite: (app: Hono$1, vite: ViteDevServer) => void;
|
|
|
69
69
|
* @returns A Hono middleware function
|
|
70
70
|
* @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049
|
|
71
71
|
*/
|
|
72
|
-
declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createMiddleware$1<{
|
|
72
|
+
export declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createMiddleware$1<{
|
|
73
73
|
Bindings: HttpBindings;
|
|
74
74
|
}>>;
|
|
75
75
|
/**
|
|
@@ -78,7 +78,7 @@ declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createM
|
|
|
78
78
|
* @param c - Hono request context
|
|
79
79
|
* @returns A promise resolving to the parsed body with its content type
|
|
80
80
|
*/
|
|
81
|
-
declare const readBody: (c: Context) => Promise<BodyResult>;
|
|
81
|
+
export declare const readBody: (c: Context) => Promise<BodyResult>;
|
|
82
82
|
/**
|
|
83
83
|
* Issues an HTTP redirect on a Hono context. Hono's `c.redirect(location,
|
|
84
84
|
* status)` returns a `Response` object that the handler must return (it never
|
|
@@ -89,7 +89,7 @@ declare const readBody: (c: Context) => Promise<BodyResult>;
|
|
|
89
89
|
* @param status - HTTP status code, defaults to 303
|
|
90
90
|
* @returns A Hono `Response` to return from the handler
|
|
91
91
|
*/
|
|
92
|
-
declare const redirect: (c: Context, location: string, status?: RedirectStatusCode) => Response;
|
|
92
|
+
export declare const redirect: (c: Context, location: string, status?: RedirectStatusCode) => Response;
|
|
93
93
|
//#endregion
|
|
94
|
-
export {
|
|
94
|
+
export type { Hono, HonoContext, HonoMiddlewareFn, HonoMiddlewareHandler, HonoMiddlewareHooks, HonoMiddlewareOptions, IncomingWithBody };
|
|
95
95
|
//# 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/hono/helpers.ts"],"mappings":";;;;;;;;;;;KAOY,mBAAmB;EAAoB;;;;;KAKvC,wBAAwB;;;;UAKnB;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,sCACxC,iBAAiB,QAAQ,kBAAkB,QACxC;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"hono.d.mts","names":["Hono","createMiddleware"],"sources":["../../src/hono/types.d.ts","../../src/hono/createMiddleware.ts","../../src/hono/helpers.ts"],"mappings":";;;;;;;;;;;KAOY,mBAAmB;EAAoB;;;;;KAKvC,wBAAwB;;;;UAKnB;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAU,sCACxC,iBAAiB,QAAQ,kBAAkB,QACxC;;;;;;;;;;qBCkBQ,kBAAkB;;;;;;;;qBAgFlB,qBAAqB;;;;;;;;wBC3GZ,UAAU,KAAKA,SAAI;;;;;;;qBAgB5B,aAAU,KAASA,QAAI,MAAQ;;;;;;;;;qBAY/B,iBAAc,MACnB,kBACL,kBAAkBC;EAAmB,UAAU;;;;;;;;qBAgDrC,WAAQ,GAChB,YACF,QAAQ;;;;;;;;;;;qBA4DE,WAAQ,GAChB,SAAO,kBACM,SACR,uBACP"}
|
package/dist/hono/hono.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.mjs","names":["createMiddleware","createHonoMiddleware"],"sources":["../../src/options.ts","../../src/functionsMap.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: ServerFunctionOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n};\n\nexport const defaultPrefix = \"__rpc\";\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: defaultPrefix,\n adapter: \"express\",\n serverFiles: \"exact\",\n scanRoot: undefined,\n};\n\nexport const defaultMiddlewareOptions: MiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n};\n","import type { ServerFnEntry } from \"./types.d.ts\";\nimport { defaultPrefix } from \"./options.ts\";\n\n/**\n * Global symbol under which the shared `serverFunctionsByPrefix` map is stored\n * on `globalThis`. Keeping it on a `Symbol.for` key makes it instance-stable\n * across the bundled entry copies (`index.mjs`, `server.mjs`, `express.mjs`,\n * ...) and dev-server hot reloads, exactly like the request-context storage in\n * `context.ts`. Without this, `scanForServerFiles` (bundled into the plugin)\n * would populate a map copy the adapter middleware could not read.\n */\nconst functionsMapSymbol = Symbol.for(\"thednp.rpc.functionsMap\");\n\n/**\n * Map of rpcPrefix -> Map of function names -> ServerFnEntry\n * Enables multiple RPC instances with different prefixes to coexist\n * without name collisions.\n */\nexport const serverFunctionsByPrefix: Map<\n string,\n Map<string, ServerFnEntry>\n> = ((globalThis as Record<symbol, Map<string, Map<string, ServerFnEntry>>>)[\n functionsMapSymbol\n] ??= new Map());\n\n/**\n * Gets or creates the function map for a specific prefix.\n * @param prefix - The RPC prefix (e.g., \"__rpc\", \"v1:rpc\", \"admin:rpc\")\n * @returns Map of function names to ServerFnEntry for that prefix\n */\nexport const getFunctionsForPrefix = (\n prefix: string,\n): Map<string, ServerFnEntry> => {\n if (!serverFunctionsByPrefix.has(prefix)) {\n serverFunctionsByPrefix.set(prefix, new Map());\n }\n return serverFunctionsByPrefix.get(prefix)!;\n};\n\n/**\n * Backward compatibility: default map for the default prefix.\n * Legacy code can still use serverFunctionsMap.set(name, entry).\n */\nexport const serverFunctionsMap: Map<string, ServerFnEntry> = {\n get: (key: string) => getFunctionsForPrefix(defaultPrefix).get(key),\n set: (key: string, value: ServerFnEntry) =>\n getFunctionsForPrefix(defaultPrefix).set(key, value),\n has: (key: string) => getFunctionsForPrefix(defaultPrefix).has(key),\n delete: (key: string) => getFunctionsForPrefix(defaultPrefix).delete(key),\n clear: () => getFunctionsForPrefix(defaultPrefix).clear(),\n get size() {\n return getFunctionsForPrefix(defaultPrefix).size;\n },\n entries: () => getFunctionsForPrefix(defaultPrefix).entries(),\n keys: () => getFunctionsForPrefix(defaultPrefix).keys(),\n values: () => getFunctionsForPrefix(defaultPrefix).values(),\n forEach: (\n callback: (\n value: ServerFnEntry,\n key: string,\n map: Map<string, ServerFnEntry>,\n ) => void,\n ) => getFunctionsForPrefix(defaultPrefix).forEach(callback),\n [Symbol.iterator]: () =>\n getFunctionsForPrefix(defaultPrefix)[Symbol.iterator](),\n} as unknown as Map<string, ServerFnEntry>;\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 UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const BAD_REQUEST = \"Bad Request\";\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\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\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 {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from \"hono/utils/http-status\";\nimport type { BodyResult } from \"@thednp/rpc\";\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 // The main plugin entry statically imports Vite, so loadRPCConfig is\n // imported lazily: function bundles that never call attachRPC (e.g.\n // serverless functions) keep Vite out of the bundle (or externalized).\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 isMultipart = contentType.includes(\"multipart/form-data\");\n const isUrlEncoded = contentType.includes(\"urlencoded\");\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: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? (reqBody as Record<string, unknown>)\n : isJSON\n ? reqBody\n : isUrlEncoded\n ? (reqBody as Record<string, unknown>)\n : 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 {\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? ({ raw: text } as Record<string, unknown>)\n : isUrlEncoded\n ? Object.fromEntries(new URLSearchParams(text))\n : String(text),\n } as BodyResult;\n};\n\n/**\n * Issues an HTTP redirect on a Hono context. Hono's `c.redirect(location,\n * status)` returns a `Response` object that the handler must return (it never\n * writes directly). Defaults to `303 See Other` for convention\n * (Post/Redirect/Get).\n * @param c - Hono context\n * @param location - The URL to redirect to\n * @param status - HTTP status code, defaults to 303\n * @returns A Hono `Response` to return from the handler\n */\nexport const redirect = (\n c: Context,\n location: string,\n status: RedirectStatusCode = 303,\n): Response => {\n return c.redirect(location, status);\n};\n","// src/hono/createMiddleware.ts\nimport type { Context, Next } from \"hono\";\nimport type {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from \"hono/utils/http-status\";\nimport type { HonoMiddlewareFn, HonoMiddlewareOptions } from \"./types.d.ts\";\nimport type { JsonValue } from \"@thednp/rpc\";\nimport { createMiddleware as createHonoMiddleware } from \"hono/factory\";\nimport type { RequestEvent } from \"@thednp/rpc/server\";\nimport {\n escapeRegExp,\n formatError,\n getGlobalPrefix,\n hasContentTypeMismatch,\n provideRequestContext,\n safeURL,\n scanForServerFiles,\n} from \"@thednp/rpc/server\";\nimport { getFunctionsForPrefix } from \"../functionsMap.ts\";\nimport {\n defaultMiddlewareOptions,\n defaultPrefix,\n defaultRPCOptions,\n} from \"../options.ts\";\nimport {\n BAD_REQUEST,\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n UNSUPPORTED_MEDIA_TYPE,\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 let 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 = safeURL(c.req.path);\n const url = reqUrl.pathname;\n\n // No need to continue when no handler provided\n if (!handler) {\n return next();\n }\n\n if (pathMatcher && !pathMatcher.test(url)) {\n return next();\n }\n\n if (prefixRegex && !prefixRegex.test(url)) {\n return next();\n }\n\n rpcPrefix = (rpcPrefix ?? defaultPrefix) as string;\n\n // When serving from production server, scan for server files\n if (getFunctionsForPrefix(rpcPrefix).size === 0) {\n await scanForServerFiles({\n rpcPrefix,\n serverFiles:\n (options as unknown as { serverFiles?: \"exact\" | \"glob\" })\n .serverFiles,\n scanRoot: (options as unknown as { scanRoot?: string }).scanRoot,\n } as never);\n }\n\n return (await handler(c, next)) as Response;\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 prefix = rpcPrefix || getGlobalPrefix() || defaultPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${prefix}/`;\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 = getFunctionsForPrefix(prefix).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 if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return c.json({ error: BAD_REQUEST }, 400);\n }\n args = parsed as JsonValue[];\n }\n } else {\n // Content-type enforcement: strict for json/text, lenient between forms.\n // Requests without a Content-Type header are exempt (curl/GET compat).\n // Checked BEFORE readBody so mismatched bodies are never buffered.\n if (\n hasContentTypeMismatch(\n serverFunction.options?.contentType ?? \"application/json\",\n c.req.header(\"content-type\"),\n )\n ) {\n return c.json({ error: UNSUPPORTED_MEDIA_TYPE }, 415);\n }\n const body = await readBody(c);\n args = Array.isArray(body.data)\n ? body.data as JsonValue[]\n : [body.data as JsonValue];\n }\n const requestEvent: RequestEvent = {\n request: c.req,\n response: c.res,\n nativeEvent: c,\n locals: {},\n functionName,\n // Hono's `c.redirect`/`c.json` return a `Response` (never write\n // directly), so the bound redirect/send only record the intent; the\n // middleware uses them after the dispatch to return the Response.\n redirect: (location, status = 303) => {\n requestEvent.redirected = { location, status };\n },\n send: (status, body, headers) => {\n requestEvent.sent = { status, body, headers };\n },\n };\n const fnResult = provideRequestContext(\n requestEvent,\n () => serverFunction.handler(...args),\n );\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 if (requestEvent.redirected) {\n return c.redirect(\n requestEvent.redirected.location,\n requestEvent.redirected.status as RedirectStatusCode,\n ) as Response;\n }\n\n if (requestEvent.sent) {\n const { status, body, headers } = requestEvent.sent;\n return c.body(JSON.stringify(body), status as ContentfulStatusCode, {\n \"content-type\": \"application/json\",\n ...headers,\n });\n }\n\n return c.json({ data: result }, 200);\n } catch (err) {\n console.error(String(err));\n const isProduction = process.env.NODE_ENV === \"production\";\n return c.json(formatError(err, isProduction), 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/functionsMap.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: ServerFunctionOptions = {\n contentType: \"application/json\",\n credentials: \"same-origin\",\n method: \"POST\",\n};\n\nexport const defaultPrefix = \"__rpc\";\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPrefix: defaultPrefix,\n adapter: \"express\",\n serverFiles: \"exact\",\n scanRoot: undefined,\n};\n\nexport const defaultMiddlewareOptions: MiddlewareOptions = {\n rpcPrefix: undefined,\n path: undefined,\n origin: undefined,\n};\n","import type { ServerFnEntry } from \"./types.d.ts\";\nimport { defaultPrefix } from \"./options.ts\";\n\n/**\n * Global symbol under which the shared `serverFunctionsByPrefix` map is stored\n * on `globalThis`. Keeping it on a `Symbol.for` key makes it instance-stable\n * across the bundled entry copies (`index.mjs`, `server.mjs`, `express.mjs`,\n * ...) and dev-server hot reloads, exactly like the request-context storage in\n * `context.ts`. Without this, `scanForServerFiles` (bundled into the plugin)\n * would populate a map copy the adapter middleware could not read.\n */\nconst functionsMapSymbol = Symbol.for(\"thednp.rpc.functionsMap\");\n\n/**\n * Map of rpcPrefix -> Map of function names -> ServerFnEntry\n * Enables multiple RPC instances with different prefixes to coexist\n * without name collisions.\n */\nexport const serverFunctionsByPrefix: Map<\n string,\n Map<string, ServerFnEntry>\n> = ((globalThis as Record<symbol, Map<string, Map<string, ServerFnEntry>>>)[\n functionsMapSymbol\n] ??= new Map());\n\n/**\n * Gets or creates the function map for a specific prefix.\n * @param prefix - The RPC prefix (e.g., \"__rpc\", \"v1:rpc\", \"admin:rpc\")\n * @returns Map of function names to ServerFnEntry for that prefix\n */\nexport const getFunctionsForPrefix = (\n prefix: string,\n): Map<string, ServerFnEntry> => {\n if (!serverFunctionsByPrefix.has(prefix)) {\n serverFunctionsByPrefix.set(prefix, new Map());\n }\n return serverFunctionsByPrefix.get(prefix)!;\n};\n\n/**\n * Backward compatibility: default map for the default prefix.\n * Legacy code can still use serverFunctionsMap.set(name, entry).\n */\nexport const serverFunctionsMap: Map<string, ServerFnEntry> = {\n get: (key: string) => getFunctionsForPrefix(defaultPrefix).get(key),\n set: (key: string, value: ServerFnEntry) =>\n getFunctionsForPrefix(defaultPrefix).set(key, value),\n has: (key: string) => getFunctionsForPrefix(defaultPrefix).has(key),\n delete: (key: string) => getFunctionsForPrefix(defaultPrefix).delete(key),\n clear: () => getFunctionsForPrefix(defaultPrefix).clear(),\n get size() {\n return getFunctionsForPrefix(defaultPrefix).size;\n },\n entries: () => getFunctionsForPrefix(defaultPrefix).entries(),\n keys: () => getFunctionsForPrefix(defaultPrefix).keys(),\n values: () => getFunctionsForPrefix(defaultPrefix).values(),\n forEach: (\n callback: (\n value: ServerFnEntry,\n key: string,\n map: Map<string, ServerFnEntry>,\n ) => void,\n ) => getFunctionsForPrefix(defaultPrefix).forEach(callback),\n [Symbol.iterator]: () =>\n getFunctionsForPrefix(defaultPrefix)[Symbol.iterator](),\n} as unknown as Map<string, ServerFnEntry>;\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 UNSUPPORTED_MEDIA_TYPE = \"Unsupported Media Type\";\n\nexport const BAD_REQUEST = \"Bad Request\";\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 =\n ` ⚡︎ No RPC config found, loading the defaults..`;\n\nexport const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;\n\n/** Error template for duplicate server function names across files. @param name - The duplicate registered name */\nexport const DUPLICATE_FUNCTION_NAME = (name: string) =>\n `Duplicate server function \"${name}\" detected. Each server function must have a unique name. Remove or rename the duplicate.`;\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 {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from \"hono/utils/http-status\";\nimport type { BodyResult } from \"@thednp/rpc\";\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 // The main plugin entry statically imports Vite, so loadRPCConfig is\n // imported lazily: function bundles that never call attachRPC (e.g.\n // serverless functions) keep Vite out of the bundle (or externalized).\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 isMultipart = contentType.includes(\"multipart/form-data\");\n const isUrlEncoded = contentType.includes(\"urlencoded\");\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: isMultipart\n ? \"multipart/form-data\"\n : isJSON\n ? \"application/json\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? (reqBody as Record<string, unknown>)\n : isJSON\n ? reqBody\n : isUrlEncoded\n ? (reqBody as Record<string, unknown>)\n : 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 {\n contentType: isMultipart\n ? \"multipart/form-data\"\n : isUrlEncoded\n ? \"application/x-www-form-urlencoded\"\n : \"text/plain\",\n data: isMultipart\n ? ({ raw: text } as Record<string, unknown>)\n : isUrlEncoded\n ? Object.fromEntries(new URLSearchParams(text))\n : String(text),\n } as BodyResult;\n};\n\n/**\n * Issues an HTTP redirect on a Hono context. Hono's `c.redirect(location,\n * status)` returns a `Response` object that the handler must return (it never\n * writes directly). Defaults to `303 See Other` for convention\n * (Post/Redirect/Get).\n * @param c - Hono context\n * @param location - The URL to redirect to\n * @param status - HTTP status code, defaults to 303\n * @returns A Hono `Response` to return from the handler\n */\nexport const redirect = (\n c: Context,\n location: string,\n status: RedirectStatusCode = 303,\n): Response => {\n return c.redirect(location, status);\n};\n","// src/hono/createMiddleware.ts\nimport type { Context, Next } from \"hono\";\nimport type {\n ContentfulStatusCode,\n RedirectStatusCode,\n} from \"hono/utils/http-status\";\nimport type { HonoMiddlewareFn, HonoMiddlewareOptions } from \"./types.d.ts\";\nimport type { JsonValue } from \"@thednp/rpc\";\nimport { createMiddleware as createHonoMiddleware } from \"hono/factory\";\nimport type { RequestEvent } from \"@thednp/rpc/server\";\nimport {\n escapeRegExp,\n formatError,\n getGlobalPrefix,\n hasContentTypeMismatch,\n provideRequestContext,\n safeURL,\n scanForServerFiles,\n} from \"@thednp/rpc/server\";\nimport { getFunctionsForPrefix } from \"../functionsMap.ts\";\nimport {\n defaultMiddlewareOptions,\n defaultPrefix,\n defaultRPCOptions,\n} from \"../options.ts\";\nimport {\n BAD_REQUEST,\n CLIENT_DISCONNECTED,\n FUNCTION_NOT_FOUND,\n METHOD_NOT_ALLOWED,\n MIDDLEWARE_NAME_USED,\n REQUEST_FORBIDDEN,\n UNSUPPORTED_MEDIA_TYPE,\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 let 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 = safeURL(c.req.path);\n const url = reqUrl.pathname;\n\n // No need to continue when no handler provided\n if (!handler) {\n return next();\n }\n\n if (pathMatcher && !pathMatcher.test(url)) {\n return next();\n }\n\n if (prefixRegex && !prefixRegex.test(url)) {\n return next();\n }\n\n rpcPrefix = (rpcPrefix ?? defaultPrefix) as string;\n\n // When serving from production server, scan for server files\n if (getFunctionsForPrefix(rpcPrefix).size === 0) {\n await scanForServerFiles({\n rpcPrefix,\n serverFiles:\n (options as unknown as { serverFiles?: \"exact\" | \"glob\" })\n .serverFiles,\n scanRoot: (options as unknown as { scanRoot?: string }).scanRoot,\n } as never);\n }\n\n return (await handler(c, next)) as Response;\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 prefix = rpcPrefix || getGlobalPrefix() || defaultPrefix;\n const prefixRegex = rpcPrefix\n ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${prefix}/`;\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 = getFunctionsForPrefix(prefix).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 if (raw) {\n const parsed: unknown = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return c.json({ error: BAD_REQUEST }, 400);\n }\n args = parsed as JsonValue[];\n }\n } else {\n // Content-type enforcement: strict for json/text, lenient between forms.\n // Requests without a Content-Type header are exempt (curl/GET compat).\n // Checked BEFORE readBody so mismatched bodies are never buffered.\n if (\n hasContentTypeMismatch(\n serverFunction.options?.contentType ?? \"application/json\",\n c.req.header(\"content-type\"),\n )\n ) {\n return c.json({ error: UNSUPPORTED_MEDIA_TYPE }, 415);\n }\n const body = await readBody(c);\n args = Array.isArray(body.data)\n ? body.data as JsonValue[]\n : [body.data as JsonValue];\n }\n const requestEvent: RequestEvent = {\n request: c.req,\n response: c.res,\n nativeEvent: c,\n locals: {},\n functionName,\n // Hono's `c.redirect`/`c.json` return a `Response` (never write\n // directly), so the bound redirect/send only record the intent; the\n // middleware uses them after the dispatch to return the Response.\n redirect: (location, status = 303) => {\n requestEvent.redirected = { location, status };\n },\n send: (status, body, headers) => {\n requestEvent.sent = { status, body, headers };\n },\n };\n const fnResult = provideRequestContext(\n requestEvent,\n () => serverFunction.handler(...args),\n );\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 if (requestEvent.redirected) {\n return c.redirect(\n requestEvent.redirected.location,\n requestEvent.redirected.status as RedirectStatusCode,\n ) as Response;\n }\n\n if (requestEvent.sent) {\n const { status, body, headers } = requestEvent.sent;\n return c.body(JSON.stringify(body), status as ContentfulStatusCode, {\n \"content-type\": \"application/json\",\n ...headers,\n });\n }\n\n return c.json({ data: result }, 200);\n } catch (err) {\n console.error(String(err));\n const isProduction = process.env.NODE_ENV === \"production\";\n return c.json(formatError(err, isProduction), 500);\n }\n },\n });\n};\n"],"mappings":"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"}
|
package/dist/index.d.mts
CHANGED
|
@@ -245,18 +245,18 @@ type ServerFnArgs = [...JsonArray];
|
|
|
245
245
|
* Server-side handler signature: receives the `AbortSignal` first,
|
|
246
246
|
* followed by any serializable arguments.
|
|
247
247
|
*/
|
|
248
|
-
type ServerFunction<TArgs extends JsonArray = JsonArray, TResult
|
|
248
|
+
type ServerFunction<TArgs extends JsonArray = JsonArray, TResult = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
|
|
249
249
|
/**
|
|
250
250
|
* Server function initialization signature, identical to `ServerFunction`.
|
|
251
251
|
* Used when registering a function with `createServerFunction`.
|
|
252
252
|
*/
|
|
253
|
-
type ServerFunctionInit<TArgs extends FormData | JsonArray = JsonArray, TResult
|
|
253
|
+
type ServerFunctionInit<TArgs extends FormData | JsonArray = JsonArray, TResult = JsonValue> = (signal: AbortSignal, ...args: TArgs) => Promise<TResult>;
|
|
254
254
|
/**
|
|
255
255
|
* Client-side stub signature generated for each server function.
|
|
256
256
|
* Returns a promise-backed `data` handle plus a `cancel` function
|
|
257
257
|
* that aborts the underlying fetch request.
|
|
258
258
|
*/
|
|
259
|
-
type ClientFunction<TArgs extends JsonArray = JsonArray, TResult
|
|
259
|
+
type ClientFunction<TArgs extends JsonArray = JsonArray, TResult = JsonValue> = (...args: TArgs) => {
|
|
260
260
|
/** Promise resolving to the server response data */
|
|
261
261
|
data: Promise<TResult>;
|
|
262
262
|
/** Aborts the in-flight request with the given reason */
|
|
@@ -345,6 +345,13 @@ interface RpcPluginOptions {
|
|
|
345
345
|
* @default "exact"
|
|
346
346
|
*/
|
|
347
347
|
serverFiles?: "exact" | "glob";
|
|
348
|
+
/**
|
|
349
|
+
* Suppress the "no RPC config found" warning when no config file is
|
|
350
|
+
* discovered. Useful when the plugin is wrapped by another tool that
|
|
351
|
+
* provides configuration externally (e.g. a meta-framework adapter).
|
|
352
|
+
* @default false
|
|
353
|
+
*/
|
|
354
|
+
silent?: boolean;
|
|
348
355
|
}
|
|
349
356
|
interface MiddlewareOptions<A extends RpcPluginOptions["adapter"] = "express"> {
|
|
350
357
|
/**
|
|
@@ -453,9 +460,12 @@ type InnerModReturn<T extends JsonValue> = {
|
|
|
453
460
|
* Searches in order: `rpc.config.ts`, `rpc.config.js`, `rpc.config.mjs`, `rpc.config.mts`,
|
|
454
461
|
* `.rpcrc.ts`, `.rpcrc.js`. Falls back to defaults if none found.
|
|
455
462
|
* @param configFile - Optional explicit config file path; skips file search when provided
|
|
463
|
+
* @param opts - Optional settings; `silent` suppresses the "no config found" warning
|
|
456
464
|
* @returns Resolved RPC plugin options
|
|
457
465
|
*/
|
|
458
|
-
declare const loadRPCConfig: (
|
|
466
|
+
declare const loadRPCConfig: (configFile?: string, opts?: {
|
|
467
|
+
silent?: boolean;
|
|
468
|
+
}) => Promise<RpcPluginOptions>;
|
|
459
469
|
/**
|
|
460
470
|
* Vite plugin that enables automatic RPC generation.
|
|
461
471
|
* Transforms server function imports into fetch-based client stubs during development and production builds.
|
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/h3/types.d.ts","../src/types.d.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;KAgBY,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":["MiddlewareOptions","RpcPluginOptions","Response","RpcPluginOptions","MiddlewareOptions","MiddlewareOptions","RpcPluginOptions","MiddlewareOptions","RpcPluginOptions","MiddlewareOptions","RpcPluginOptions"],"sources":["../src/express/types.d.ts","../src/hono/types.d.ts","../src/fastify/types.d.ts","../src/koa/types.d.ts","../src/h3/types.d.ts","../src/types.d.ts","../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;KAgBY,2BAA2BA;;;;;KAM3B,uBACV,UAAUC,2CAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiBC,YACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;;;;UCzBU;;EAEf,SAAS;;;;;;KAOC,oBAAoB,UAAUC,wCACxC,iBAAiB,QAAQC,oBAAkB,QACxC;;;;;;KCWO,2BAA2BC;;;;;KAM3B,uBACV,UAAUC,2CAEV,iBAAiB,QAAQ,8BACtB;;;;UAKY;;;;;;;EAOf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;;;;;KCtDK,uBAAuBC;;;;UAalB;;;;;;EAMf,UAAU,KAAK,SAAS,MAAM,SAAS;;;;;;KAO7B,mBAAmB,UAAUC,uCACvC,iBAAiB,QAAQ,0BACtB;;;;;;KChCO,sBAAsBC;;;;UAKjB;;;;;;EAMf,SAAS;;;;;;KAOC,kBAAkB,UAAUC,sCACtC,iBAAiB,QAAQ,yBACtB;;;;;;;UCJY;;EAEf,SAAS;;EAET,MAAM;;EAEN,SAAS;;EAET,KAAK;;EAEL,IAAI;;;;;UAMW;;EAEf,SAAS;;EAET,MAAM;;EAEN,SAAS;;EAET,KAAK;;EAEL,IAAI;;;;;;KAOM;;;;KAUA;;;;KASA;;;;KAKA;EACN;EAAiC,MAAM;;EACvC;EAA2B;;EAE7B;EACA,MAAM;;EAEJ;EAAoC,MAAM;;;;;;UAM/B;;;;;EAKf,aAAa;;;;;EAKb,cAAc;;;;;;;EAOd;;;;;EAKA;;;;;;KAOU;;;;KAIA;GAAgB,cAAc,YAAY;;;;;KAI1C,aAAa,WAAW;;;;KAIxB,YAAY,gBAAgB,YAAY;;;;;;;;;;;;;;;;;;;KAuBxC,mBAAmB;;;;;KAMnB,eACV,cAAc,YAAY,WAC1B,UAAU,cACP,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;;;;;KAMzC,mBACV,cAAc,WAAW,YAAY,WACrC,UAAU,cACP,QAAQ,gBAAgB,MAAM,UAAU,QAAQ;;;;;;KAOzC,eACV,cAAc,YAAY,WAC1B,UAAU,iBACJ,MAAM;;EAEZ,MAAM,QAAQ;;EAEd,SAAS;;;;;;KAOC,4BAA4B;;EAEtC;;EAEA,UAAU;;;;;UAMK;;EAEf;;EAEA;;;;;UAMe,mBAAmB,KAAK;EACvC;EACA,SAAS,QAAQ;EACjB;EACA;;EAEA;;;;;;UAOe;;EAEf;;EAEA,SAAS;;EAET,UAAU;;EAEV;;;;;;;;UASe;;;;;;;;;;;EAWf;;;;;;;EAQA;;;;;;;EAQA;;;;;;;EAQA;;;;;;;EAQA;;UAGe,kBACf,UAAU;;;;EAKV;;;;;;;;;;;;EAaA,gBAAgB;;;;;;;;;;EAWhB;;;;;;;;EASA;;;;;;;;EASA;;;;;EAMA;;;;;;;;;;;;;;;;;;EAmBA,UAAU,eAAe;;;;;;UAOV;;;;;EAKf;;;;;EAKA,aAAa;;;;;;;EAOb,aAAa;;;;;;KAOH,eAAe,UAAU;;EAEnC,MAAM,QAAQ;;EAEd,SAAS;;;;;;;;;;;;cC5VL,gBACJ,qBACA;EAAS;MACN,QAAQ;;;;;;;;iBA6FJ,UACP,aAAY,QAAQ,oBACnB"}
|
package/dist/index.mjs
CHANGED
|
@@ -23,8 +23,8 @@ const INVALID_IDENTIFIER = (label, name) => `Invalid ${label}: "${name}" must ma
|
|
|
23
23
|
const INVALID_PATH_SEGMENT = (label, segment) => `Invalid ${label}: "${segment}" must match /^[A-Za-z0-9_$@:][A-Za-z0-9_$@:/-]*$/`;
|
|
24
24
|
/** 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 */
|
|
25
25
|
const CONFIG_FILE_NOT_FOUND = (configFile, configFilePath) => ` ⚠︎ The specified RPC config file ${configFile} cannot be found at ${configFilePath}, loading the defaults..`;
|
|
26
|
-
const NO_CONFIG_FOUND = `
|
|
27
|
-
const FAILED_LOAD_CONFIG = `
|
|
26
|
+
const NO_CONFIG_FOUND = ` ⚡︎ No RPC config found, loading the defaults..`;
|
|
27
|
+
const FAILED_LOAD_CONFIG = ` ⚠︎ Failed to load RPC config:`;
|
|
28
28
|
/** Error template for duplicate server function names across files. @param name - The duplicate registered name */
|
|
29
29
|
const DUPLICATE_FUNCTION_NAME = (name) => `Duplicate server function "${name}" detected. Each server function must have a unique name. Remove or rename the duplicate.`;
|
|
30
30
|
//#endregion
|
|
@@ -323,9 +323,10 @@ let RPCConfig;
|
|
|
323
323
|
* Searches in order: `rpc.config.ts`, `rpc.config.js`, `rpc.config.mjs`, `rpc.config.mts`,
|
|
324
324
|
* `.rpcrc.ts`, `.rpcrc.js`. Falls back to defaults if none found.
|
|
325
325
|
* @param configFile - Optional explicit config file path; skips file search when provided
|
|
326
|
+
* @param opts - Optional settings; `silent` suppresses the "no config found" warning
|
|
326
327
|
* @returns Resolved RPC plugin options
|
|
327
328
|
*/
|
|
328
|
-
const loadRPCConfig = async (configFile) => {
|
|
329
|
+
const loadRPCConfig = async (configFile, opts) => {
|
|
329
330
|
try {
|
|
330
331
|
const env = {
|
|
331
332
|
command: "serve",
|
|
@@ -376,7 +377,7 @@ const loadRPCConfig = async (configFile) => {
|
|
|
376
377
|
}
|
|
377
378
|
}
|
|
378
379
|
RPCConfig = defaultRPCOptions;
|
|
379
|
-
console.warn(NO_CONFIG_FOUND);
|
|
380
|
+
if (!opts?.silent) console.warn(NO_CONFIG_FOUND);
|
|
380
381
|
} catch (error) {
|
|
381
382
|
RPCConfig = defaultRPCOptions;
|
|
382
383
|
console.warn(FAILED_LOAD_CONFIG, error);
|
|
@@ -400,7 +401,7 @@ function rpcPlugin(devOptions = {}) {
|
|
|
400
401
|
name: "vite-plugin-universal-rpc",
|
|
401
402
|
enforce: "pre",
|
|
402
403
|
async configResolved(resolvedConfig) {
|
|
403
|
-
const uniConfig = await loadRPCConfig();
|
|
404
|
+
const uniConfig = await loadRPCConfig(void 0, { silent: devOptions.silent });
|
|
404
405
|
options = mergeConfig(uniConfig, devOptions);
|
|
405
406
|
config = resolvedConfig;
|
|
406
407
|
},
|