@thednp/rpc 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/AGENTS.md +142 -0
  2. package/LICENSE +21 -0
  3. package/README.md +178 -0
  4. package/dist/express/express.d.mts +39 -0
  5. package/dist/express/express.d.mts.map +1 -0
  6. package/dist/express/express.mjs +179 -0
  7. package/dist/express/express.mjs.map +1 -0
  8. package/dist/fastify/fastify.d.mts +45 -0
  9. package/dist/fastify/fastify.d.mts.map +1 -0
  10. package/dist/fastify/fastify.mjs +164 -0
  11. package/dist/fastify/fastify.mjs.map +1 -0
  12. package/dist/fastify/plugin/fastify/plugin.d.mts +114 -0
  13. package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -0
  14. package/dist/fastify/plugin/fastify/plugin.mjs +151 -0
  15. package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -0
  16. package/dist/helpers/helpers.d.mts +9 -0
  17. package/dist/helpers/helpers.d.mts.map +1 -0
  18. package/dist/helpers/helpers.mjs +36 -0
  19. package/dist/helpers/helpers.mjs.map +1 -0
  20. package/dist/hono/hono.d.mts +49 -0
  21. package/dist/hono/hono.d.mts.map +1 -0
  22. package/dist/hono/hono.mjs +153 -0
  23. package/dist/hono/hono.mjs.map +1 -0
  24. package/dist/index.d.mts +192 -0
  25. package/dist/index.d.mts.map +1 -0
  26. package/dist/index.mjs +296 -0
  27. package/dist/index.mjs.map +1 -0
  28. package/dist/koa/koa.d.mts +26 -0
  29. package/dist/koa/koa.d.mts.map +1 -0
  30. package/dist/koa/koa.mjs +155 -0
  31. package/dist/koa/koa.mjs.map +1 -0
  32. package/dist/server/server.d.mts +92 -0
  33. package/dist/server/server.d.mts.map +1 -0
  34. package/dist/server/server.mjs +161 -0
  35. package/dist/server/server.mjs.map +1 -0
  36. package/package.json +78 -0
  37. package/wiki/adapters.md +143 -0
  38. package/wiki/best-practices.md +201 -0
  39. package/wiki/client-usage.md +62 -0
  40. package/wiki/configuration.md +77 -0
  41. package/wiki/getting-started.md +76 -0
  42. package/wiki/index.md +26 -0
  43. package/wiki/security.md +54 -0
  44. package/wiki/server-functions.md +93 -0
  45. package/wiki/setup.md +77 -0
