@atom-forge/rpc 0.3.2
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/LICENSE +28 -0
- package/README.en.md +612 -0
- package/README.hu.md +613 -0
- package/README.llm.md +343 -0
- package/README.md +25 -0
- package/dist/client/client-context.d.ts +45 -0
- package/dist/client/client-context.js +48 -0
- package/dist/client/create-client.d.ts +9 -0
- package/dist/client/create-client.js +277 -0
- package/dist/client/logger.d.ts +6 -0
- package/dist/client/logger.js +41 -0
- package/dist/client/middleware.d.ts +6 -0
- package/dist/client/middleware.js +7 -0
- package/dist/client/rpc-response.d.ts +27 -0
- package/dist/client/rpc-response.js +46 -0
- package/dist/client/types.d.ts +151 -0
- package/dist/client/types.js +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/server/create-handler.d.ts +18 -0
- package/dist/server/create-handler.js +210 -0
- package/dist/server/errors.d.ts +10 -0
- package/dist/server/errors.js +14 -0
- package/dist/server/middleware.d.ts +22 -0
- package/dist/server/middleware.js +39 -0
- package/dist/server/rpc.d.ts +65 -0
- package/dist/server/rpc.js +49 -0
- package/dist/server/server-context.d.ts +79 -0
- package/dist/server/server-context.js +86 -0
- package/dist/server/types.d.ts +30 -0
- package/dist/server/types.js +1 -0
- package/dist/util/constants.d.ts +1 -0
- package/dist/util/constants.js +1 -0
- package/dist/util/cookies.d.ts +22 -0
- package/dist/util/cookies.js +54 -0
- package/dist/util/pipeline.d.ts +23 -0
- package/dist/util/pipeline.js +22 -0
- package/dist/util/string.d.ts +6 -0
- package/dist/util/string.js +11 -0
- package/dist/util/types.d.ts +5 -0
- package/dist/util/types.js +1 -0
- package/package.json +38 -0
package/README.hu.md
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# Rpc
|
|
2
|
+
|
|
3
|
+
Az Rpc egy teljes stackes RPC (Remote Procedure Call) keretrendszer TypeScript projektekhez. Leegyűsíti a kliens és a szerver közötti kommunikációt egy típusbiztos API biztosításával. **Framework-agnosztikus** — bármely Node.js vagy edge futtatókörnyezettel használható (SvelteKit, Express, Hono, stb.).
|
|
4
|
+
|
|
5
|
+
## Telepítés
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @atom-forge/rpc
|
|
9
|
+
pnpm add @atom-forge/rpc
|
|
10
|
+
yarn add @atom-forge/rpc
|
|
11
|
+
bun add @atom-forge/rpc
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Alapkoncepció: Végpontok közötti típusbiztonság
|
|
15
|
+
|
|
16
|
+
Az Rpc fő funkciója a szerver és a kliens közötti végpontok közötti típusbiztonság biztosítása. Az API-t a szerveren definiálod, majd megosztod a definíció típusát a klienssel. Ez automatikus kiegészítést és típusellenőrzést biztosít az API-hívásokhoz.
|
|
17
|
+
|
|
18
|
+
**1. Definiáld az API-t és hozd létre a handlert a szerveren:**
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// api.ts (megosztott API definíció)
|
|
22
|
+
import { rpc } from '@atom-forge/rpc';
|
|
23
|
+
|
|
24
|
+
export const api = {
|
|
25
|
+
posts: {
|
|
26
|
+
list: rpc.query(async ({ page }: { page: number }, ctx) => {
|
|
27
|
+
// ... bejegyzések lekérése
|
|
28
|
+
return { posts: [{ id: 1, title: 'Helló' }] };
|
|
29
|
+
}),
|
|
30
|
+
create: rpc.command(async ({ title }: { title: string }) => {
|
|
31
|
+
// ... bejegyzés létrehozása
|
|
32
|
+
return { success: true };
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// SvelteKit: src/routes/rpc/[...path]/+server.ts
|
|
40
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
41
|
+
import { api } from '$lib/api';
|
|
42
|
+
|
|
43
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
44
|
+
|
|
45
|
+
export const GET = (event) => handle(event.request, { path: event.params.path }, event);
|
|
46
|
+
export const POST = GET;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**2. Használd a típust a kliensen:**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// src/lib/client/rpc.ts
|
|
53
|
+
import { createClient } from '@atom-forge/rpc';
|
|
54
|
+
import type { api } from '$lib/api';
|
|
55
|
+
|
|
56
|
+
const [client, cfg] = createClient<typeof api>('/rpc');
|
|
57
|
+
|
|
58
|
+
// Minden hívás RpcResponse-t ad vissza
|
|
59
|
+
const res = await client.posts.list.$query({ page: 1 });
|
|
60
|
+
if (res.isOK()) {
|
|
61
|
+
console.log(res.result); // típusa: { posts: { id: number, title: string }[] }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await client.posts.create.$command({ title: 'Új bejegyzésem' });
|
|
65
|
+
|
|
66
|
+
export default client;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Framework adapterek
|
|
70
|
+
|
|
71
|
+
Az Rpc core `createCoreHandler` függvénye szabványos `Request` → `Response` alapon működik. Minden frameworkhöz ~2-5 sor adapter kód szükséges.
|
|
72
|
+
|
|
73
|
+
### SvelteKit
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// src/routes/rpc/[...path]/+server.ts
|
|
77
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
78
|
+
import { api } from '$lib/api';
|
|
79
|
+
|
|
80
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
81
|
+
|
|
82
|
+
export const GET = (event) => handle(event.request, { path: event.params.path }, event);
|
|
83
|
+
export const POST = GET;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
SvelteKit esetén a `ctx.adapterContext` a `RequestEvent` objektum, így a `locals`, `platform` stb. elérhetők:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// ctx.adapterContext típusa: RequestEvent
|
|
90
|
+
const user = (ctx.adapterContext as RequestEvent).locals.user;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Alternatíva: `hooks.server.ts`**
|
|
94
|
+
|
|
95
|
+
Route fájl helyett a szerver hookból is elfoghatod az RPC kéréseket — hasznos, ha már van `hooks.server.ts`, vagy ha egy helyen szeretnéd tartani az összes szerver logikát:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// src/hooks.server.ts
|
|
99
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
100
|
+
import { api } from '$lib/api';
|
|
101
|
+
|
|
102
|
+
const handleRpc = createCoreHandler(flattenApiDefinition(api));
|
|
103
|
+
|
|
104
|
+
export const handle = async ({ event, resolve }) => {
|
|
105
|
+
if (event.url.pathname.startsWith('/rpc/')) {
|
|
106
|
+
const path = event.url.pathname.slice('/rpc/'.length);
|
|
107
|
+
return handleRpc(event.request, { path }, event);
|
|
108
|
+
}
|
|
109
|
+
return resolve(event);
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Nem kell route fájl. A hook a SvelteKit routerje előtt fut, így nincs szükség `src/routes/rpc/` könyvtárra sem.
|
|
114
|
+
|
|
115
|
+
### Express
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
119
|
+
import { api } from './api';
|
|
120
|
+
|
|
121
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
122
|
+
|
|
123
|
+
app.all('/rpc/:path', async (req, res) => {
|
|
124
|
+
const request = new Request(
|
|
125
|
+
`${req.protocol}://${req.get('host')}${req.originalUrl}`,
|
|
126
|
+
{ method: req.method, headers: req.headers as any, body: req.method !== 'GET' ? req : null }
|
|
127
|
+
);
|
|
128
|
+
const response = await handle(request, { path: req.params.path }, { req, res });
|
|
129
|
+
res.status(response.status);
|
|
130
|
+
response.headers.forEach((v, k) => res.setHeader(k, v));
|
|
131
|
+
res.send(Buffer.from(await response.arrayBuffer()));
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Hono
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
139
|
+
import { api } from './api';
|
|
140
|
+
|
|
141
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
142
|
+
|
|
143
|
+
app.all('/rpc/:path', (c) => handle(c.req.raw, { path: c.req.param('path') }, c));
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Next.js (App Router)
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// app/rpc/[...path]/route.ts
|
|
150
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
151
|
+
import { api } from '@/lib/api';
|
|
152
|
+
|
|
153
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
154
|
+
|
|
155
|
+
export async function GET(request: Request, { params }: { params: Promise<{ path: string[] }> }) {
|
|
156
|
+
const { path } = await params;
|
|
157
|
+
return handle(request, { path: path.join('.') }, { request, params });
|
|
158
|
+
}
|
|
159
|
+
export const POST = GET;
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> `path.join('.')` az URL szegmenseket (`['users', 'get-all']`) visszaalakítja a dot-szeparált útvonallá (`'users.get-all'`). Next.js 15-től a `params` Promise — ezért kell az `await`.
|
|
163
|
+
|
|
164
|
+
### Nuxt 3
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// server/routes/rpc/[...path].ts
|
|
168
|
+
import { createCoreHandler, flattenApiDefinition } from '@atom-forge/rpc';
|
|
169
|
+
import { getRouterParam, toWebRequest } from 'h3';
|
|
170
|
+
import { api } from '~/lib/api';
|
|
171
|
+
|
|
172
|
+
const handle = createCoreHandler(flattenApiDefinition(api));
|
|
173
|
+
|
|
174
|
+
export default defineEventHandler(async (event) => {
|
|
175
|
+
const path = getRouterParam(event, 'path') ?? '';
|
|
176
|
+
return handle(toWebRequest(event), { path }, event);
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
> A h3 `toWebRequest()` konvertálja az h3 eventet szabványos `Request`-té. A `defineEventHandler` natívan képes `Response` objektumot visszaadni.
|
|
181
|
+
|
|
182
|
+
## Kommunikációs protokoll
|
|
183
|
+
|
|
184
|
+
Az Rpc elsődlegesen a **MessagePack** protokollt használja a hatékonyság és teljesítmény érdekében. Azon kliensek számára, amelyek nem támogatják a MessagePacket, **JSON** formátumra is visszaeshet.
|
|
185
|
+
|
|
186
|
+
- **`$command`**: Az adatokat a kérés törzsében küldi, MessagePack kódolással (`application/msgpack`). A szerver az egyszerű JSON-t (`application/json`) is elfogadja.
|
|
187
|
+
- **`$query`**: Az adatokat az URL lekérdezési paraméterében küldi, MessagePack és Base64 kódolással. Ez az ajánlott módszer lekérdezésekhez.
|
|
188
|
+
- **`$get`**: Az adatokat egyszerű szövegként küldi az URL lekérdezési paraméterében. Hasznos, ha a kliens nem támogatja a MessagePacket, vagy egyszerű, nem összetett lekérdezéseknél.
|
|
189
|
+
|
|
190
|
+
A szerver automatikusan észleli a kliens `Accept` fejlécét, és vagy MessagePack vagy JSON formátumban válaszol.
|
|
191
|
+
|
|
192
|
+
### Válasz fejlécek
|
|
193
|
+
|
|
194
|
+
Minden válasz tartalmazza az alábbi fejléceket:
|
|
195
|
+
|
|
196
|
+
| Fejléc | Leírás |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `x-atom-forge-rpc-exec-time` | Szerver oldali végrehajtási idő milliszekundumban. |
|
|
199
|
+
|
|
200
|
+
## Kliens oldali használat
|
|
201
|
+
|
|
202
|
+
### `createClient`
|
|
203
|
+
|
|
204
|
+
A `createClient` függvény új API klienst hoz létre. A kliens oldali hívás módja (`$query` vagy `$get`) meg kell, hogy egyezzen a szerveren definiált módszerrel (`rpc.query` vagy `rpc.get`).
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
import { createClient } from '@atom-forge/rpc';
|
|
208
|
+
import type { api } from './api';
|
|
209
|
+
|
|
210
|
+
const [client, cfg] = createClient<typeof api>('/rpc');
|
|
211
|
+
|
|
212
|
+
// Ha a szerver oldali endpoint rpc.query-vel van definiálva:
|
|
213
|
+
const result = await client.posts.list.$query({ page: 1 });
|
|
214
|
+
|
|
215
|
+
// Command hívás példa
|
|
216
|
+
await client.posts.create.$command({ title: 'Hello Világ' });
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Hívási opciók
|
|
220
|
+
|
|
221
|
+
Minden RPC metódus (`$command`, `$query`, `$get`) egy opcionális második argumentumot fogad el hívás szintű beállításokhoz:
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
const result = await client.posts.list.$query({ page: 1 }, {
|
|
225
|
+
// Kérés megszakítása AbortController segítségével
|
|
226
|
+
abortSignal: controller.signal,
|
|
227
|
+
|
|
228
|
+
// Upload/download haladás követése (XHR-t használ belül)
|
|
229
|
+
onProgress: ({ loaded, total, percent, phase }) => {
|
|
230
|
+
console.log(`${phase}: ${percent}%`);
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// Egyedi kérés fejlécek hozzáadása csak ehhez a híváshoz
|
|
234
|
+
headers: new Headers({ 'X-Custom-Header': 'érték' }),
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `RpcResponse`
|
|
239
|
+
|
|
240
|
+
Minden RPC hívás `RpcResponse`-t ad vissza, az alábbi tagokkal:
|
|
241
|
+
|
|
242
|
+
| Tag | Leírás |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `res.isOK()` | `true`, ha a hívás sikeres volt |
|
|
245
|
+
| `res.isError(code?)` | `true`, ha hiba; opcionálisan egy konkrét kódot ellenőriz |
|
|
246
|
+
| `res.status` | `'OK'` sikernél, vagy a hibakód stringje |
|
|
247
|
+
| `res.result` | Típusos sikeres adat, vagy a hiba részletei |
|
|
248
|
+
| `res.ctx` | A híváshoz tartozó teljes `ClientContext` |
|
|
249
|
+
|
|
250
|
+
**Hibakód formátumok:**
|
|
251
|
+
- Alkalmazás-szintű hibák: `'INVALID_ARGUMENT'`, `'PERMISSION_DENIED'`, egyedi kódok, stb.
|
|
252
|
+
- Transport hibák: `'HTTP:401'`, `'HTTP:404'`, `'HTTP:500'`, stb.
|
|
253
|
+
- Hálózati hibák: `'NETWORK_ERROR'`
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const res = await client.posts.create.$command({ title: 'Helló' });
|
|
257
|
+
|
|
258
|
+
if (res.isOK()) {
|
|
259
|
+
console.log(res.result); // típusos eredmény
|
|
260
|
+
} else if (res.isError('INVALID_ARGUMENT')) {
|
|
261
|
+
console.log(res.result.message); // hiba részletei
|
|
262
|
+
} else if (res.isError('HTTP:401')) {
|
|
263
|
+
// átirányítás a bejelentkezési oldalra
|
|
264
|
+
} else {
|
|
265
|
+
console.log(res.status, res.result); // bármilyen egyéb hiba
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Kontextus elérése (válasz fejlécek, eltelt idő, stb.)
|
|
269
|
+
console.log(res.ctx.response?.status);
|
|
270
|
+
console.log(res.ctx.elapsedTime);
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Fájlfeltöltés
|
|
274
|
+
|
|
275
|
+
A `$command` endpointok automatikusan felismerik, ha az argumentumokban `File` vagy `File[]` értékek szerepelnek, és átváltanak `multipart/form-data` kérésre. A fájlfeltöltéseket kombinálhatod normál argumentumokkal, és követheted a haladást.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// Szerver oldalon
|
|
279
|
+
const api = {
|
|
280
|
+
posts: {
|
|
281
|
+
create: rpc.command(async ({ title, cover }: { title: string; cover: File }) => {
|
|
282
|
+
// a cover egy File objektum
|
|
283
|
+
}),
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
// Kliens oldalon
|
|
288
|
+
const coverFile = document.querySelector('input[type=file]').files[0];
|
|
289
|
+
|
|
290
|
+
await client.posts.create.$command(
|
|
291
|
+
{ title: 'Helló', cover: coverFile },
|
|
292
|
+
{
|
|
293
|
+
onProgress: ({ percent, phase }) => console.log(`${phase}: ${percent}%`),
|
|
294
|
+
}
|
|
295
|
+
);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Több fájlhoz használj tömböt és `[]` végzőt a kulcson:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Szerver oldalon
|
|
302
|
+
const api = {
|
|
303
|
+
media: {
|
|
304
|
+
upload: rpc.command(async ({ files }: { files: File[] }) => { ... }),
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Kliens oldalon
|
|
309
|
+
await client.media.upload.$command({ 'files[]': selectedFiles });
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### `clientLogger`
|
|
313
|
+
|
|
314
|
+
A `clientLogger` egy beépített kliens middleware, amely RPC hívások részleteit naplózza a böngésző konzoljára — a kérés útvonalát, argumentumait, a választ, az időzítést és a HTTP státuszkódot.
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { createClient, clientLogger } from '@atom-forge/rpc';
|
|
318
|
+
|
|
319
|
+
const [client, cfg] = createClient<typeof api>('/rpc');
|
|
320
|
+
cfg.$ = clientLogger('/rpc'); // globális alkalmazás
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### `makeClientMiddleware`
|
|
324
|
+
|
|
325
|
+
A `makeClientMiddleware` függvény kliens oldali middleware létrehozására szolgál.
|
|
326
|
+
|
|
327
|
+
> ⚠️ **Mindig `return await next()`-et használj** a middleware-ben. Ha a `next()` hívás eredményét nem adod vissza, a válasz elvész és a hívó `undefined`-ot kap.
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import { makeClientMiddleware } from '@atom-forge/rpc';
|
|
331
|
+
|
|
332
|
+
const loggerMiddleware = makeClientMiddleware(async (ctx, next) => {
|
|
333
|
+
console.log('Kérés:', ctx.path, ctx.getArgs());
|
|
334
|
+
const result = await next(); // ✅ mindig add vissza a next() eredményét
|
|
335
|
+
console.log('Válasz:', ctx.result);
|
|
336
|
+
return result;
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Middleware alkalmazása az összes útvonalra
|
|
340
|
+
cfg.$ = loggerMiddleware;
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Szerver oldali használat
|
|
344
|
+
|
|
345
|
+
### `createCoreHandler` és `flattenApiDefinition`
|
|
346
|
+
|
|
347
|
+
A `createCoreHandler` framework-agnosztikus handler függvényt hoz létre, amelyik szabványos `Request` → `Response` alapon működik. A `flattenApiDefinition` előkészíti az API definíciót a handler számára.
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { createCoreHandler, flattenApiDefinition, rpc } from '@atom-forge/rpc';
|
|
351
|
+
|
|
352
|
+
const api = {
|
|
353
|
+
posts: {
|
|
354
|
+
// Ez az endpoint $query hívást vár a klienstől
|
|
355
|
+
list: rpc.query(async ({ page }, ctx) => {
|
|
356
|
+
ctx.cache.set(60);
|
|
357
|
+
return { posts: [] };
|
|
358
|
+
}),
|
|
359
|
+
// Ez az endpoint $get hívást vár a klienstől
|
|
360
|
+
getById: rpc.get(async ({ id }, ctx) => {
|
|
361
|
+
return { id, title: 'Példa bejegyzés' };
|
|
362
|
+
}),
|
|
363
|
+
// Ez az endpoint $command hívást vár a klienstől
|
|
364
|
+
create: rpc.command(async ({ title }) => {
|
|
365
|
+
// új bejegyzés létrehozása
|
|
366
|
+
}),
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const endpointMap = flattenApiDefinition(api);
|
|
371
|
+
const handle = createCoreHandler(endpointMap);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
#### Egyedi szerver kontextus
|
|
375
|
+
|
|
376
|
+
Megadhatsz egyedi szerver kontextus factory-t, hogy saját tulajdonságokat (pl. hitelesített felhasználó) injektálj minden handlerbe:
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { createCoreHandler, flattenApiDefinition, ServerContext } from '@atom-forge/rpc';
|
|
380
|
+
import type { RequestEvent } from '@sveltejs/kit';
|
|
381
|
+
|
|
382
|
+
class AppContext extends ServerContext<RequestEvent> {
|
|
383
|
+
get user() {
|
|
384
|
+
return this.adapterContext.locals.user;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const handle = createCoreHandler(flattenApiDefinition(api), {
|
|
389
|
+
createServerContext: (args, request, adapterContext) =>
|
|
390
|
+
new AppContext(args, request, adapterContext),
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### A `rpc` objektum
|
|
395
|
+
|
|
396
|
+
A `rpc` objektum az API endpointok definiálásához nyújt metódusokat. A szerveren használt metódus határozza meg, hogy a kliens hogyan kell meghívja az endpointot.
|
|
397
|
+
|
|
398
|
+
* `rpc.query`: MessagePack kódolt argumentumokat váró lekérdezési endpointot definiál. A kliensnek **`$query`**-t kell használnia.
|
|
399
|
+
* `rpc.get`: Egyszerű szöveges URL argumentumokat váró lekérdezési endpointot definiál. A kliensnek **`$get`**-t kell használnia.
|
|
400
|
+
* `rpc.command`: Parancsi endpointot definiál. A kliensnek **`$command`**-t kell használnia.
|
|
401
|
+
|
|
402
|
+
#### `rpcFactory`
|
|
403
|
+
|
|
404
|
+
Ha egyedi szerver kontextust használsz (lásd fent), a `rpcFactory` segítségével hozhatsz létre típusos `rpc` példányt, hogy a `ctx` megfelelően legyen típusozva a handlerekben:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { rpcFactory } from '@atom-forge/rpc';
|
|
408
|
+
|
|
409
|
+
const rpc = rpcFactory<AppContext>();
|
|
410
|
+
|
|
411
|
+
const api = {
|
|
412
|
+
posts: {
|
|
413
|
+
list: rpc.query(async ({ page }, ctx) => {
|
|
414
|
+
// ctx típusa: AppContext
|
|
415
|
+
const user = ctx.user;
|
|
416
|
+
return { posts: [] };
|
|
417
|
+
}),
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Szerver kontextus (`ctx`)
|
|
423
|
+
|
|
424
|
+
Minden handler és szerver oldali middleware kap egy `ctx` objektumot az alábbi tagokkal:
|
|
425
|
+
|
|
426
|
+
| Tag | Leírás |
|
|
427
|
+
|---|---|
|
|
428
|
+
| `ctx.request` | A szabványos Web API `Request` objektum. |
|
|
429
|
+
| `ctx.adapterContext` | A framework-specifikus kontextus (SvelteKit: `RequestEvent`, Hono: `Context`, stb.). |
|
|
430
|
+
| `ctx.getArgs()` | Visszaadja az összes argumentumot egyszerű objektumként. |
|
|
431
|
+
| `ctx.args` | Az argumentumok `Map<string, any>` formában. |
|
|
432
|
+
| `ctx.cookies` | Cookie kezelő: `get(name)`, `set(name, value, opts?)`, `delete(name, opts?)`, `getAll()`. |
|
|
433
|
+
| `ctx.headers.request` | A bejövő kérés fejlécei. |
|
|
434
|
+
| `ctx.headers.response` | A módosítható válasz fejlécek. |
|
|
435
|
+
| `ctx.cache.set(seconds)` | Beállítja a `Cache-Control` max-age értékét GET válaszoknál. |
|
|
436
|
+
| `ctx.cache.get()` | Visszaadja az aktuális cache időtartamát. |
|
|
437
|
+
| `ctx.status.set(code)` | Beállítja a HTTP válasz státuszkódját. |
|
|
438
|
+
| `ctx.status.notFound()` | Rövidítés a leggyakoribb HTTP kódokhoz (lásd lent). |
|
|
439
|
+
| `ctx.env` | `Map<string\|symbol, any>` az adatok middleware-ek közötti átadásához. |
|
|
440
|
+
| `ctx.elapsedTime` | Szerver oldali eltelt idő milliszekundumban. |
|
|
441
|
+
|
|
442
|
+
**Státusz rövidítések:** `ok`, `created`, `accepted`, `noContent`, `badRequest`, `unauthorized`, `forbidden`, `notFound`, `methodNotAllowed`, `conflict`, `tooManyRequests`, `serverError`, `serviceUnavailable`, és még sok más.
|
|
443
|
+
|
|
444
|
+
### Gyorsítótárazás
|
|
445
|
+
|
|
446
|
+
Az Rpc támogatja a szerver oldali gyorsítótárazást `GET` kérésekhez (mind `rpc.query`, mind `rpc.get` esetén). A cache időtartamát másodpercekben állíthatod be a `ctx.cache.set()` metódussal az endpoint implementációján belül.
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
const api = {
|
|
450
|
+
posts: {
|
|
451
|
+
list: rpc.query(async ({ page }, ctx) => {
|
|
452
|
+
ctx.cache.set(60); // Válasz gyorsítótárazása 60 másodpercre
|
|
453
|
+
return { posts: [] };
|
|
454
|
+
}),
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Hibakezelés
|
|
460
|
+
|
|
461
|
+
A szerveren a beépített helper függvényekkel küldhetsz alkalmazás-szintű hibákat. Ezek mindig `200 OK` választ adnak az `atomforge.rpc.error` kulccsal, így a kliens egy típusos `RpcResponse`-t kap.
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import { rpc } from '@atom-forge/rpc';
|
|
465
|
+
|
|
466
|
+
const api = {
|
|
467
|
+
posts: {
|
|
468
|
+
create: rpc.command(async ({ title }, ctx) => {
|
|
469
|
+
// SvelteKit esetén: (ctx.adapterContext as RequestEvent).locals.user
|
|
470
|
+
if (!ctx.adapterContext?.locals?.user) return rpc.error.permissionDenied();
|
|
471
|
+
if (title.length < 3) return rpc.error.invalidArgument({ message: 'A cím túl rövid' });
|
|
472
|
+
// ...
|
|
473
|
+
return { id: 1, title };
|
|
474
|
+
}),
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Egyedi hibakódhoz használd a `rpc.error.make` metódust:
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
return rpc.error.make('POST_ALREADY_EXISTS', 'Ez a slug már létezik', { slug: post.slug });
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
| Metódus | Hibakód | Mikor használd |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| `rpc.error.invalidArgument(details?)` | `INVALID_ARGUMENT` | Üzleti logikai validáció (Zod-on túl) |
|
|
488
|
+
| `rpc.error.permissionDenied(details?)` | `PERMISSION_DENIED` | Jogosultsági hiba |
|
|
489
|
+
| `rpc.error.internalError(details?)` | `INTERNAL_ERROR` | Kezelt belső hiba (auto `correlationId`) |
|
|
490
|
+
| `rpc.error.make(code, message?, result?)` | egyedi | Bármilyen egyedi hibakód |
|
|
491
|
+
|
|
492
|
+
### `zod` integráció
|
|
493
|
+
|
|
494
|
+
Az Rpc beépített `zod` támogatással rendelkezik a bemeneti validációhoz. Telepítsd a `zod`-ot a projekted függőségeként, és importáld közvetlenül onnan.
|
|
495
|
+
|
|
496
|
+
Ha a validáció sikertelen, az Rpc automatikusan alkalmazás-szintű hibát küld (`200 OK`) `INVALID_ARGUMENT` kóddal, és a `ZodIssue` tömböt az `issues` mezőben. A handler nem fut le.
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// Szerver oldalon
|
|
500
|
+
import { rpc } from '@atom-forge/rpc';
|
|
501
|
+
import { z } from 'zod';
|
|
502
|
+
|
|
503
|
+
const api = {
|
|
504
|
+
posts: {
|
|
505
|
+
create: rpc.zod({
|
|
506
|
+
title: z.string().min(3, "A cím legalább 3 karakter hosszú kell legyen."),
|
|
507
|
+
content: z.string().min(10),
|
|
508
|
+
}).command(async ({ title, content }) => {
|
|
509
|
+
// Ez a kód csak akkor fut le, ha a validáció sikeres
|
|
510
|
+
}),
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
A `rpc.zod` `query` és `get` esetén is működik:
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
rpc.zod({ id: z.number() }).query(async ({ id }, ctx) => { ... })
|
|
519
|
+
rpc.zod({ id: z.number() }).get(async ({ id }, ctx) => { ... })
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
A validációs hibákat a kliensen a `RpcResponse`-on keresztül kezelheted:
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
// Kliens oldalon
|
|
526
|
+
const res = await client.posts.create.$command({ title: 'Hi' });
|
|
527
|
+
if (res.isError('INVALID_ARGUMENT')) {
|
|
528
|
+
console.log(res.result.issues); // ZodIssue[]
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
### `makeServerMiddleware`
|
|
533
|
+
|
|
534
|
+
A `makeServerMiddleware` függvény szerver oldali middleware létrehozására szolgál. Egy opcionális második argumentum segítségével accessor függvényeket csatolhatsz a middleware-hez, ami hasznos az újrafelhasználható, önálló middleware-ek és segédprogramok létrehozásához.
|
|
535
|
+
|
|
536
|
+
> ⚠️ **Mindig `return await next()`-et használj** a middleware-ben. Ha a `next()` hívás eredményét nem adod vissza, a handler visszatérési értéke elvész és a kliens `undefined`-ot kap.
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
import { makeServerMiddleware } from '@atom-forge/rpc';
|
|
540
|
+
import type { RequestEvent } from '@sveltejs/kit';
|
|
541
|
+
|
|
542
|
+
const authMiddleware = makeServerMiddleware(
|
|
543
|
+
async (ctx, next) => {
|
|
544
|
+
const user = (ctx.adapterContext as RequestEvent).locals.user;
|
|
545
|
+
if (!user) {
|
|
546
|
+
ctx.status.unauthorized();
|
|
547
|
+
return { error: 'Nem engedélyezett' }; // ✅ korai visszatérés, next() hívás nem szükséges
|
|
548
|
+
}
|
|
549
|
+
return await next(); // ✅ mindig add vissza a next() eredményét
|
|
550
|
+
},
|
|
551
|
+
// Opcionális accessor-ok, amelyek a middleware függvényhez vannak csatolva
|
|
552
|
+
{
|
|
553
|
+
isAdmin: (ctx) => (ctx.adapterContext as RequestEvent).locals.user?.role === 'admin',
|
|
554
|
+
}
|
|
555
|
+
);
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Az accessor függvények közvetlenül a middleware függvény objektumhoz vannak csatolva, így a middleware és a kapcsolódó segédprogramok egy helyen maradnak. Az endpoint implementációkon belül `ctx` átadásával hívhatók meg:
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
const api = {
|
|
562
|
+
admin: {
|
|
563
|
+
deletePost: rpc.middleware(authMiddleware).command(async ({ id }, ctx) => {
|
|
564
|
+
if (!authMiddleware.isAdmin(ctx)) {
|
|
565
|
+
ctx.status.forbidden();
|
|
566
|
+
return { error: 'Csak adminoknak' };
|
|
567
|
+
}
|
|
568
|
+
// folytatás...
|
|
569
|
+
}),
|
|
570
|
+
},
|
|
571
|
+
};
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
Ez a minta egy helyen tartja a middleware tudását — hogy mi számít `isAdmin` ellenőrzésnek —, ahelyett, hogy minden endpointban megismételné a logikát.
|
|
575
|
+
|
|
576
|
+
### Middleware alkalmazása a `rpc.middleware` segítségével
|
|
577
|
+
|
|
578
|
+
Használd a `rpc.middleware()` metódust egy vagy több szerver middleware endpoint-hoz csatolásához:
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
import { rpc } from '@atom-forge/rpc';
|
|
582
|
+
import { z } from 'zod';
|
|
583
|
+
|
|
584
|
+
// Middleware alkalmazása egy konkrét endpointra
|
|
585
|
+
const api = {
|
|
586
|
+
posts: {
|
|
587
|
+
create: rpc.middleware(authMiddleware).command(async ({ title }) => {
|
|
588
|
+
// ...
|
|
589
|
+
}),
|
|
590
|
+
// Middleware kombinálása zod validációval
|
|
591
|
+
update: rpc.middleware(authMiddleware).zod({
|
|
592
|
+
id: z.number(),
|
|
593
|
+
title: z.string(),
|
|
594
|
+
}).command(async ({ id, title }) => {
|
|
595
|
+
// ...
|
|
596
|
+
}),
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Middleware-t bármilyen meglévő objektumhoz csatolhatsz az `.on()` segítségével:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
const postsApi = {
|
|
605
|
+
list: rpc.query(async () => { ... }),
|
|
606
|
+
create: rpc.command(async () => { ... }),
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// authMiddleware csatolása az egész postsApi csoporthoz
|
|
610
|
+
rpc.middleware(authMiddleware).on(postsApi);
|
|
611
|
+
|
|
612
|
+
const api = { posts: postsApi };
|
|
613
|
+
```
|