@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.
Files changed (42) hide show
  1. package/LICENSE +28 -0
  2. package/README.en.md +612 -0
  3. package/README.hu.md +613 -0
  4. package/README.llm.md +343 -0
  5. package/README.md +25 -0
  6. package/dist/client/client-context.d.ts +45 -0
  7. package/dist/client/client-context.js +48 -0
  8. package/dist/client/create-client.d.ts +9 -0
  9. package/dist/client/create-client.js +277 -0
  10. package/dist/client/logger.d.ts +6 -0
  11. package/dist/client/logger.js +41 -0
  12. package/dist/client/middleware.d.ts +6 -0
  13. package/dist/client/middleware.js +7 -0
  14. package/dist/client/rpc-response.d.ts +27 -0
  15. package/dist/client/rpc-response.js +46 -0
  16. package/dist/client/types.d.ts +151 -0
  17. package/dist/client/types.js +1 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.js +7 -0
  20. package/dist/server/create-handler.d.ts +18 -0
  21. package/dist/server/create-handler.js +210 -0
  22. package/dist/server/errors.d.ts +10 -0
  23. package/dist/server/errors.js +14 -0
  24. package/dist/server/middleware.d.ts +22 -0
  25. package/dist/server/middleware.js +39 -0
  26. package/dist/server/rpc.d.ts +65 -0
  27. package/dist/server/rpc.js +49 -0
  28. package/dist/server/server-context.d.ts +79 -0
  29. package/dist/server/server-context.js +86 -0
  30. package/dist/server/types.d.ts +30 -0
  31. package/dist/server/types.js +1 -0
  32. package/dist/util/constants.d.ts +1 -0
  33. package/dist/util/constants.js +1 -0
  34. package/dist/util/cookies.d.ts +22 -0
  35. package/dist/util/cookies.js +54 -0
  36. package/dist/util/pipeline.d.ts +23 -0
  37. package/dist/util/pipeline.js +22 -0
  38. package/dist/util/string.d.ts +6 -0
  39. package/dist/util/string.js +11 -0
  40. package/dist/util/types.d.ts +5 -0
  41. package/dist/util/types.js +1 -0
  42. 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
+ ```