package/AGENTS.md ADDED
@@ -0,0 +1,142 @@
1
+ # @thednp/rpc
2
+
3
+ ## Dev Commands
4
+
5
+ ```bash
6
+ pnpm dev # Run examples/spa dev server
7
+ pnpm dev:express # Run examples/express dev server
8
+ pnpm dev:fastify # Run examples/fastify dev server
9
+ pnpm dev:hono # Run examples/hono dev server
10
+ pnpm dev:koa # Run examples/koa dev server
11
+ pnpm dev:ssr # Run examples/ssr dev server
12
+ pnpm lint # Lint + typecheck (deno lint + tsc)
13
+ pnpm test # Run tests with coverage
14
+ pnpm test-ui # Run tests with UI
15
+ pnpm lint:ts # deno lint src
16
+ pnpm fix:ts # deno lint src --fix
17
+ pnpm check:ts # tsc -noEmit
18
+ pnpm format # deno fmt src
19
+ pnpm build # tsdown (outputs to dist/)
20
+ pnpm up:examples # Update all example deps
21
+ pnpm up:src # Update all src deps
22
+ pnpm up:root # Update root deps
23
+ pnpm upd # Update all deps (up:examples + up:src + up:root)
24
+ pnpm prepublishOnly # upd + lint + check:ts + format + build
25
+ ```
26
+
27
+ ## Build Order
28
+
29
+ `lint -> check:ts -> format -> build` (verified in `prepublishOnly`)
30
+
31
+ ## Examples
32
+
33
+ The `examples/` directory contains 6 example apps:
34
+
35
+ | Example | Adapter | Type | Run Command | Config |
36
+ | -----------| -------------------------------------------------------------------| ------| --------------------| ----------------------------------|
37
+ | `spa` | Vite dev server (no adapter) | SPA | `pnpm dev` | `examples/spa/rpc.config.ts` |
38
+ | `express` | Express | SSR | `pnpm dev:express` | `examples/express/rpc.config.ts` |
39
+ | `fastify` | Fastify | SSR | `pnpm dev:fastify` | `examples/fastify/rpc.config.ts` |
40
+ | `hono` | Hono | SSR | `pnpm dev:hono` | `examples/hono/rpc.config.ts` |
41
+ | `koa` | Koa | SSR | `pnpm dev:koa` | `examples/koa/rpc.config.ts` |
42
+ | `ssr` | Custom `http-express.ts` (Express-compatible `node:http` server ) | SSR | `pnpm dev:ssr` | `examples/ssr/rpc.config.ts` |
43
+
44
+ Each example follows the same structure:
45
+
46
+ - `server.js` / `server.ts` — production server with Vite middleware in dev, static serving in prod
47
+ - `src/api/server.ts` — RPC server functions (auto-scanned by the plugin)
48
+ - `src/entry-server.ts` — SSR entry (hydrates on client)
49
+ - `src/entry-client.ts` — Client entry
50
+ - `vite.config.ts` + `rpc.config.ts` — Configuration files
51
+
52
+ ## Key Directories
53
+
54
+ - `src/` — source for all packages (vite plugin, server, express, fastify, hono, koa adapters)
55
+ - `dist/` — build output (not committed, generated by tsdown)
56
+ - `tests/` — test files (one per adapter + plugin)
57
+ - `tests/fixtures/` — test fixtures (config files, vite-mock.ts)
58
+ - `examples/` — example apps (spa, express, fastify, hono, koa, ssr)
59
+
60
+ ## Build Output (tsdown)
61
+
62
+ The tsdown.config.ts produces multiple entries:
63
+
64
+ - `dist/index.mjs` — main Vite plugin
65
+ - `dist/server/server.mjs` — standalone server
66
+ - `dist/express/express.mjs` — Express middleware
67
+ - `dist/fastify/fastify.mjs` — Fastify middleware
68
+ - `dist/hono/hono.mjs` — Hono middleware
69
+ - `dist/koa/koa.mjs` — Koa middleware
70
+
71
+ ## Test Files
72
+
73
+ | File | Tests | |
74
+ | -------------------------| --------------------------------------------------------------------| -----|
75
+ | `tests/plugin.test.ts` | Plugin init, loadRPCConfig, createServerFunction, getClientModules | |
76
+ | `tests/express.test.ts` | Express helpers, createMiddleware, createRPCMiddleware | |
77
+ | `tests/fastify.test.ts` | Fastify helpers, plugin, createMiddleware, createRPCMiddleware | |
78
+ | `tests/hono.test.ts` | Hono helpers, createMiddleware, createRPCMiddleware | |
79
+ | `tests/koa.test.ts` | Koa helpers, createMiddleware, createRPCMiddleware | |
80
+ | `tests/scan.test.ts` | scanForServerFiles (real scan, skip, devServer, error handling) | |
81
+
82
+ ## Important Notes
83
+
84
+ - In dev mode, **only** the Vite dev server and Express/Connect middleware are available
85
+ - Uses `deno` for linting and formatting (not eslint/prettier)
86
+ - Uses `tsdown` for bundling (not rollup/vite directly)
87
+ - Uses `vitest` for testing with `istanbul` coverage
88
+ - The `iscScanned` module-level flag in `scanForServerFiles.ts` persists across tests — use `devServer` parameter or reset modules to bypass
89
+
90
+ ## Framework
91
+
92
+ - Vite plugin for creating server functions with automatic RPC generation
93
+ - Server functions return `{ data: Promise<T>, cancel: (reason?: string) => void }` shape
94
+ - Framework-agnostic core with adapters for Express, Fastify, Hono, and Koa
95
+ - Client modules are auto-generated with `AbortController` support for cancellation
96
+ - Server-side caching must be handled by third party tools (e.g. `@tanstack/react-query`)
97
+
98
+ ## Security & Hardening
99
+
100
+ - **Prefix boundary check**: All adapters use `new RegExp(\`^/${escapeRegExp(rpcPreffix)}/\`)` instead of `startsWith` to prevent path segment bypassing (e.g., `/__rpc-evil/foo` no longer matches prefix `"__rpc"`)
101
+ - **Prefix regex injection prevention**: `rpcPreffix` config string is escaped via `escapeRegExp()` before being embedded in the boundary regex, preventing ReDoS or unintended matching from metacharacters in the prefix
102
+ - **Regex compilation hoisted**: All prefix/path regexes are compiled once at middleware creation time (not per-request), eliminating per-request regex overhead
103
+ - **Koa URL normalization**: Koa adapter parses `ctx.url` through `new URL()` to strip query strings and normalize encoding before prefix checking
104
+ - **Code injection prevention in client module generation**: `getClientModules.ts` validates all interpolated identifiers (`fnName`, `fnEntry`, `rpcPreffix`) against `/^[A-Za-z_$][A-Za-z0-9_$]*$/` (and a path-safe variant allowing `/`) before interpolating into the generated client bundle. This prevents code injection via malicious export names or prefixes containing template literal interpolations (`${...}`), backticks, or `</script>` sequences.
105
+ - **Body size limits**: Host frameworks cap parsed JSON bodies — Express (`express.json({ limit })`), Fastify (`bodyLimit`), Koa (`koa-body`), Hono (`hono/body-limit`). Rely on your framework's body parser middleware for size limits (see wiki/best-practices.md). The raw stream path in `readBody` does not impose a built-in limit — use framework middleware or a custom body limit handler for defense-in-depth.
106
+ - **Generic 404 responses**: Error messages no longer echo the requested function name, preventing function enumeration
107
+ - **Auth is middleware's responsibility**: Authentication should be handled by middleware registered before `createRPCMiddleware()`. The middleware chain naturally composes — no built-in auth hook is needed.
108
+ - **No client-side secrets or stack traces**: Error responses always return `"Internal Server Error"` regardless of the underlying error; `console.error(String(err))` is server-side only for debugging and does not surface internals to the client
109
+
110
+ ## Threat Model
111
+
112
+ The framework's security boundary is the **RPC prefix-gated HTTP endpoint**. Inputs:
113
+
114
+ | Input | Source | Trust Level | Hardening Applied |
115
+ | -----------------------| -------------------------------| ---------------------| --------------------------------------------------------------------------|
116
+ | `rpcPreffix` (config) | `rpc.config.ts` / dev options | Developer-trusted | Escaped before regex; validated before code gen |
117
+ | Function export name | `src/api/server.ts` exports | Developer-trusted | Validated against identifier regex before client codegen |
118
+ | HTTP request URL | Untrusted client | Boundary-filtered | Prefix regex (escaped, anchored, hoisted); Koa URL normalization |
119
+ | HTTP request body | Untrusted client | Capped by framework | Framework body parsers cap JSON and raw bodies |
120
+
121
+ **Attackers cannot**:
122
+ - Inject code via the prefix or function names (validated identifiers)
123
+ - Bypass the prefix via segment-prefixing tricks (anchored regex, not `startsWith`)
124
+ - Trigger ReDoS via prefix metacharacters (escaped before compilation)
125
+ - Exhaust memory via large raw text bodies (framework body parsers enforce limits)
126
+
127
+ **Attackers are expected to**:
128
+ - Be free to send as many requests as the host allows (no rate limiting — host's responsibility)
129
+ - Be free to hit any URL (no auth — host's responsibility via prior middleware)
130
+ - Be rejected with generic errors (no enumeration, no stack traces)
131
+
132
+ ## Documentation
133
+
134
+ - `wiki/index.md` — Overview
135
+ - `wiki/getting-started.md` — Quick start
136
+ - `wiki/setup.md` — Project structure and configuration
137
+ - `wiki/server-functions.md` — Creating server functions
138
+ - `wiki/client-usage.md` — Client-side usage
139
+ - `wiki/configuration.md` — Configuration reference
140
+ - `wiki/adapters.md` — Framework adapters
141
+ - `wiki/security.md` — Security hardening
142
+ - `wiki/best-practices.md` — Tips and best practices
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 thednp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @thednp/rpc
2
+
3
+ [![Coverage Status](https://coveralls.io/repos/github/thednp/rpcv/badge.svg)](https://coveralls.io/github/thednp/rpcv)
4
+ [![ci](https://github.com/thednp/rpcv/actions/workflows/ci.yml/badge.svg)](https://github.com/thednp/rpcv/actions/workflows/ci.yml)
5
+ [![NPM Version](https://img.shields.io/npm/v/@thednp/rpc.svg)](https://www.npmjs.com/package/@thednp/rpc)
6
+ [![NPM Downloads](https://img.shields.io/npm/dm/@thednp/rpc.svg)](http://npm-stat.com/charts.html?package=@thednp/rpc)
7
+
8
+ A Vite plugin for automatic RPC generation that is simple, framework agnostic and very simple to use.
9
+
10
+ * Server functions defined in `src/api/server.ts` are auto-scanned, with client modules generated at build time.
11
+ * Typed client `fetch` based modules are generated and available via the RPC plugin.
12
+
13
+ The name stands for RPC via Vite, because that's exactly what it is. No more, no less.
14
+
15
+ ## Why this exists
16
+
17
+ Most RPC solutions ask you to adopt a new way of thinking. You learn a complex API, you organize your code into a specific structure, for sure they are powerful, they work well and provide excelent DX, but complexity comes with its own drawbacks.
18
+
19
+ `@thednp/rpc` takes the opposite bet: your **server functions should just be functions**. You define them in a file, import and call them where you need them. The plugin handles everything in between — system wide configuration, scanning, type inference, client stub generation, middleware registration, request cancellation — without asking you to restructure your codebase or learn a new DSL (Domain-Specific Language).
20
+
21
+ ### The Mental model
22
+ * **Query Engine** The Brain (something like `@tanstack/react-query` that handles caching, lifecycles, deduplication).
23
+ * **@thednp/rpc** The Nervous System (transport, serialization, client/server bridge, request cancellation).
24
+ * **UI Framework** = The Muscle (Reactive DOM updates).
25
+
26
+ ## Features
27
+
28
+ - Framework-agnostic core with adapters for **Express**, **Fastify**, **Hono**, and **Koa**
29
+ - Automatic RPC generation — server functions are auto-scanned; client `fetch` based modules are generated at build time
30
+ - File-level server code isolation (no `'use server'` directives required)
31
+ - System-wide configuration via `rpc.config.ts`
32
+ - `AbortController` based request cancellation (via `.cancel()` on the returned handle)
33
+ - TypeScript support with generic type inference
34
+
35
+ ## Demos
36
+
37
+ | Example | Source Code | Try online |
38
+ | -----------------| ---------------------------------------------------------------------------------| -------------------------------------------------------------------------------------------|
39
+ | SPA - node:http | [examples/spa](https://github.com/thednp/rpcv/tree/master/examples/spa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/spa) |
40
+ | SSR - node:http | [examples/ssr](https://github.com/thednp/rpcv/tree/master/examples/ssr) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/ssr) |
41
+ | Express | [examples/express](https://github.com/thednp/rpcv/tree/master/examples/express) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/express) |
42
+ | Fastify | [examples/fastify](https://github.com/thednp/rpcv/tree/master/examples/fastify) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/fastify) |
43
+ | Hono | [examples/hono](https://github.com/thednp/rpcv/tree/master/examples/hono) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/hono) |
44
+ | Koa | [examples/koa](https://github.com/thednp/rpcv/tree/master/examples/koa) | [StackBlitz](https://stackblitz.com/fork/github/thednp/rpcv/tree/master/examples/koa) |
45
+
46
+
47
+ ## Examples
48
+
49
+ | Example | Adapter | Type | Run Command |
50
+ | ---------| -----------------------------------------------| ------| --------------------|
51
+ | spa | Vite dev server (Connect, Express-compatible) | SPA | `pnpm dev` |
52
+ | express | Express | SSR | `pnpm dev:express` |
53
+ | fastify | Fastify | SSR | `pnpm dev:fastify` |
54
+ | hono | Hono | SSR | `pnpm dev:hono` |
55
+ | koa | Koa | SSR | `pnpm dev:koa` |
56
+ | ssr | Custom `node:http` (Express-compatible) | SSR | `pnpm dev:ssr` |
57
+
58
+ ## Quick Start
59
+
60
+ ### 1. Installation
61
+
62
+ ```bash
63
+ pnpm add @thednp/rpc@latest
64
+ ```
65
+
66
+ ### 2. Configuration
67
+
68
+ Create `rpc.config.ts` at your project root:
69
+
70
+ ```ts
71
+ import { defineConfig } from "@thednp/rpc";
72
+
73
+ export default defineConfig({
74
+ adapter: "express",
75
+ rpcPreffix: "__rpc",
76
+ });
77
+ ```
78
+
79
+ Update `vite.config.ts` at your project root:
80
+
81
+ ```ts
82
+ import { defineConfig } from 'vite';
83
+ import rpc from '@thednp/rpc';
84
+
85
+ export default defineConfig({
86
+ plugins: [rpc(/* development options */)]
87
+ });
88
+
89
+ ```
90
+
91
+ ### 3. Define a server function
92
+
93
+ Create `src/api/server.ts`:
94
+
95
+ ```ts
96
+ import { createServerFunction } from "@thednp/rpc/server";
97
+
98
+ export const greet = createServerFunction("greet", (signal, name: string) => {
99
+ // access AbortSignal
100
+ signal.throwIfAborted();
101
+
102
+ // add validation and other server ONLY functionality
103
+
104
+ // return the result of processing
105
+ return `Hello, ${name}!`;
106
+ });
107
+ ```
108
+
109
+ Create `src/api/index.ts`:
110
+
111
+ ```ts
112
+ export * from "./server";
113
+ ```
114
+
115
+ ### 4. Call it from the client
116
+
117
+ Import the generated client module in any client-side file:
118
+
119
+ ```ts
120
+ // src/app.ts
121
+ import { greet } from "./api";
122
+
123
+ const { data, cancel } = greet("World");
124
+ const result = await data; // "Hello, World!"
125
+ cancel(); // AbortController-based cancellation
126
+ ```
127
+
128
+ ### 5. Register the RPC middleware on the server
129
+
130
+ Import and use the middleware from your chosen adapter package. See the [Adapters guide](./wiki/adapters.md) for full snippets for each framework.
131
+
132
+ ## Testing
133
+
134
+ ### Unit Testing
135
+
136
+ ```bash
137
+ pnpm test # Run tests with coverage
138
+ pnpm test-ui # Run tests with UI
139
+ pnpm test --run # Single run
140
+ ```
141
+
142
+ Tests use **Vitest** with **Istanbul** coverage. There are 6 test files covering all adapters plus the plugin.
143
+
144
+ ### Live Testing
145
+
146
+ ```bash
147
+ pnpm test-dev # Runs all examples/<example> in DEV mode and reports their status in a table
148
+ pnpm test-prod # Runs all examples/<example> in PRODUCTION mode and reports their status in a table
149
+ ```
150
+
151
+ These tests check the following:
152
+ * check if the server runs and doesn't crash
153
+ * check if there is any issue generating the HTML
154
+ * check if server functions work properly
155
+
156
+ ## Security
157
+
158
+ - Prefix boundary check via anchored regex — prevents `/__rpc-evil/foo` bypass
159
+ - Body size limit: `readBody` caps raw text/plain streams at 1 MiB by default
160
+ - Code injection prevention: all interpolated identifiers are validated before client module generation
161
+ - Generic error responses — no stack traces or internal details exposed to the client
162
+
163
+ See [Security](./wiki/security.md) for full details.
164
+
165
+ ## Documentation
166
+
167
+ - [Getting Started](./wiki/getting-started.md)
168
+ - [Setup Guide](./wiki/setup.md)
169
+ - [Configuration](./wiki/configuration.md)
170
+ - [Server Functions](./wiki/server-functions.md)
171
+ - [Client Usage](./wiki/client-usage.md)
172
+ - [Adapters](./wiki/adapters.md)
173
+ - [Best Practices](./wiki/best-practices.md)
174
+ - [Security](./wiki/security.md)
175
+
176
+ ## License
177
+
178
+ Released under [MIT](LICENSE).
@@ -0,0 +1,39 @@
1
+ import { Connect, ViteDevServer } from "vite";
2
+ import { BodyResult, JsonValue, MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
3
+ import { IncomingMessage, ServerResponse } from "node:http";
4
+ import { Express, NextFunction, Request, Response } from "express";
5
+ //#region src/express/types.d.ts
6
+ type ExpressMiddlewareOptions = MiddlewareOptions<"express">;
7
+ type ExpressMiddlewareFn = <A extends RpcPluginOptions["adapter"] = "express">(initialOptions?: Partial<ExpressMiddlewareOptions>) => ExpressMiddlewareHooks["handler"];
8
+ interface ExpressMiddlewareHooks {
9
+ handler: (req: IncomingMessage | Request, res: ServerResponse | Response, next: Connect.NextFunction | NextFunction) => Promise<void>;
10
+ }
11
+ //#endregion
12
+ //#region src/express/createMiddleware.d.ts
13
+ declare const createMiddleware: ExpressMiddlewareFn;
14
+ declare const createRPCMiddleware: ExpressMiddlewareFn;
15
+ //#endregion
16
+ //#region src/express/helpers.d.ts
17
+ declare function attachRPC(app: Express): Promise<void>;
18
+ declare function attachVite(app: Express, vite: ViteDevServer): void;
19
+ declare const readBody: (req: Request | IncomingMessage) => Promise<BodyResult>;
20
+ declare const isExpressRequest: (req: IncomingMessage | Request) => req is Request;
21
+ declare const isExpressResponse: (res: ServerResponse | Response) => res is Response;
22
+ declare const hasPreParsedBody: (req: IncomingMessage | Request) => req is Request;
23
+ declare const getRequestDetails: (request: Request | IncomingMessage) => {
24
+ url: string;
25
+ search: string;
26
+ searchParams: URLSearchParams;
27
+ headers: import("node:http").IncomingHttpHeaders;
28
+ method: string | undefined;
29
+ };
30
+ declare const getResponseDetails: (response: Response | ServerResponse) => {
31
+ isResponseSent: boolean;
32
+ setHeader: (name: string, value: string) => void;
33
+ statusCode: number;
34
+ setStatusCode: (code: number) => void;
35
+ sendResponse: (code: number, output: Record<string, JsonValue>) => void;
36
+ };
37
+ //#endregion
38
+ export { type ExpressMiddlewareFn, type ExpressMiddlewareHooks, type ExpressMiddlewareOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, getRequestDetails, getResponseDetails, hasPreParsedBody, isExpressRequest, isExpressResponse, readBody };
39
+ //# sourceMappingURL=express.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.d.mts","names":[],"sources":["../../src/express/types.d.ts","../../src/express/createMiddleware.ts","../../src/express/helpers.ts"],"mappings":";;;;;KAKY,2BAA2B;KAE3B,uBACV,UAAU,yCAEV,iBAAiB,QAAQ,8BACtB;UAEY;EACf,UACE,KAAK,kBAAkB,SACvB,KAAK,iBAAiB,UACtB,MAAM,QAAQ,eAAe,iBAC1B;;;;cCGM,kBAAkB;cAmElB,qBAAqB;;;iBC5EZ,UAAU,KAAK,UAAO;iBAM5B,WAAW,KAAK,SAAS,MAAM;cAKlC,WAAQ,KACd,UAAiB,oBACrB,QAAQ;cAsDE,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;cAIG,oBAAiB,KACvB,iBAAiB,aACrB,OAAO;cAIG,mBAAgB,KACtB,kBAAkB,YACtB,OAAO;cAIG,oBAAiB,SACnB,UAAiB;EAQxB;EACA;EACA,cAAY;EACZ,6BAAO;EACP;;cAIS,qBAAkB,UACnB,WAAkB;;EAIH,YAAA,cAAM;;EAQF,gBAAA;EAQD,eAAA,cAAM,QAAU,eAAe"}
@@ -0,0 +1,179 @@
1
+ import { scanForServerFiles, serverFunctionsMap } from "@thednp/rpc/server";
2
+ //#region src/options.ts
3
+ const defaultRPCOptions = {
4
+ rpcPreffix: "__rpc",
5
+ adapter: "express"
6
+ };
7
+ const defaultMiddlewareOptions = {
8
+ rpcPreffix: void 0,
9
+ path: void 0
10
+ };
11
+ //#endregion
12
+ //#region src/express/helpers.ts
13
+ async function attachRPC(app) {
14
+ const { loadRPCConfig } = await import("@thednp/rpc");
15
+ const { adapter: _adapter, ...options } = await loadRPCConfig();
16
+ app.use(createRPCMiddleware(options));
17
+ }
18
+ function attachVite(app, vite) {
19
+ app.use(vite.middlewares);
20
+ }
21
+ const readBody = (req) => {
22
+ return new Promise((resolve, reject) => {
23
+ if (hasPreParsedBody(req) && req.body !== void 0) {
24
+ const isJSON = (req.headers["content-type"]?.toLowerCase() || "").includes("json");
25
+ resolve({
26
+ contentType: isJSON ? "application/json" : "text/plain",
27
+ data: isJSON ? req.body : String(req.body)
28
+ });
29
+ return;
30
+ }
31
+ let body = "";
32
+ const toggleListeners = (add) => {
33
+ const method = add ? "on" : "off";
34
+ req[method]("data", onData);
35
+ req[method]("end", onEnd);
36
+ req[method]("error", onError);
37
+ };
38
+ const onData = (chunk) => {
39
+ body += chunk.toString();
40
+ };
41
+ const onEnd = () => {
42
+ toggleListeners();
43
+ const isJSON = (req.headers["content-type"]?.toLowerCase() || "").includes("json");
44
+ try {
45
+ const data = JSON.parse(body);
46
+ resolve({
47
+ contentType: isJSON ? "application/json" : "text/plain",
48
+ data
49
+ });
50
+ } catch (_e) {
51
+ resolve({
52
+ contentType: "text/plain",
53
+ data: String(body)
54
+ });
55
+ }
56
+ };
57
+ const onError = (err) => {
58
+ toggleListeners();
59
+ reject(err);
60
+ };
61
+ toggleListeners(true);
62
+ });
63
+ };
64
+ const isExpressRequest = (req) => {
65
+ return "originalUrl" in req;
66
+ };
67
+ const isExpressResponse = (res) => {
68
+ return "json" in res && "send" in res;
69
+ };
70
+ const hasPreParsedBody = (req) => {
71
+ return "body" in req;
72
+ };
73
+ const getRequestDetails = (request) => {
74
+ const rawUrl = isExpressRequest(request) ? request.originalUrl : request.url;
75
+ const url = new URL(rawUrl, "http://localhost");
76
+ return {
77
+ url: url.pathname,
78
+ search: url.search,
79
+ searchParams: url.searchParams,
80
+ headers: request.headers,
81
+ method: request.method
82
+ };
83
+ };
84
+ const getResponseDetails = (response) => {
85
+ const isResponseSent = response.headersSent || response.writableEnded;
86
+ const setHeader = (name, value) => {
87
+ if (isExpressResponse(response)) response.header(name, value);
88
+ else response.setHeader(name, value);
89
+ };
90
+ const setStatusCode = (code) => {
91
+ if (isExpressResponse(response)) response.status(code);
92
+ else response.statusCode = code;
93
+ };
94
+ const sendResponse = (code, output) => {
95
+ setStatusCode(code);
96
+ setHeader("Content-Type", "application/json");
97
+ if (isExpressResponse(response)) response.send(JSON.stringify(output));
98
+ else response.end(JSON.stringify(output));
99
+ };
100
+ return {
101
+ isResponseSent,
102
+ setHeader,
103
+ statusCode: response.statusCode,
104
+ setStatusCode,
105
+ sendResponse
106
+ };
107
+ };
108
+ //#endregion
109
+ //#region src/tools.ts
110
+ function escapeRegExp(s) {
111
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
112
+ }
113
+ //#endregion
114
+ //#region src/express/createMiddleware.ts
115
+ let middlewareCount = 0;
116
+ const middlewareStack = /* @__PURE__ */ new Set();
117
+ const createMiddleware = (initialOptions = {}) => {
118
+ const options = Object.assign({}, defaultMiddlewareOptions, initialOptions);
119
+ const middlewareName = options.name;
120
+ const rpcPreffix = options.rpcPreffix;
121
+ const path = options.path;
122
+ const handler = options.handler;
123
+ let name = middlewareName;
124
+ if (!name) {
125
+ name = "viteRPCMiddleware-" + middlewareCount;
126
+ middlewareCount += 1;
127
+ }
128
+ if (middlewareStack.has(name)) throw new Error(`The middleware name "${name}" is already used.`);
129
+ middlewareStack.add(name);
130
+ const prefixRegex = rpcPreffix ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`) : null;
131
+ const pathMatcher = path ? typeof path === "string" ? new RegExp(path) : path : null;
132
+ const middlewareHandler = async (req, _res, next) => {
133
+ const { url } = getRequestDetails(req);
134
+ if (serverFunctionsMap.size === 0) await scanForServerFiles();
135
+ if (!handler) return next?.();
136
+ if (pathMatcher && !pathMatcher.test(url)) return next?.();
137
+ if (prefixRegex && !prefixRegex.test(url)) return next?.();
138
+ await handler(req, _res, next);
139
+ };
140
+ Object.defineProperty(middlewareHandler, "name", { value: name });
141
+ return middlewareHandler;
142
+ };
143
+ const createRPCMiddleware = (initialOptions = {}) => {
144
+ const options = Object.assign({}, defaultMiddlewareOptions, { rpcPreffix: defaultRPCOptions.rpcPreffix }, initialOptions);
145
+ const rpcPreffix = options.rpcPreffix;
146
+ const prefixRegex = rpcPreffix ? new RegExp(`^/${escapeRegExp(rpcPreffix)}/`) : null;
147
+ const prefixReplace = `/${rpcPreffix}/`;
148
+ return createMiddleware({
149
+ ...options,
150
+ handler: async (req, res, _next) => {
151
+ const { url } = getRequestDetails(req);
152
+ const { sendResponse } = getResponseDetails(res);
153
+ if (prefixRegex && !prefixRegex.test(url)) return;
154
+ const functionName = url.replace(prefixReplace, "");
155
+ const serverFunction = serverFunctionsMap.get(functionName);
156
+ if (!serverFunction) {
157
+ sendResponse(404, { error: "Function not found" });
158
+ return;
159
+ }
160
+ try {
161
+ const body = await readBody(req);
162
+ const args = Array.isArray(body.data) ? body.data : [body.data];
163
+ const { data, cancel } = serverFunction.handler(...args);
164
+ const onClose = () => cancel("client disconnected");
165
+ req.on("close", onClose);
166
+ const result = await data;
167
+ req.off("close", onClose);
168
+ if (!res.headersSent) sendResponse(200, { data: result });
169
+ } catch (err) {
170
+ console.error(String(err));
171
+ sendResponse(500, { error: "Internal Server Error" });
172
+ }
173
+ }
174
+ });
175
+ };
176
+ //#endregion
177
+ export { attachRPC, attachVite, createMiddleware, createRPCMiddleware, getRequestDetails, getResponseDetails, hasPreParsedBody, isExpressRequest, isExpressResponse, readBody };
178
+
179
+ //# sourceMappingURL=express.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"express.mjs","names":[],"sources":["../../src/options.ts","../../src/express/helpers.ts","../../src/tools.ts","../../src/express/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} 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","// src/express/helpers.ts\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type {\n Request as ExpressRequest,\n Response as ExpressResponse,\n} from \"express\";\nimport type { BodyResult, JsonValue } from \"@thednp/rpc\";\nimport { Buffer } from \"node:buffer\";\nimport type { ViteDevServer } from \"vite\";\nimport type { Express } from \"express\";\nimport { createRPCMiddleware } from \"./createMiddleware.ts\";\n\nexport async function attachRPC(app: Express) {\n const { loadRPCConfig } = await import(\"@thednp/rpc\");\n const { adapter: _adapter, ...options } = await loadRPCConfig();\n app.use(createRPCMiddleware(options));\n}\n\nexport function attachVite(app: Express, vite: ViteDevServer) {\n app.use(vite.middlewares);\n}\n\n// src/express/helpers.ts\nexport const readBody = (\n req: ExpressRequest | IncomingMessage,\n): Promise<BodyResult> => {\n return new Promise((resolve, reject) => {\n // If an Express body parser already consumed the stream\n // via app.use(express.json()), use req.body directly\n if (hasPreParsedBody(req) && req.body !== undefined) {\n // istanbul ignore next\n const contentType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const isJSON = contentType.includes(\"json\");\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data: isJSON ? req.body : String(req.body),\n } as BodyResult);\n return;\n }\n\n // Else we parse the body right away\n let body = \"\";\n\n const toggleListeners = (add?: boolean) => {\n const method = add ? \"on\" : \"off\";\n req[method](\"data\", onData);\n req[method](\"end\", onEnd);\n req[method](\"error\", onError);\n };\n\n const onData = (chunk: Buffer) => {\n body += chunk.toString();\n };\n\n const onEnd = () => {\n toggleListeners();\n const incomingType = req.headers[\"content-type\"]?.toLowerCase() || \"\";\n const isJSON = incomingType.includes(\"json\");\n try {\n const data = JSON.parse(body);\n resolve({\n contentType: isJSON ? \"application/json\" : \"text/plain\",\n data,\n });\n } catch (_e) {\n resolve({ contentType: \"text/plain\", data: String(body) });\n }\n };\n\n const onError = (err: Error) => {\n toggleListeners();\n\n reject(err);\n };\n\n toggleListeners(true);\n });\n};\n\nexport const isExpressRequest = (\n req: IncomingMessage | ExpressRequest,\n): req is ExpressRequest => {\n return \"originalUrl\" in req;\n};\n\nexport const isExpressResponse = (\n res: ServerResponse | ExpressResponse,\n): res is ExpressResponse => {\n return \"json\" in res && \"send\" in res;\n};\n\nexport const hasPreParsedBody = (\n req: IncomingMessage | ExpressRequest,\n): req is ExpressRequest => {\n return \"body\" in req;\n};\n\nexport const getRequestDetails = (\n request: ExpressRequest | IncomingMessage,\n) => {\n const rawUrl = (\n isExpressRequest(request) ? request.originalUrl : request.url\n ) as string;\n const url = new URL(rawUrl, \"http://localhost\");\n\n return {\n url: url.pathname,\n search: url.search,\n searchParams: url.searchParams,\n headers: request.headers,\n method: request.method,\n };\n};\n\nexport const getResponseDetails = (\n response: ExpressResponse | ServerResponse,\n) => {\n const isResponseSent = response.headersSent || response.writableEnded;\n\n const setHeader = (name: string, value: string) => {\n if (isExpressResponse(response)) {\n response.header(name, value);\n } else {\n response.setHeader(name, value);\n }\n };\n\n const setStatusCode = (code: number) => {\n if (isExpressResponse(response)) {\n response.status(code);\n } else {\n response.statusCode = code;\n }\n };\n\n const sendResponse = (code: number, output: Record<string, JsonValue>) => {\n setStatusCode(code);\n setHeader(\"Content-Type\", \"application/json\");\n\n if (isExpressResponse(response)) {\n response.send(JSON.stringify(output));\n } else {\n response.end(JSON.stringify(output));\n }\n };\n\n return {\n isResponseSent,\n setHeader,\n statusCode: response.statusCode,\n setStatusCode,\n sendResponse,\n };\n};\n","export function escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n","// src/express/createMidleware.ts\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type {\n NextFunction,\n Request as ExpressRequest,\n Response as ExpressResponse,\n} from \"express\";\nimport type {\n ExpressMiddlewareFn,\n ExpressMiddlewareOptions,\n} from \"./types.d.ts\";\nimport type { Connect } from \"vite\";\n// import type { JsonArray, JsonValue } from \"@thednp/rpc\";\nimport { scanForServerFiles, serverFunctionsMap } from \"@thednp/rpc/server\";\nimport { defaultMiddlewareOptions, defaultRPCOptions } from \"../options.ts\";\nimport { getRequestDetails, getResponseDetails, readBody } from \"./helpers.ts\";\nimport { escapeRegExp } from \"../tools.ts\";\n\nlet middlewareCount = 0;\nconst middlewareStack = new Set<string>();\n\nexport const createMiddleware: ExpressMiddlewareFn = (initialOptions = {}) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n initialOptions,\n ) as ExpressMiddlewareOptions;\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 = async (\n req: IncomingMessage | ExpressRequest,\n _res: ServerResponse | ExpressResponse,\n next: Connect.NextFunction | NextFunction,\n ) => {\n const { url } = getRequestDetails(req);\n\n // When serving from production server, scan for server files\n if (serverFunctionsMap.size === 0) {\n await scanForServerFiles();\n }\n\n // No need to continue when no handler provided\n if (!handler) {\n return next?.();\n }\n\n // Path matching\n if (pathMatcher && !pathMatcher.test(url)) return next?.();\n\n // rpcPreffix matching (boundary-safe via escaped regex)\n if (prefixRegex && !prefixRegex.test(url)) {\n return next?.();\n }\n\n // Execute handler\n await handler(req, _res, next);\n };\n\n Object.defineProperty(middlewareHandler, \"name\", {\n value: name,\n });\n\n return middlewareHandler;\n};\n\n// Create RPC middleware\nexport const createRPCMiddleware: ExpressMiddlewareFn = (\n initialOptions = {},\n) => {\n const options = Object.assign(\n {},\n defaultMiddlewareOptions,\n { rpcPreffix: defaultRPCOptions.rpcPreffix },\n initialOptions,\n ) as ExpressMiddlewareOptions;\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 (\n req: IncomingMessage | ExpressRequest,\n res: ServerResponse | ExpressResponse,\n _next: NextFunction | Connect.NextFunction,\n ) => {\n const { url } = getRequestDetails(req);\n const { sendResponse } = getResponseDetails(res);\n\n // Validate the url starts with the prefix via the escaped regex\n // istanbul ignore next\n if (prefixRegex && !prefixRegex.test(url)) {\n // falls through to next handler (never reached in practice; the outer\n // createMiddleware already gates on this, but kept for defense-in-depth)\n return;\n }\n\n const functionName = url.replace(prefixReplace, \"\");\n const serverFunction = serverFunctionsMap.get(functionName);\n\n if (!serverFunction) {\n sendResponse(404, { error: \"Function not found\" });\n return;\n }\n\n try {\n const body = await readBody(req);\n const args = Array.isArray(body.data) ? body.data : [body.data];\n const { data, cancel } = serverFunction.handler(...args);\n const onClose = () => cancel(\"client disconnected\");\n\n req.on(\"close\", onClose);\n const result = await data;\n req.off(\"close\", onClose);\n\n // istanbul ignore else\n if (!res.headersSent) sendResponse(200, { data: result });\n } catch (err) {\n console.error(String(err));\n sendResponse(500, { error: \"Internal Server Error\" });\n }\n },\n });\n};\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { FastifyInstance, FastifyReply, FastifyRequest, HookHandlerDoneFunction } from "fastify";
2
+ import { MiddlewareOptions, RpcPluginOptions } from "@thednp/rpc";
3
+ import { ViteDevServer } from "vite";
4
+ import "express";
5
+ import "hono";
6
+ import "@hono/node-server";
7
+ import "koa";
8
+ //#region src/fastify/types.d.ts
9
+ type FastifyMiddlewareOptions = MiddlewareOptions<"fastify">;
10
+ type FastifyMiddlewareFn = <A extends RpcPluginOptions["adapter"] = "fastify">(initialOptions?: Partial<FastifyMiddlewareOptions>) => FastifyMiddlewareHooks["handler"];
11
+ interface FastifyMiddlewareHooks {
12
+ handler: (req: FastifyRequest, res: FastifyReply, done: HookHandlerDoneFunction) => Promise<void>;
13
+ }
14
+ // Define the plugin function
15
+ type RpcFastifyPluginOptions = MiddlewareOptions<"fastify"> & {
16
+ isRPC: boolean;
17
+ };
18
+ //#endregion
19
+ //#region src/fastify/createMiddleware.d.ts
20
+ declare const createMiddleware: FastifyMiddlewareFn;
21
+ declare const createRPCMiddleware: FastifyMiddlewareFn;
22
+ //#endregion
23
+ //#region src/types.d.ts
24
+ type BodyResult = {
25
+ contentType: "application/json";
26
+ data: JsonValue;
27
+ } | {
28
+ contentType: "text/plain";
29
+ data: string;
30
+ };
31
+ // primitives and their compositions
32
+ type JsonPrimitive = string | number | boolean | null | undefined;
33
+ type JsonObject = {
34
+ [key: string]: JsonValue | JsonArray;
35
+ };
36
+ type JsonArray = JsonValue[];
37
+ type JsonValue = JsonPrimitive | JsonArray | JsonObject;
38
+ //#endregion
39
+ //#region src/fastify/helpers.d.ts
40
+ declare function attachRPC(app: FastifyInstance): Promise<void>;
41
+ declare function attachVite(app: FastifyInstance, vite: ViteDevServer): void;
42
+ declare const readBody: (req: FastifyRequest) => Promise<BodyResult>;
43
+ //#endregion
44
+ export { type FastifyMiddlewareFn, type FastifyMiddlewareHooks, type FastifyMiddlewareOptions, type RpcFastifyPluginOptions, attachRPC, attachVite, createMiddleware, createRPCMiddleware, readBody };
45
+ //# sourceMappingURL=fastify.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.d.mts","names":[],"sources":["../../src/fastify/types.d.ts","../../src/fastify/createMiddleware.ts","../../src/types.d.ts","../../src/fastify/helpers.ts"],"mappings":";;;;;;;;KAOY,2BAA2B;KAE3B,uBACV,UAAU,yCAEV,iBAAiB,QAAQ,8BACtB;UAEY;EACf,UACE,KAAK,gBACL,KAAK,cACL,MAAM,4BACH;;;KAIK,0BAA0B;EACpC;;;;cCLW,kBAAkB;cAyElB,qBAAqB;;;KCxDtB;EACN;EAAiC,MAAM;;EACvC;EAA2B;;;KAOrB;KACA;GAAgB,cAAc,YAAY;;KAC1C,YAAY;KACZ,YAAY,gBAAgB,YAAY;;;iBCzC9B,UAAU,KAAK,kBAAe;iBAMpC,WAAW,KAAK,iBAAiB,MAAM;cAU1C,WAAQ,KACd,mBACJ,QAAQ"}