@thednp/rpc 0.0.4 → 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/README.md +2 -0
- package/dist/fastify/fastify.d.mts +21 -8
- package/dist/fastify/fastify.d.mts.map +1 -1
- package/dist/fastify/fastify.mjs +1 -6
- package/dist/fastify/fastify.mjs.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.d.mts +8 -8
- package/dist/fastify/plugin/fastify/plugin.d.mts.map +1 -1
- package/dist/fastify/plugin/fastify/plugin.mjs +1 -6
- package/dist/fastify/plugin/fastify/plugin.mjs.map +1 -1
- package/dist/helpers/helpers.d.mts +11 -10
- package/dist/helpers/helpers.d.mts.map +1 -1
- package/dist/helpers/helpers.mjs.map +1 -1
- package/dist/hono/hono.d.mts +9 -1
- package/dist/hono/hono.d.mts.map +1 -1
- package/dist/hono/hono.mjs.map +1 -1
- package/dist/index.d.mts +29 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/server/server.d.mts +17 -16
- package/dist/server/server.d.mts.map +1 -1
- package/dist/server/server.mjs.map +1 -1
- package/package.json +3 -4
- package/wiki/adapters.md +0 -172
- package/wiki/best-practices.md +0 -364
- package/wiki/client-usage.md +0 -66
- package/wiki/configuration.md +0 -76
- package/wiki/getting-started.md +0 -105
- package/wiki/index.md +0 -14
- package/wiki/security.md +0 -76
- package/wiki/server-functions.md +0 -137
- package/wiki/setup.md +0 -81
package/wiki/server-functions.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Server Functions
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Server functions run exclusively on the server. They have access to server-only resources (databases, file system, environment variables, private APIs) and **never execute on the client**.
|
|
6
|
-
|
|
7
|
-
The `@thednp/rpc` Vite plugin transforms imports of server functions into client-side stubs that call the real implementation over HTTP. This isomorphic bridge means you write your functions once and call them from either server-rendered pages or client-side code — the RPC middleware handles routing on the server while the generated client modules handle serialization, transport, and cancellation.
|
|
8
|
-
|
|
9
|
-
All examples except the SPA use SSR to demonstrate this: the same server functions are imported directly during server-side rendering (in `entry-server.ts`) and also called from client-side JavaScript (via the auto-generated fetch module).
|
|
10
|
-
|
|
11
|
-
The [SPA example](../examples/spa) uses a thin `node:http` based proxy that executes the server functions.
|
|
12
|
-
|
|
13
|
-
## `createServerFunction(name, handler, options?)`
|
|
14
|
-
|
|
15
|
-
The core API for defining server-side functions.
|
|
16
|
-
|
|
17
|
-
### Signature
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
function createServerFunction<T>(
|
|
21
|
-
name: string,
|
|
22
|
-
handler: (signal: AbortSignal, ...args: JsonArray) => Promise<T>,
|
|
23
|
-
options?: {
|
|
24
|
-
contentType?: 'application/json' | 'text/plain',
|
|
25
|
-
credentials?: "same-origin" | "include" | "omit",
|
|
26
|
-
method?: "GET" | "POST",
|
|
27
|
-
}
|
|
28
|
-
): ServerFunction<T>;
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Parameters
|
|
32
|
-
|
|
33
|
-
- **`name`** (`string`) — The registered name used in RPC routing.
|
|
34
|
-
- **`handler`** (`(signal: AbortSignal, ...args: JsonArray) => Promise<T>`) — The actual implementation. The first argument is always an `AbortSignal`; remaining arguments come from the client. The return value must be JSON-serializable.
|
|
35
|
-
- **`options`** — Optional credentials, serialization strategy, and HTTP method
|
|
36
|
-
* `contentType?: 'application/json' | 'text/plain'` - Defaults to `'application/json'`.
|
|
37
|
-
* `credentials?: "include" | "same-origin" | "omit"` - Defaults to `'same-origin'`.
|
|
38
|
-
* `method?: "GET" | "POST"` - Defaults to `'POST'`.
|
|
39
|
-
|
|
40
|
-
### HTTP Method
|
|
41
|
-
|
|
42
|
-
By default every server function is invoked via `POST`. Functions that are safe to call from a browser URL bar, a `<script>` tag, or a CDN can opt into `GET` — arguments then travel as an `?args=` JSON query parameter:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
export const publicData = createServerFunction(
|
|
46
|
-
'public-data',
|
|
47
|
-
async (signal, topic: string) => {
|
|
48
|
-
return await fetchPublicData(topic);
|
|
49
|
-
},
|
|
50
|
-
{ method: 'GET' },
|
|
51
|
-
);
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The generated client module issues a `GET /__rpc/public-data?args=%5B%22news%22%5D` request. The middleware rejects requests whose HTTP method does not match the function's configured method with `405 Method Not Allowed` — so `POST`-only functions are safe from cross-site `GET` requests, and `GET` functions can be linked/bookmarked directly.
|
|
55
|
-
|
|
56
|
-
> **Security note:** defaulting to `POST` prevents CSRF via `<img>`/`<script>`/form `GET` requests. Only set `method: "GET"` for functions with no side effects.
|
|
57
|
-
|
|
58
|
-
> **Why only `GET` and `POST`?** This is deliberate, not an oversight:
|
|
59
|
-
>
|
|
60
|
-
> - RPC dispatch is not REST — functions have no resource semantics, so the meanings of `PUT` (idempotent replace), `PATCH` (partial update), or `DELETE` (removal) don't apply to a function call. The only transport distinctions that matter are `POST` (args in the body, any payload) and `GET` (args in the query string, cacheable by browsers and CDNs).
|
|
61
|
-
> - `OPTIONS` is reserved by the HTTP protocol for CORS preflight; browsers send it automatically, and frameworks handle it. Exposing it as a function method would collide with framework CORS handling.
|
|
62
|
-
> - `HEAD` is derived from `GET` at the HTTP layer, so it needs no function-level support.
|
|
63
|
-
> - Every accepted method is another dispatch path to validate. Keeping the surface minimal (and defaulting to `POST`) reduces CSRF and parsing attack surface.
|
|
64
|
-
>
|
|
65
|
-
> If a concrete need arises (e.g. a REST-style wrapper wanting true `PUT` semantics), the `method` union is a one-line extension — adapters already centralize dispatch on it.
|
|
66
|
-
|
|
67
|
-
### AbortSignal
|
|
68
|
-
|
|
69
|
-
The first argument to every server function is an `AbortSignal`. This allows the client to cancel a request:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
export const longTask = createServerFunction(
|
|
73
|
-
'long-task',
|
|
74
|
-
async (signal: AbortSignal, id: string) => {
|
|
75
|
-
signal.throwIfAborted(); // throws if client cancelled
|
|
76
|
-
// ... do work ...
|
|
77
|
-
signal.throwIfAborted(); // check again after each step
|
|
78
|
-
return result;
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
Use `signal.aborted` or `signal.throwIfAborted()` in long-running functions to respond to cancellation promptly.
|
|
84
|
-
|
|
85
|
-
### Registration
|
|
86
|
-
|
|
87
|
-
When `createServerFunction` is called, it registers the function in a server-side map keyed by `name`. This map is used by the RPC middleware to route incoming requests to the correct implementation.
|
|
88
|
-
|
|
89
|
-
### Return Type
|
|
90
|
-
|
|
91
|
-
The return value of `handler` is serialized to JSON and sent as the HTTP response body. **Ensure your return type is JSON-serializable.**
|
|
92
|
-
|
|
93
|
-
## Input Validation
|
|
94
|
-
|
|
95
|
-
Server functions receive raw, untrusted client data. **Always validate data within your server functions before use.**
|
|
96
|
-
|
|
97
|
-
**zod:**
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
import { z } from 'zod';
|
|
101
|
-
import { createServerFunction } from '@thednp/rpc/server';
|
|
102
|
-
|
|
103
|
-
const AddSchema = z.object({
|
|
104
|
-
a: z.number(),
|
|
105
|
-
b: z.number(),
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
export const add = createServerFunction('add', async (signal, raw) => {
|
|
109
|
-
const parsed = AddSchema.safeParse(raw);
|
|
110
|
-
if (!parsed.success) {
|
|
111
|
-
return { error: parsed.error.flatten() };
|
|
112
|
-
}
|
|
113
|
-
return parsed.data.a + parsed.data.b;
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**valibot:**
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
import * as v from 'valibot';
|
|
121
|
-
import { createServerFunction } from '@thednp/rpc/server';
|
|
122
|
-
|
|
123
|
-
const AddSchema = v.object({
|
|
124
|
-
a: v.number(),
|
|
125
|
-
b: v.number(),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
export const add = createServerFunction('add', async (signal, raw) => {
|
|
129
|
-
const parsed = v.safeParse(AddSchema, raw);
|
|
130
|
-
if (parsed.issues) {
|
|
131
|
-
return { error: v.flatten(parsed.issues).nested };
|
|
132
|
-
}
|
|
133
|
-
return parsed.output.a + parsed.output.b;
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
Validation errors return structured data instead of throwing — the client's auto-generated `handleResponse` receives `{ error: ... }` and surfaces it as an `Error`.
|
package/wiki/setup.md
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# Setup
|
|
2
|
-
|
|
3
|
-
## Required Project Structure
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
project/
|
|
7
|
-
├── src/
|
|
8
|
-
│ ├── api/
|
|
9
|
-
│ │ └── index.ts # All server functions exports
|
|
10
|
-
│ │ └── server.ts # Auto-scanned server functions
|
|
11
|
-
│ ├── entry-client.ts # Client entry (SSR projects)
|
|
12
|
-
│ └── entry-server.ts # Server entry (SSR projects)
|
|
13
|
-
├── vite.config.ts # Add rpc() plugin here
|
|
14
|
-
├── rpc.config.ts # Optional config
|
|
15
|
-
├── package.json # The project npm configuration
|
|
16
|
-
└── server.js # Your Express/Hono/Fastify/Koa server
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
> Various frameworks like `@tanstack-start`, `@sveltejs/kit` prefer a more specific structure, so be sure to check their documentation; most frameworks have their own data transport layer.
|
|
20
|
-
|
|
21
|
-
## Server Files
|
|
22
|
-
|
|
23
|
-
The plugin looks in `src/api/` for files with these **exact** names (matching is case-sensitive and non-partial, so `server.tsx`, `my-server.ts`, or `server.txt` are ignored):
|
|
24
|
-
|
|
25
|
-
- `server.ts`
|
|
26
|
-
- `server.js`
|
|
27
|
-
- `server.mjs`
|
|
28
|
-
- `server.mts`
|
|
29
|
-
|
|
30
|
-
Each matched file is loaded with `vite.ssrLoadModule`, and all named exports are mapped to client functions. Export each function individually for proper mapping.
|
|
31
|
-
|
|
32
|
-
## How Auto-Scanning Works
|
|
33
|
-
|
|
34
|
-
1. During `resolveId`, the plugin intercepts imports from `./api` (or paths under `src/api/`).
|
|
35
|
-
2. It scans `src/api/` for the server files listed above and loads them via `vite.ssrLoadModule`.
|
|
36
|
-
3. It builds a map of export names to their `createServerFunction` registration names.
|
|
37
|
-
4. During `transform`, it replaces the import with generated client modules that use `fetch` API under the hood.
|
|
38
|
-
|
|
39
|
-
## SSR vs SPA
|
|
40
|
-
|
|
41
|
-
### SSR Projects
|
|
42
|
-
|
|
43
|
-
Create both `src/entry-client.ts` and `src/entry-server.ts`. The client and server bundles both import from `./api`. On the client your server function gets transformed by the plugin. On the server, `createServerFunction` runs directly (not transformed).
|
|
44
|
-
|
|
45
|
-
### SPA Projects
|
|
46
|
-
|
|
47
|
-
Import directly from `./api` in your client code. No server entry is needed.
|
|
48
|
-
|
|
49
|
-
## Production Adapters
|
|
50
|
-
|
|
51
|
-
Use the appropriate adapter for your framework:
|
|
52
|
-
|
|
53
|
-
```ts
|
|
54
|
-
// Express
|
|
55
|
-
import { attachRPC, attachVite } from '@thednp/rpc/express';
|
|
56
|
-
await attachRPC(app); // production
|
|
57
|
-
attachVite(app, vite); // development
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
// Fastify
|
|
62
|
-
import { attachRPC, attachVite } from '@thednp/rpc/fastify';
|
|
63
|
-
await attachRPC(app); // production
|
|
64
|
-
attachVite(app, vite); // development
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
// Hono
|
|
69
|
-
import { attachRPC, attachVite } from '@thednp/rpc/hono';
|
|
70
|
-
await attachRPC(app); // production
|
|
71
|
-
attachVite(app, vite); // development
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
// Koa
|
|
76
|
-
import { attachRPC, attachVite } from '@thednp/rpc/koa';
|
|
77
|
-
await attachRPC(app); // production
|
|
78
|
-
attachVite(app, vite); // development
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
See [Adapters](./adapters.md) for full server setup examples.
|