@thednp/rpc 0.0.1 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -5
- package/CLAUDE.md +1 -0
- package/README.md +195 -47
- package/dist/express/express.d.mts +110 -16
- package/dist/express/express.d.mts.map +1 -1
- package/dist/express/express.mjs +113 -20
- package/dist/express/express.mjs.map +1 -1
- package/dist/fastify/fastify.d.mts +83 -5
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +84 -18
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +64 -9
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs +73 -18
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +66 -4
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs +34 -7
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts +75 -6
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs +84 -24
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +210 -19
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +137 -31
- package/dist/index.mjs.map +1 -1
- package/dist/koa/koa.d.mts +52 -0
- package/dist/koa/koa.d.mts.map +1 -1
- package/dist/koa/koa.mjs +86 -16
- package/dist/koa/koa.mjs.map +1 -1
- package/dist/server/server.d.mts +111 -10
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs +117 -17
- package/dist/server/server.mjs.map +1 -1
- package/package.json +48 -31
- package/wiki/adapters.md +0 -143
- package/wiki/best-practices.md +0 -201
- package/wiki/client-usage.md +0 -62
- package/wiki/configuration.md +0 -77
- package/wiki/getting-started.md +0 -76
- package/wiki/index.md +0 -26
- package/wiki/security.md +0 -54
- package/wiki/server-functions.md +0 -93
- package/wiki/setup.md +0 -77
package/dist/hono/hono.d.mts
CHANGED
|
@@ -1,22 +1,59 @@
|
|
|
1
|
+
import { createMiddleware as createMiddleware$1 } from "hono/factory";
|
|
1
2
|
import { Context, Hono, MiddlewareHandler } from "hono";
|
|
3
|
+
import { IncomingMessage } from "node:http";
|
|
2
4
|
import { MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
|
|
3
5
|
import { HttpBindings } from "@hono/node-server";
|
|
4
6
|
import { ViteDevServer } from "vite";
|
|
5
7
|
import "express";
|
|
6
8
|
import "fastify";
|
|
9
|
+
import "fastify-plugin";
|
|
7
10
|
import "koa";
|
|
8
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
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Hono-specific middleware options, constrained to the `"hono"` adapter.
|
|
20
|
+
*/
|
|
9
21
|
type HonoMiddlewareOptions = MiddlewareOptions<"hono">;
|
|
22
|
+
/**
|
|
23
|
+
* Hono middleware handler signature used by the RPC middleware.
|
|
24
|
+
*/
|
|
10
25
|
interface HonoMiddlewareHooks {
|
|
26
|
+
/** Hono middleware handler */
|
|
11
27
|
handler: MiddlewareHandler;
|
|
12
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Hono middleware factory: takes optional initial options and returns
|
|
31
|
+
* the Hono-compatible handler.
|
|
32
|
+
*/
|
|
13
33
|
type HonoMiddlewareFn = <A extends RpcPluginOptions["adapter"] = "hono">(initialOptions?: Partial<MiddlewareOptions<A>>) => HonoMiddlewareHooks["handler"];
|
|
14
34
|
//#endregion
|
|
15
35
|
//#region src/hono/createMiddleware.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Creates a Hono middleware with optional path and rpcPrefix filtering.
|
|
38
|
+
* Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.
|
|
39
|
+
* Uses Hono's factory `createMiddleware` to wrap the handler.
|
|
40
|
+
* @param initialOptions - Options for rpcPrefix, path matching, and the handler function
|
|
41
|
+
* @returns A Hono middleware function
|
|
42
|
+
*/
|
|
16
43
|
declare const createMiddleware: HonoMiddlewareFn;
|
|
44
|
+
/**
|
|
45
|
+
* Creates the Hono RPC middleware that routes incoming requests to registered server functions.
|
|
46
|
+
* Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches
|
|
47
|
+
* to the matching function, and returns the JSON-serialized result.
|
|
48
|
+
* @param initialOptions - Options including rpcPrefix for URL routing
|
|
49
|
+
* @returns A Hono middleware function
|
|
50
|
+
*/
|
|
17
51
|
declare const createRPCMiddleware: HonoMiddlewareFn;
|
|
18
52
|
//#endregion
|
|
19
53
|
//#region src/types.d.ts
|
|
54
|
+
/**
|
|
55
|
+
* Parsed request body result discriminated by content type.
|
|
56
|
+
*/
|
|
20
57
|
type BodyResult = {
|
|
21
58
|
contentType: "application/json";
|
|
22
59
|
data: JsonValue;
|
|
@@ -25,25 +62,57 @@ type BodyResult = {
|
|
|
25
62
|
data: string;
|
|
26
63
|
};
|
|
27
64
|
// primitives and their compositions
|
|
65
|
+
/**
|
|
66
|
+
* Primitive JSON values, including `undefined` for optional parameters.
|
|
67
|
+
*/
|
|
28
68
|
type JsonPrimitive = string | number | boolean | null | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* A JSON object whose values are JSON values or arrays.
|
|
71
|
+
*/
|
|
29
72
|
type JsonObject = {
|
|
30
73
|
[key: string]: JsonValue | JsonArray;
|
|
31
74
|
};
|
|
75
|
+
/**
|
|
76
|
+
* A JSON array of JSON values.
|
|
77
|
+
*/
|
|
32
78
|
type JsonArray = JsonValue[];
|
|
79
|
+
/**
|
|
80
|
+
* Any JSON-serializable value: primitive, array, or object.
|
|
81
|
+
*/
|
|
33
82
|
type JsonValue = JsonPrimitive | JsonArray | JsonObject;
|
|
34
83
|
//#endregion
|
|
35
84
|
//#region src/hono/helpers.d.ts
|
|
85
|
+
/**
|
|
86
|
+
* Convenience function to load RPC config and attach the RPC middleware to a Hono app.
|
|
87
|
+
* Dynamically imports loadRPCConfig and registers the middleware.
|
|
88
|
+
* @param app - Hono application instance
|
|
89
|
+
*/
|
|
36
90
|
declare function attachRPC(app: Hono): Promise<void>;
|
|
37
|
-
declare function attachVite(app: Hono, vite: ViteDevServer): void;
|
|
38
91
|
/**
|
|
39
|
-
*
|
|
92
|
+
* Attaches Vite's dev server middlewares to a Hono app for development mode.
|
|
93
|
+
* Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into Hono.
|
|
94
|
+
* @param app - Hono application instance
|
|
95
|
+
* @param vite - Running Vite dev server
|
|
96
|
+
*/
|
|
97
|
+
declare const attachVite: (app: Hono, vite: ViteDevServer) => void;
|
|
98
|
+
/**
|
|
99
|
+
* Creates a Hono-compatible middleware from a Vite dev server middleware stack.
|
|
100
|
+
* Bridges the Connect/Express middleware interface to Hono's context-based request/response model.
|
|
101
|
+
* Supports both Node.js and Bun runtimes with separate polyfill paths.
|
|
102
|
+
* @param vite - Running Vite dev server
|
|
103
|
+
* @returns A Hono middleware function
|
|
40
104
|
* @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049
|
|
41
|
-
* @param vite the vite development server
|
|
42
105
|
*/
|
|
43
|
-
declare const viteMiddleware: (vite: ViteDevServer) =>
|
|
106
|
+
declare const viteMiddleware: (vite: ViteDevServer) => ReturnType<typeof createMiddleware$1<{
|
|
44
107
|
Bindings: HttpBindings;
|
|
45
|
-
}
|
|
108
|
+
}>>;
|
|
109
|
+
/**
|
|
110
|
+
* Reads and parses the HTTP request body from a Hono context.
|
|
111
|
+
* Supports JSON and text content types, with pre-parsed body detection for server-side environments.
|
|
112
|
+
* @param c - Hono request context
|
|
113
|
+
* @returns A promise resolving to the parsed body with its content type
|
|
114
|
+
*/
|
|
46
115
|
declare const readBody: (c: Context) => Promise<BodyResult>;
|
|
47
116
|
//#endregion
|
|
48
|
-
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 };
|
|
49
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
CHANGED
|
@@ -1,35 +1,64 @@
|
|
|
1
|
+
import { createMiddleware as createMiddleware$1 } from "hono/factory";
|
|
1
2
|
import { scanForServerFiles, serverFunctionsMap } from "@thednp/rpc/server";
|
|
2
|
-
//#region node_modules/.pnpm/hono@4.12.32/node_modules/hono/dist/helper/factory/index.js
|
|
3
|
-
var createMiddleware$1 = (middleware) => middleware;
|
|
4
|
-
//#endregion
|
|
5
3
|
//#region src/options.ts
|
|
6
4
|
const defaultRPCOptions = {
|
|
7
|
-
|
|
5
|
+
rpcPrefix: "__rpc",
|
|
8
6
|
adapter: "express"
|
|
9
7
|
};
|
|
10
8
|
const defaultMiddlewareOptions = {
|
|
11
|
-
|
|
12
|
-
path: void 0
|
|
9
|
+
rpcPrefix: void 0,
|
|
10
|
+
path: void 0,
|
|
11
|
+
origin: void 0
|
|
13
12
|
};
|
|
14
13
|
//#endregion
|
|
15
14
|
//#region src/tools.ts
|
|
15
|
+
/**
|
|
16
|
+
* Escapes special regex metacharacters in a string.
|
|
17
|
+
* Used to safely embed user-configurable values (like rpcPrefix) into regular expressions,
|
|
18
|
+
* preventing ReDoS and regex injection attacks.
|
|
19
|
+
* @param s - The raw string to escape
|
|
20
|
+
* @returns The escaped string safe for use in new RegExp()
|
|
21
|
+
*/
|
|
16
22
|
function escapeRegExp(s) {
|
|
17
23
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18
24
|
}
|
|
19
25
|
//#endregion
|
|
26
|
+
//#region src/constants.ts
|
|
27
|
+
const FUNCTION_NOT_FOUND = "Function not found";
|
|
28
|
+
const METHOD_NOT_ALLOWED = "Method Not Allowed";
|
|
29
|
+
const REQUEST_FORBIDDEN = "Forbidden";
|
|
30
|
+
const INTERNAL_SERVER_ERROR = "Internal Server Error";
|
|
31
|
+
const CLIENT_DISCONNECTED = "client disconnected";
|
|
32
|
+
/** Returns a warning when a middleware name is reused, preventing registration conflicts. @param name - The duplicate middleware name */
|
|
33
|
+
const MIDDLEWARE_NAME_USED = (name) => `The middleware name "${name}" is already used.`;
|
|
34
|
+
//#endregion
|
|
20
35
|
//#region src/hono/helpers.ts
|
|
36
|
+
/**
|
|
37
|
+
* Convenience function to load RPC config and attach the RPC middleware to a Hono app.
|
|
38
|
+
* Dynamically imports loadRPCConfig and registers the middleware.
|
|
39
|
+
* @param app - Hono application instance
|
|
40
|
+
*/
|
|
21
41
|
async function attachRPC(app) {
|
|
22
42
|
const { loadRPCConfig } = await import("@thednp/rpc");
|
|
23
43
|
const { adapter: _adapter, ...options } = await loadRPCConfig();
|
|
24
44
|
app.use(createRPCMiddleware(options));
|
|
25
45
|
}
|
|
26
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Attaches Vite's dev server middlewares to a Hono app for development mode.
|
|
48
|
+
* Uses the viteMiddleware wrapper to bridge Vite's Connect-compatible stack into Hono.
|
|
49
|
+
* @param app - Hono application instance
|
|
50
|
+
* @param vite - Running Vite dev server
|
|
51
|
+
*/
|
|
52
|
+
const attachVite = (app, vite) => {
|
|
27
53
|
app.use(viteMiddleware(vite));
|
|
28
|
-
}
|
|
54
|
+
};
|
|
29
55
|
/**
|
|
30
|
-
* Creates a
|
|
56
|
+
* Creates a Hono-compatible middleware from a Vite dev server middleware stack.
|
|
57
|
+
* Bridges the Connect/Express middleware interface to Hono's context-based request/response model.
|
|
58
|
+
* Supports both Node.js and Bun runtimes with separate polyfill paths.
|
|
59
|
+
* @param vite - Running Vite dev server
|
|
60
|
+
* @returns A Hono middleware function
|
|
31
61
|
* @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049
|
|
32
|
-
* @param vite the vite development server
|
|
33
62
|
*/
|
|
34
63
|
const viteMiddleware = (vite) => {
|
|
35
64
|
return createMiddleware$1((c, next) => {
|
|
@@ -59,6 +88,12 @@ const viteMiddleware = (vite) => {
|
|
|
59
88
|
});
|
|
60
89
|
});
|
|
61
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* Reads and parses the HTTP request body from a Hono context.
|
|
93
|
+
* Supports JSON and text content types, with pre-parsed body detection for server-side environments.
|
|
94
|
+
* @param c - Hono request context
|
|
95
|
+
* @returns A promise resolving to the parsed body with its content type
|
|
96
|
+
*/
|
|
62
97
|
const readBody = async (c) => {
|
|
63
98
|
const isJSON = (c.req.header("content-type")?.toLowerCase() || "").includes("json");
|
|
64
99
|
const incoming = c.env.incoming;
|
|
@@ -83,10 +118,17 @@ const readBody = async (c) => {
|
|
|
83
118
|
//#region src/hono/createMiddleware.ts
|
|
84
119
|
let middlewareCount = 0;
|
|
85
120
|
const middlewareStack = /* @__PURE__ */ new Set();
|
|
121
|
+
/**
|
|
122
|
+
* Creates a Hono middleware with optional path and rpcPrefix filtering.
|
|
123
|
+
* Middleware names are deduplicated. Prefix and path regexes are compiled once at creation time.
|
|
124
|
+
* Uses Hono's factory `createMiddleware` to wrap the handler.
|
|
125
|
+
* @param initialOptions - Options for rpcPrefix, path matching, and the handler function
|
|
126
|
+
* @returns A Hono middleware function
|
|
127
|
+
*/
|
|
86
128
|
const createMiddleware = (initialOptions = {}) => {
|
|
87
129
|
const options = Object.assign({}, defaultMiddlewareOptions, initialOptions);
|
|
88
130
|
const middlewareName = options.name;
|
|
89
|
-
const
|
|
131
|
+
const rpcPrefix = options.rpcPrefix;
|
|
90
132
|
const path = options.path;
|
|
91
133
|
const handler = options.handler;
|
|
92
134
|
let name = middlewareName;
|
|
@@ -94,9 +136,9 @@ const createMiddleware = (initialOptions = {}) => {
|
|
|
94
136
|
name = "viteRPCMiddleware-" + middlewareCount;
|
|
95
137
|
middlewareCount += 1;
|
|
96
138
|
}
|
|
97
|
-
if (middlewareStack.has(name)) throw new Error(
|
|
139
|
+
if (middlewareStack.has(name)) throw new Error(MIDDLEWARE_NAME_USED(name));
|
|
98
140
|
middlewareStack.add(name);
|
|
99
|
-
const prefixRegex =
|
|
141
|
+
const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
|
|
100
142
|
const pathMatcher = path ? typeof path === "string" ? new RegExp(path) : path : null;
|
|
101
143
|
const middlewareHandler = createMiddleware$1(async (c, next) => {
|
|
102
144
|
const url = new URL(c.req.path, "http://localhost").pathname;
|
|
@@ -118,31 +160,49 @@ const createMiddleware = (initialOptions = {}) => {
|
|
|
118
160
|
Object.defineProperty(middlewareHandler, "name", { value: name });
|
|
119
161
|
return middlewareHandler;
|
|
120
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* Creates the Hono RPC middleware that routes incoming requests to registered server functions.
|
|
165
|
+
* Wraps the generic createMiddleware with the RPC handler that reads the body, dispatches
|
|
166
|
+
* to the matching function, and returns the JSON-serialized result.
|
|
167
|
+
* @param initialOptions - Options including rpcPrefix for URL routing
|
|
168
|
+
* @returns A Hono middleware function
|
|
169
|
+
*/
|
|
121
170
|
const createRPCMiddleware = (initialOptions = {}) => {
|
|
122
|
-
const options = Object.assign({}, defaultMiddlewareOptions, {
|
|
123
|
-
const
|
|
124
|
-
const prefixRegex =
|
|
125
|
-
const prefixReplace = `/${
|
|
171
|
+
const options = Object.assign({}, defaultMiddlewareOptions, { rpcPrefix: defaultRPCOptions.rpcPrefix }, initialOptions);
|
|
172
|
+
const rpcPrefix = options.rpcPrefix;
|
|
173
|
+
const prefixRegex = rpcPrefix ? new RegExp(`^/${escapeRegExp(rpcPrefix)}/`) : null;
|
|
174
|
+
const prefixReplace = `/${rpcPrefix}/`;
|
|
126
175
|
return createMiddleware({
|
|
127
176
|
...options,
|
|
128
177
|
handler: async (c, _next) => {
|
|
129
178
|
const { path: reqPath } = c.req;
|
|
130
179
|
if (prefixRegex && !prefixRegex.test(reqPath)) return;
|
|
180
|
+
const origin = options.origin;
|
|
181
|
+
const requestOrigin = c.req.header("origin");
|
|
182
|
+
if (origin && requestOrigin && requestOrigin !== origin) return c.json({ error: REQUEST_FORBIDDEN }, 403);
|
|
131
183
|
const functionName = reqPath.replace(prefixReplace, "");
|
|
132
184
|
const serverFunction = serverFunctionsMap.get(functionName);
|
|
133
|
-
if (!serverFunction) return c.json({ error:
|
|
185
|
+
if (!serverFunction) return c.json({ error: FUNCTION_NOT_FOUND }, 404);
|
|
134
186
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
187
|
+
const method = serverFunction.options?.method || "POST";
|
|
188
|
+
if (c.req.method.toUpperCase() !== method) return c.json({ error: METHOD_NOT_ALLOWED }, 405);
|
|
189
|
+
let args = [];
|
|
190
|
+
if (method === "GET") {
|
|
191
|
+
const raw = c.req.query("args");
|
|
192
|
+
if (raw) args = JSON.parse(raw);
|
|
193
|
+
} else {
|
|
194
|
+
const body = await readBody(c);
|
|
195
|
+
args = Array.isArray(body.data) ? body.data : [body.data];
|
|
196
|
+
}
|
|
137
197
|
const fnResult = serverFunction.handler(...args);
|
|
138
|
-
const onAbort = () => fnResult.cancel(
|
|
139
|
-
c.env.incoming
|
|
198
|
+
const onAbort = () => fnResult.cancel(CLIENT_DISCONNECTED);
|
|
199
|
+
c.env.incoming?.on("close", onAbort);
|
|
140
200
|
const result = await fnResult.data;
|
|
141
|
-
c.env.incoming
|
|
201
|
+
c.env.incoming?.off("close", onAbort);
|
|
142
202
|
return c.json({ data: result }, 200);
|
|
143
203
|
} catch (err) {
|
|
144
204
|
console.error(String(err));
|
|
145
|
-
return c.json({ error:
|
|
205
|
+
return c.json({ error: INTERNAL_SERVER_ERROR }, 500);
|
|
146
206
|
}
|
|
147
207
|
}
|
|
148
208
|
});
|
package/dist/hono/hono.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hono.mjs","names":["createMiddleware","createMiddleware","createHonoMiddleware"],"sources":["../../node_modules/.pnpm/hono@4.12.32/node_modules/hono/dist/helper/factory/index.js","../../src/options.ts","../../src/tools.ts","../../src/hono/helpers.ts","../../src/hono/createMiddleware.ts"],"sourcesContent":["// src/helper/factory/index.ts\nimport { Hono } from \"../../hono.js\";\nvar Factory = class {\n initApp;\n #defaultAppOptions;\n constructor(init) {\n this.initApp = init?.initApp;\n this.#defaultAppOptions = init?.defaultAppOptions;\n }\n createApp = (options) => {\n const app = new Hono(\n options && this.#defaultAppOptions ? { ...this.#defaultAppOptions, ...options } : options ?? this.#defaultAppOptions\n );\n if (this.initApp) {\n this.initApp(app);\n }\n return app;\n };\n createMiddleware = (middleware) => middleware;\n createHandlers = (...handlers) => {\n return handlers.filter((handler) => handler !== void 0);\n };\n};\nvar createFactory = (init) => new Factory(init);\nvar createMiddleware = (middleware) => middleware;\nexport {\n Factory,\n createFactory,\n createMiddleware\n};\n","import type {\n MiddlewareOptions,\n RpcPluginOptions,\n ServerFunctionOptions,\n} from \"./types.d.ts\";\n\nexport const defaultServerFnOptions = {\n contentType: \"application/json\",\n} satisfies ServerFunctionOptions;\n\nexport const defaultRPCOptions: RpcPluginOptions = {\n rpcPreffix: \"__rpc\",\n adapter: \"express\",\n};\n\nexport const defaultMiddlewareOptions = {\n rpcPreffix: undefined,\n path: undefined,\n} satisfies MiddlewareOptions;\n","export function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\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\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\nexport function attachVite(app: Hono, vite: ViteDevServer): void {\n app.use(viteMiddleware(vite));\n}\n\n/**\n * Creates a hono compatible middleware for a given vite development server.\n * @see https://github.com/honojs/hono/issues/3162#issuecomment-2331118049\n * @param vite the vite development server\n */\nexport const viteMiddleware = (vite: ViteDevServer) => {\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\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 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\";\n// import type { JsonArray, JsonValue } from \"@thednp/rpc\";\nimport type { HonoMiddlewareFn, HonoMiddlewareOptions } 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 { readBody } from \"./helpers.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\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 rpcPreffix = options.rpcPreffix;\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(`The middleware name \"${name}\" is already used.`);\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 = rpcPreffix\n ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`)\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\nexport const createRPCMiddleware: HonoMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPreffix: defaultRPCOptions.rpcPreffix },\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 rpcPreffix = options.rpcPreffix;\n const prefixRegex = rpcPreffix\n ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`)\n : /* istanbul ignore next */ null;\n const prefixReplace = `/${rpcPreffix}/`;\n\n return createMiddleware({\n ...options,\n handler: async (c: Context, _next: Next) => {\n const { path: reqPath } = c.req;\n // const { rpcPreffix: 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 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 body = await readBody(c);\n const args = Array.isArray(body.data) ? body.data : [body.data];\n const fnResult = serverFunction.handler(...args);\n const onAbort = () => fnResult.cancel(\"client disconnected\");\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"],"x_google_ignoreList":[0],"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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"}
|