@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/wiki/adapters.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Adapters
|
|
2
|
-
|
|
3
|
-
`@thednp/rpc` provides adapters for ExpressJS, Fastify, Hono, and Koa. Each adapter exports `attachRPC` for production and `attachVite` for development.
|
|
4
|
-
|
|
5
|
-
## Common Pattern
|
|
6
|
-
|
|
7
|
-
All adapters share the same two-function API:
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { attachRPC, attachVite } from '@thednp/rpc/<adapter>';
|
|
11
|
-
|
|
12
|
-
// Production: mount RPC middleware
|
|
13
|
-
await attachRPC(app);
|
|
14
|
-
|
|
15
|
-
// Development: mount RPC + Vite middleware
|
|
16
|
-
attachVite(app, vite);
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Express
|
|
20
|
-
|
|
21
|
-
### Installation
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
pnpm add @thednp/rpc express
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Usage
|
|
28
|
-
|
|
29
|
-
```ts
|
|
30
|
-
// server.ts
|
|
31
|
-
import express from 'express';
|
|
32
|
-
import { attachRPC, attachVite } from '@thednp/rpc/express';
|
|
33
|
-
import { createServer } from 'vite';
|
|
34
|
-
|
|
35
|
-
const app = express();
|
|
36
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
37
|
-
|
|
38
|
-
if (isDev) {
|
|
39
|
-
const vite = await createServer({ server: { middlewareMode: true } });
|
|
40
|
-
attachVite(app, vite);
|
|
41
|
-
} else {
|
|
42
|
-
await attachRPC(app);
|
|
43
|
-
app.use(express.static('dist'));
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
app.listen(3000);
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Fastify
|
|
50
|
-
|
|
51
|
-
### Installation
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
pnpm add @thednp/rpc fastify
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Usage
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
// server.ts
|
|
61
|
-
import Fastify from 'fastify';
|
|
62
|
-
import { attachRPC, attachVite } from '@thednp/rpc/fastify';
|
|
63
|
-
import { createServer } from 'vite';
|
|
64
|
-
|
|
65
|
-
const fastify = Fastify({ logger: true });
|
|
66
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
67
|
-
|
|
68
|
-
if (isDev) {
|
|
69
|
-
const vite = await createServer({ server: { middlewareMode: true } });
|
|
70
|
-
attachVite(fastify, vite);
|
|
71
|
-
} else {
|
|
72
|
-
await attachRPC(fastify);
|
|
73
|
-
fastify.register(require('@fastify/static'), { root: 'dist' });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
fastify.listen({ port: 3000 });
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
## Hono
|
|
80
|
-
|
|
81
|
-
### Installation
|
|
82
|
-
|
|
83
|
-
```bash
|
|
84
|
-
pnpm add @thednp/rpc hono
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Usage
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// server.ts
|
|
91
|
-
import { Hono } from 'hono';
|
|
92
|
-
import { attachRPC, attachVite } from '@thednp/rpc/hono';
|
|
93
|
-
import { createServer } from 'vite';
|
|
94
|
-
|
|
95
|
-
const app = new Hono();
|
|
96
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
97
|
-
|
|
98
|
-
if (isDev) {
|
|
99
|
-
const vite = await createServer({ server: { middlewareMode: true } });
|
|
100
|
-
attachVite(app, vite);
|
|
101
|
-
} else {
|
|
102
|
-
await attachRPC(app);
|
|
103
|
-
app.use('*', serveStatic({ root: './dist' }));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default app;
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
## Koa
|
|
110
|
-
|
|
111
|
-
### Installation
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
pnpm add @thednp/rpc koa koa-body
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### Usage
|
|
118
|
-
|
|
119
|
-
Body parser must be registered before the RPC middleware:
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// server.ts
|
|
123
|
-
import Koa from 'koa';
|
|
124
|
-
import { koaBody } from 'koa-body';
|
|
125
|
-
import { attachRPC, attachVite } from '@thednp/rpc/koa';
|
|
126
|
-
import { createServer } from 'vite';
|
|
127
|
-
|
|
128
|
-
const app = new Koa();
|
|
129
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
130
|
-
|
|
131
|
-
// Body parser must come before RPC middleware
|
|
132
|
-
app.use(koaBody({ jsonLimit: 1024 * 1024 }));
|
|
133
|
-
|
|
134
|
-
if (isDev) {
|
|
135
|
-
const vite = await createServer({ server: { middlewareMode: true } });
|
|
136
|
-
attachVite(app, vite);
|
|
137
|
-
} else {
|
|
138
|
-
await attachRPC(app);
|
|
139
|
-
app.use(require('koa-static')('dist'));
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
app.listen(3000);
|
|
143
|
-
```
|
package/wiki/best-practices.md
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
# Best Practices
|
|
2
|
-
|
|
3
|
-
## @thednp/rpc is a Transport Pipe
|
|
4
|
-
|
|
5
|
-
`@thednp/rpc` handles serialization and **transport only**. It does not provide:
|
|
6
|
-
|
|
7
|
-
- Caching
|
|
8
|
-
- Authentication
|
|
9
|
-
- Validation
|
|
10
|
-
- State management
|
|
11
|
-
- Retry logic
|
|
12
|
-
|
|
13
|
-
Use dedicated tools for these concerns.
|
|
14
|
-
|
|
15
|
-
## Client-Side Caching
|
|
16
|
-
|
|
17
|
-
All major frameworks have a `@tanstack/<framework>-query` made by [Tanstack](https://tanstack.com/) to cover all needs except validation and authentication.
|
|
18
|
-
|
|
19
|
-
For instance the `@tanstack/react-query` is the recommended layer for React apps client-side caching, invalidation, and stale-while-revalidate:
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
// src/components/GreetUser.tsx
|
|
23
|
-
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
|
24
|
-
import { sayHi } from '../api';
|
|
25
|
-
|
|
26
|
-
function GreetUser({ name }: { name: string }) {
|
|
27
|
-
const { data } = useQuery({
|
|
28
|
-
queryKey: ['say-hi', name],
|
|
29
|
-
queryFn: ({ signal }) => {
|
|
30
|
-
const result = sayHi(name);
|
|
31
|
-
signal.addEventListener('abort', () => result.cancel('query cancelled'));
|
|
32
|
-
return result.data;
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
return <div>{data ?? 'Loading...'}</div>;
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## AbortSignal Best Practices
|
|
40
|
-
|
|
41
|
-
Always check `signal.aborted` or call `signal.throwIfAborted()` in long-running server functions:
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
export const processBatch = createServerFunction(
|
|
45
|
-
'process-batch',
|
|
46
|
-
async (signal: AbortSignal, items: string[]) => {
|
|
47
|
-
const results = [];
|
|
48
|
-
const errors = [];
|
|
49
|
-
for (const item of items) {
|
|
50
|
-
signal.throwIfAborted();
|
|
51
|
-
const result = await heavyWork(item);
|
|
52
|
-
results.push(result);
|
|
53
|
-
// handle errors properly
|
|
54
|
-
}
|
|
55
|
-
return results;
|
|
56
|
-
},
|
|
57
|
-
);
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Input Validation
|
|
61
|
-
|
|
62
|
-
Always validate client-provided data before using it in server functions. Use libraries like **zod** or **valibot** to parse and validate inputs:
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
// src/api/server.ts
|
|
66
|
-
import { z } from 'zod';
|
|
67
|
-
import { createServerFunction } from '@thednp/rpc/server';
|
|
68
|
-
|
|
69
|
-
const ProfileSchema = z.object({
|
|
70
|
-
name: z.string().min(1).max(100),
|
|
71
|
-
age: z.number().int().positive(),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
export const updateProfile = createServerFunction(
|
|
75
|
-
'update-profile',
|
|
76
|
-
async (signal, raw) => {
|
|
77
|
-
const parsed = ProfileSchema.safeParse(raw);
|
|
78
|
-
if (!parsed.success) {
|
|
79
|
-
return { ok: false, errors: parsed.error.flatten().fieldErrors };
|
|
80
|
-
}
|
|
81
|
-
// parsed.data is fully typed
|
|
82
|
-
await saveToDb(parsed.data);
|
|
83
|
-
return { ok: true };
|
|
84
|
-
},
|
|
85
|
-
);
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Return validation errors as structured data — the client's `handleResponse` will surface them as an `Error`.
|
|
89
|
-
|
|
90
|
-
## Authentication
|
|
91
|
-
|
|
92
|
-
Use middleware before `createRPCMiddleware()`:
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
// Express
|
|
96
|
-
app.use(authMiddleware);
|
|
97
|
-
app.use(createRPCMiddleware());
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
> Never add auth hooks inside the plugin.
|
|
101
|
-
|
|
102
|
-
## Body Limits
|
|
103
|
-
|
|
104
|
-
In most cases you can rely on your framework's body-parser middleware:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
// Express
|
|
108
|
-
import express from "express";
|
|
109
|
-
const app = express();
|
|
110
|
-
app.use(express.json({ limit: 1024 * 1024 })); // or "1mb"
|
|
111
|
-
app.use(createRPCMiddleware());
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
```ts
|
|
115
|
-
// Fastify
|
|
116
|
-
import Fastify from "fastify";
|
|
117
|
-
|
|
118
|
-
const app = Fastify({ logger: false, bodyLimit: 1024 * 1024 }); // "1MB"
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// Hono
|
|
123
|
-
import { Hono } from "hono";
|
|
124
|
-
import { bodyLimit } from 'hono/body-limit';
|
|
125
|
-
|
|
126
|
-
const app = new Hono();
|
|
127
|
-
app.use('*', bodyLimit({ maxSize: 1024 * 1024 })) // 1MB
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
// Koa
|
|
132
|
-
import Koa from "koa";
|
|
133
|
-
import { koaBody } from 'koa-body';
|
|
134
|
-
|
|
135
|
-
const app = new Koa();
|
|
136
|
-
app.use(koaBody({ jsonLimit: 1024 * 1024 })); // 1MB
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
```ts
|
|
140
|
-
// SSR (custom http server with Vite middleware mode)
|
|
141
|
-
import { createMiddleware, readBody } from "@thednp/rpc/express";
|
|
142
|
-
import { loadRPCConfig } from "@thednp/rpc";
|
|
143
|
-
|
|
144
|
-
const config = await loadRPCConfig();
|
|
145
|
-
const MAX_BODY_SIZE = 1024 * 1024;
|
|
146
|
-
|
|
147
|
-
app.use(createMiddleware({
|
|
148
|
-
rpcPreffix: config.rpcPreffix,
|
|
149
|
-
handler: async (req, res, next) => {
|
|
150
|
-
const { data } = await readBody(req);
|
|
151
|
-
if (Buffer.byteLength(typeof data === "string" ? data : JSON.stringify(data)) > MAX_BODY_SIZE) {
|
|
152
|
-
res.statusCode = 413;
|
|
153
|
-
res.end("Payload Too Large");
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
req.body = data;
|
|
157
|
-
next();
|
|
158
|
-
},
|
|
159
|
-
}));
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
// SPA (dedicated RPC proxy server)
|
|
164
|
-
import { readBody } from "@thednp/rpc/express";
|
|
165
|
-
|
|
166
|
-
const MAX_BODY_SIZE = 1024 * 1024;
|
|
167
|
-
|
|
168
|
-
const bodyLimit = async (req, res, next) => {
|
|
169
|
-
const { data } = await readBody(req);
|
|
170
|
-
if (Buffer.byteLength(typeof data === "string" ? data : JSON.stringify(data)) > MAX_BODY_SIZE) {
|
|
171
|
-
res.statusCode = 413;
|
|
172
|
-
res.end("Payload Too Large");
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
req.body = data;
|
|
176
|
-
next();
|
|
177
|
-
};
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## SSR Guidance
|
|
181
|
-
|
|
182
|
-
- On the **server**, `createServerFunction` runs directly (not Vite-transformed).
|
|
183
|
-
- On the **client**, the plugin replaces server function calls with `fetch`-based client modules.
|
|
184
|
-
- Keep `src/entry-client.ts` and `src/entry-server.ts` separate for proper hydration.
|
|
185
|
-
|
|
186
|
-
## File Naming
|
|
187
|
-
|
|
188
|
-
Only files matching `server.ts`, `server.js`, `server.mjs`, `server.mts` in `src/api/` are scanned. Export functions individually for proper client module mapping:
|
|
189
|
-
|
|
190
|
-
```ts
|
|
191
|
-
// ✅ Good — individual exports
|
|
192
|
-
export const sayHi = createServerFunction('say-hi', fn);
|
|
193
|
-
export const add = createServerFunction('add-numbers', fn);
|
|
194
|
-
|
|
195
|
-
// ❌ Avoid — default export or bundled objects
|
|
196
|
-
export default { sayHi, add };
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
## Cache is Not @thednp/rpc's Job
|
|
200
|
-
|
|
201
|
-
Use `react-query`, `SWR`, or your framework's data hooks for caching. `@thednp/rpc` is the **transport layer only**.
|
package/wiki/client-usage.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Client Usage
|
|
2
|
-
|
|
3
|
-
## Auto-Generated Client Modules
|
|
4
|
-
|
|
5
|
-
When you import from `./api` in your client code, the plugin intercepts the import and generates a client module for each server function.
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
import { sayHi, add } from './api';
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Each imported function returns:
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
{ data: Promise<T>, cancel: (reason: string) => void }
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
- **`data`** — A promise that resolves to the server function's return value.
|
|
18
|
-
- **`cancel(reason)`** — Aborts the underlying fetch request, causing `signal.aborted` to be set in the server function.
|
|
19
|
-
|
|
20
|
-
### Example
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
import { sayHi } from './api';
|
|
24
|
-
|
|
25
|
-
const { data, cancel } = sayHi('World');
|
|
26
|
-
const result = await data; // "Hello World!"
|
|
27
|
-
cancel('user cancelled'); // triggers AbortController on the client side
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Error Handling
|
|
31
|
-
|
|
32
|
-
- **Fetch errors** (network failure, CORS) — thrown from `await data`
|
|
33
|
-
- **HTTP 4xx/5xx responses** — thrown from `await data`
|
|
34
|
-
- **Cancellation** — returns `"Request cancelled by user"` from `await data`
|
|
35
|
-
|
|
36
|
-
## @tanstack/react-query Integration
|
|
37
|
-
|
|
38
|
-
`@thednp/rpc` is a dumb pipe — it handles serialization and transport only. For client-side caching, invalidation, and stale-while-revalidate patterns, use `@tanstack/react-query`:
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
42
|
-
import { sayHi } from './api';
|
|
43
|
-
|
|
44
|
-
function GreetUser({ name }: { name: string }) {
|
|
45
|
-
const queryClient = useQueryClient();
|
|
46
|
-
|
|
47
|
-
const { data } = useQuery({
|
|
48
|
-
queryKey: ['say-hi', name],
|
|
49
|
-
queryFn: ({ signal }) => {
|
|
50
|
-
const result = sayHi(name);
|
|
51
|
-
signal.addEventListener('abort', () => result.cancel('query cancelled'));
|
|
52
|
-
return result.data;
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return <div>{data ?? 'Loading...'}</div>;
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Combine `cancel()` with React Query's `signal` for proper abort handling during component unmount or query invalidation.
|
|
61
|
-
|
|
62
|
-
Other frameworks have a `@tanstack/<framework>-query` made by [Tanstack](https://tanstack.com/).
|
package/wiki/configuration.md
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Configuration
|
|
2
|
-
|
|
3
|
-
## `rpc.config.ts`
|
|
4
|
-
|
|
5
|
-
Create `rpc.config.ts` in your project root for system-wide configuration:
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
import { defineConfig } from '@thednp/rpc';
|
|
9
|
-
|
|
10
|
-
export default defineConfig({
|
|
11
|
-
rpcPreffix: '__rpc',
|
|
12
|
-
adapter: 'express',
|
|
13
|
-
});
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## `vite.config.ts`
|
|
17
|
-
|
|
18
|
-
Update your `vite.config.ts` in your project root and set additional development options:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```ts
|
|
22
|
-
import { defineConfig } from 'vite';
|
|
23
|
-
import rpc from '@thednp/rpc';
|
|
24
|
-
|
|
25
|
-
export default defineConfig({
|
|
26
|
-
plugins: [rpc(/* development options */)]
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
> **NOTE** these plugin options only apply to **development** and override the options in `rpc.config.ts`.
|
|
32
|
-
|
|
33
|
-
### Options
|
|
34
|
-
|
|
35
|
-
| Option | Type | Default | Description |
|
|
36
|
-
| --------------| ----------| -------------| --------------------------------------------------------------|
|
|
37
|
-
| `rpcPreffix` | `string` | `'__rpc'` | RPC endpoint prefix used in URL routing |
|
|
38
|
-
| `adapter` | `string` | `'express'` | Target adapter (`'express'`, `'fastify'`, `'hono'`, `'koa'`) |
|
|
39
|
-
|
|
40
|
-
## Config File Discovery
|
|
41
|
-
|
|
42
|
-
When `configFile` is not specified, the plugin searches for config files in this order:
|
|
43
|
-
|
|
44
|
-
1. `rpc.config.ts`
|
|
45
|
-
2. `rpc.config.js`
|
|
46
|
-
3. `rpc.config.mjs`
|
|
47
|
-
4. `rpc.config.mts`
|
|
48
|
-
5. `.rpcrc.ts`
|
|
49
|
-
6. `.rpcrc.js`
|
|
50
|
-
|
|
51
|
-
The first file found is used. If none is found, defaults are applied.
|
|
52
|
-
|
|
53
|
-
## Utilities
|
|
54
|
-
|
|
55
|
-
### `defineConfig`
|
|
56
|
-
|
|
57
|
-
Type-safe helper for creating the config object. Provides autocomplete and type checking for all options.
|
|
58
|
-
|
|
59
|
-
```ts
|
|
60
|
-
import { defineConfig } from '@thednp/rpc';
|
|
61
|
-
|
|
62
|
-
export default defineConfig({
|
|
63
|
-
rpcPreffix: '__rpc',
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### `loadRPCConfig`
|
|
68
|
-
|
|
69
|
-
Programmatically load the RPC config, useful in custom server setups:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { loadRPCConfig } from '@thednp/rpc';
|
|
73
|
-
|
|
74
|
-
const config = await loadRPCConfig();
|
|
75
|
-
console.log(config.rpcPreffix); // '__rpc'
|
|
76
|
-
console.log(config.adapter); // 'express'
|
|
77
|
-
```
|
package/wiki/getting-started.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Getting Started
|
|
2
|
-
|
|
3
|
-
## Installation
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
pnpm add @thednp/rpc@latest
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npm install @thednp/rpc@latest
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
bun add @thednp/rpc@latest
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
deno add npm:@thednp/rpc@latest
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Quick Start
|
|
22
|
-
|
|
23
|
-
### 1. Configure system wide configuration `rpc.config.ts`
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { defineConfig } from "@thednp/rpc";
|
|
27
|
-
|
|
28
|
-
export default defineConfig({
|
|
29
|
-
rpcPreffix: "__server",
|
|
30
|
-
adapter: "express",
|
|
31
|
-
});
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Currently `@thednp/rpc` supports `'express'`, `'fastify'`, `'hono'` and `'koa'`. Check [adapters](./adapters.md) for more guides.
|
|
35
|
-
Also check [configuration](./configuration.md) for more guides.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
### 2. Add the plugin to `vite.config.ts`
|
|
39
|
-
|
|
40
|
-
```ts
|
|
41
|
-
import rpc from '@thednp/rpc';
|
|
42
|
-
|
|
43
|
-
export default {
|
|
44
|
-
plugins: [rpc()],
|
|
45
|
-
};
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Check [configuration](./configuration.md) for more guides.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
### 3. Create a server function in `src/api/server.ts`
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
import { createServerFunction } from '@thednp/rpc/server';
|
|
55
|
-
|
|
56
|
-
export const sayHi = createServerFunction(
|
|
57
|
-
'say-hi',
|
|
58
|
-
async (signal: AbortSignal, name: string) => {
|
|
59
|
-
signal.throwIfAborted();
|
|
60
|
-
await new Promise((res) => setTimeout(res, 1500));
|
|
61
|
-
return `Hello ${name}!`;
|
|
62
|
-
},
|
|
63
|
-
);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### 4. Use it on the client
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
import { sayHi } from './api';
|
|
70
|
-
|
|
71
|
-
const { data, cancel } = sayHi('World');
|
|
72
|
-
const result = await data; // "Hello World!"
|
|
73
|
-
cancel('user cancelled');
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
That's it — the plugin auto-scans `src/api/server.ts`, maps exports to client functions, and replaces `./api` imports with fetch-based client modules during the Vite transform.
|
package/wiki/index.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# @thednp/rpc
|
|
2
|
-
|
|
3
|
-
A Vite plugin for creating server functions with automatic Remote Procedure Calls (RPC) generation. Server functions are defined in a dedicated file, auto-scanned, and transformed into client-side fetch modules — no manual API route setup required.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- File-level server code isolation without directives like `'use server'`
|
|
8
|
-
- System-wide configuration via `rpc.config.ts`
|
|
9
|
-
- Automatic RPC generation for server functions
|
|
10
|
-
- `AbortController` support for request cancellation
|
|
11
|
-
- Adapters for Express, Fastify, Hono, and Koa with unified API
|
|
12
|
-
- Framework-agnostic core
|
|
13
|
-
- TypeScript support
|
|
14
|
-
- Path-segment prefix matching prevents URL boundary bypass
|
|
15
|
-
- @tanstack/react-query integration for client-side caching and invalidation
|
|
16
|
-
|
|
17
|
-
## Table of Contents
|
|
18
|
-
|
|
19
|
-
- [Getting Started](getting-started.md) — Installation and quick start
|
|
20
|
-
- [Setup Guide](setup.md) — Project structure and configuration
|
|
21
|
-
- [Configuration](configuration.md) — Configuration reference
|
|
22
|
-
- [Server Functions](server-functions.md) — Creating server functions
|
|
23
|
-
- [Client Usage](client-usage.md) — Client-side usage
|
|
24
|
-
- [Adapters](adapters.md) — Framework adapters
|
|
25
|
-
- [Security](security.md) — Security hardening
|
|
26
|
-
- [Best Practices](best-practices.md) — Tips and best practices
|
package/wiki/security.md
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
# Security
|
|
2
|
-
|
|
3
|
-
## Prefix Boundary Check
|
|
4
|
-
|
|
5
|
-
All adapters use `new RegExp(\`^/${rpcPreffix}/\`)` instead of `startsWith` to match the RPC endpoint path. This prevents path-segment bypass attacks:
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
rpcPreffix = '__rpc'
|
|
9
|
-
|
|
10
|
-
# Safe: matches /__rpc/foo
|
|
11
|
-
// __rpc/foo → /__rpc/
|
|
12
|
-
|
|
13
|
-
# Not matched: /__rpc-evil/foo or /foo/__rpc/bar
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Using `startsWith` would incorrectly match paths like `/__rpc-evil/foo`, which could route to unintended handlers. The regex ensures the prefix is a standalone path segment.
|
|
17
|
-
|
|
18
|
-
## Koa URL Normalization
|
|
19
|
-
|
|
20
|
-
The Koa adapter parses `ctx.url` through `new URL()` before prefix checking. This strips query strings and normalizes encoding, preventing query-string injection attacks:
|
|
21
|
-
|
|
22
|
-
```ts
|
|
23
|
-
const url = new URL(ctx.url, 'http://localhost');
|
|
24
|
-
const pathname = url.pathname; // clean, no query string
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
## Generic 404 Responses
|
|
28
|
-
|
|
29
|
-
Error responses do not echo the requested function name. This prevents function enumeration — an attacker cannot discover available RPC functions by probing for non-existent endpoints.
|
|
30
|
-
|
|
31
|
-
## Authentication via Middleware
|
|
32
|
-
|
|
33
|
-
Authentication is handled by middleware registered **before** `createRPCMiddleware()`. The middleware chain composes naturally:
|
|
34
|
-
|
|
35
|
-
```ts
|
|
36
|
-
// Express example
|
|
37
|
-
app.use(authMiddleware); // auth first
|
|
38
|
-
app.use(createRPCMiddleware()); // RPC second
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Do not add auth hooks inside the plugin. Use your framework's standard middleware pattern.
|
|
42
|
-
|
|
43
|
-
## Body Size Limits
|
|
44
|
-
|
|
45
|
-
JSON body size limits are handled by your framework's body-parser middleware:
|
|
46
|
-
|
|
47
|
-
- **Express**: `express.json({ limit: '1mb' })` (default **100kb**)
|
|
48
|
-
- **Fastify**: `bodyLimit` option in Fastify config (default **1 MiB**)
|
|
49
|
-
- **Koa**: `koa-body({ formLimit: '1mb' })`
|
|
50
|
-
- **Hono**: Built-in body size limiting
|
|
51
|
-
|
|
52
|
-
Since the RPC framework's `readBody` also accepts `text/plain` requests (not parsed by the JSON body parser), the raw stream path in the Express and Koa adapters has a built-in safety net — a `maxBodySize` parameter that defaults to **1 MiB**. Pass a custom value to `readBody(req, signal, myLimit)` to override.
|
|
53
|
-
|
|
54
|
-
Register body parsing middleware before `createRPCMiddleware()`.
|