arri 0.44.0 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,590 +1,114 @@
1
- # Arri RPC
1
+ # Arri CLI
2
2
 
3
- Typescript implementation of Arri RPC. It's built on top of [H3](https://github.com/unjs/h3) and uses [esbuild](https://esbuild.github.io/) for bundling.
3
+ Command line interface for ARRI-RPC
4
4
 
5
- ## Table of Contents
5
+ ## Usage with @arrirpc/server
6
6
 
7
- - [Quickstart](#quickstart)
8
- - [Manual Setup](#manual-setup)
9
- - [Install Dependencies](#install-dependencies)
10
- - [Scaffold Your Project](#scaffold-your-project)
11
- - [Usage](#usage)
12
- - [Creating Procedures](#creating-procedures)
13
- - [File-Based Routing](#file-based-routing)
14
- - [Manual Routing](#manual-routing)
15
- - [Creating Event Stream Procedures](#creating-event-stream-procedures)
16
- - [Creating Websocket Procedures](#creating-websocket-procedures)
17
- - [Adding Non-RPC Routes](#adding-non-rpc-routes)
18
- - [Adding Middleware](#adding-middleware)
19
- - [Key Concepts](#key-concepts)
20
- - [Arri Definition File](#arri-definition-file)
21
- - [How Procedures Map To Endpoints](#how-procedures-map-to-endpoints)
22
- - [H3 Support](#h3-support)
23
- - [Arri CLI](#arri-cli)
7
+ For full details visit the [server docs](https://github.com/modiimedia/arri/blob/master/packages/arri/README.md)
24
8
 
25
- ## Quickstart
9
+ #### 1) Create an `arri.config.ts`
26
10
 
27
- ```bash
28
- # npm
29
- npx arri init [project-name]
30
- cd [project-name]
31
- npm install
32
- npm run dev
11
+ ```ts
12
+ import { defineConfig } from "arri";
33
13
 
34
- # pnpm
35
- pnpm dlx arri init [project-name]
36
- cd [project-name]
37
- pnpm install
38
- pnpm run dev
14
+ export default defineConfig({
15
+ srcDir: "src",
16
+ entry: "app.ts",
17
+ port: 3000,
18
+ generators: [
19
+ // client generators go here (can be imported from arri)
20
+ ],
21
+ });
39
22
  ```
40
23
 
41
- ## Manual Setup
42
-
43
- ### Install Dependencies
24
+ #### 2) Develop your server
44
25
 
45
26
  ```bash
46
- # npm
47
- npm install arri arri-validate
48
-
49
- # pnpm
50
- pnpm install arri arri-validate
27
+ arri dev
28
+ arri build
51
29
  ```
52
30
 
53
- ### Scaffold Your Project
31
+ ## Generate Clients Without an Arri Server
54
32
 
55
- A basic Arri app directory looks something like this:
33
+ #### 1) Create an App Definition
56
34
 
57
- ```fs
58
- |-- project-dir
59
- |-- .arri // temp files go here
60
- |-- .output // final bundle goes here
61
- |-- src
62
- |-- procedures // .rpc.ts files go here
63
- |-- app.ts
64
- |-- arri.config.ts
65
- |-- package.json
66
- |-- tsconfig.json
67
- |
68
- ```
69
-
70
- Both `.arri` and `.output` should be added to your `.gitignore` file
35
+ ```ts
36
+ // app-definition.ts
37
+ import { createAppDefinition } from "arri";
38
+ import { a } from "@arrirpc/schema";
71
39
 
72
- ```txt
73
- .arri
74
- .output
75
- node_modules
40
+ export default createAppDefinition({
41
+ procedures: {
42
+ sayHello: {
43
+ transport: "http",
44
+ method: "post",
45
+ path: "/say-hello",
46
+ params: a.object({
47
+ name: a.string(),
48
+ }),
49
+ response: a.object({
50
+ message: a.string(),
51
+ }),
52
+ },
53
+ },
54
+ });
76
55
  ```
77
56
 
78
- #### Configuration File
79
-
80
- Create an `arri.config.ts` in the project directory
57
+ #### 2) Create an `arri.config.ts`
81
58
 
82
59
  ```ts
83
60
  // arri.config.ts
84
- import { defineConfig } from "arri";
85
61
  import {
62
+ defineConfig,
86
63
  typescriptClientGenerator,
87
64
  dartClientGenerator,
88
- } from "arri/dist/codegen";
65
+ kotlinClientGenerator,
66
+ } from "arri";
89
67
 
90
68
  export default defineConfig({
91
- entry: "app.ts",
92
- port: 3000,
93
- srcDir: "src",
94
- clientGenerators: [
69
+ generators: [
95
70
  typescriptClientGenerator({
96
71
  // options
97
72
  }),
98
73
  dartClientGenerator({
99
74
  // options
100
75
  }),
76
+ kotlinClientGenerator({
77
+ // options
78
+ }),
101
79
  ],
102
80
  });
103
81
  ```
104
82
 
105
- ##### App Entry
106
-
107
- Create an app entry file in your src directory. The name of the file must match whatever you put as the `entry` in your `arri.config.ts`.
108
-
109
- ```ts
110
- // ./src/app.ts
111
- import { ArriApp } from "arri";
112
-
113
- const app = new ArriApp();
114
-
115
- export default app;
116
- ```
117
-
118
- ##### Package.json
119
-
120
- Setup your npm scripts:
121
-
122
- ```json
123
- {
124
- "name": "my-arri-app",
125
- "type": "module",
126
- "scripts": {
127
- "dev": "arri dev",
128
- "build": "arri build"
129
- },
130
- "dependencies": {
131
- ...
132
- },
133
- "devDependencies": {
134
- ...
135
- }
136
- }
137
- ```
138
-
139
- ## Usage
140
-
141
- ### Creating Procedures
142
-
143
- #### File Based Router
144
-
145
- Arri RPC comes with an optional file based router that will automatically register functions in the `./procedures` directory that end with the `.rpc.ts` file extension.
146
-
147
- ```fs
148
- |-- src
149
- |-- procedures
150
- |-- sayHello.rpc.ts // becomes sayHello()
151
- |-- users
152
- |-- getUser.rpc.ts // becomes users.getUser()
153
- |-- updateUser.rpc.ts // becomes users.updateUser()
154
- ```
155
-
156
- Example `.rpc.ts` file
157
-
158
- ```ts
159
- // ./src/users/getUser.rpc.ts
160
- import { defineRpc } from "arri";
161
- import { a } from "arri-validate";
162
-
163
- export default defineRpc({
164
- params: a.object({
165
- userId: a.string(),
166
- }),
167
- response: a.object({
168
- id: a.string(),
169
- name: a.string(),
170
- createdAt: a.timestamp(),
171
- }),
172
- handler({ params }) {
173
- // function body
174
- },
175
- });
176
- ```
177
-
178
- ##### Customizing the File Based Router
179
-
180
- ```ts
181
- export default defineConfig({
182
- // rest of config
183
- procedureDir: "procedures", // change which directory to look for procedures (This is relative to the srcDir)
184
- procedureGlobPatterns: ["**/*.rpc.ts"], // change the file name glob pattern for finding rpcs
185
- });
186
- ```
187
-
188
- #### Manual Routing
189
-
190
- For those that want to opt out of the file-based routing system you can manually register procedures like so.
191
-
192
- ```ts
193
- // using the app instance
194
- const app = new ArriApp()
195
- app.rpc('sayHello', {...})
196
-
197
- // using a sub-router
198
- const app = new ArriApp();
199
- const router = new ArriRoute();
200
- router.rpc('sayHello', {...})
201
- app.use(router)
202
- ```
203
-
204
- #### Creating Event Stream Procedures
205
-
206
- Event stream procedures make use of [Server Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) to stream events to clients.
207
-
208
- Arri Event streams sent the following event types:
209
-
210
- - `message` - A standard message with the response data serialized as JSON
211
- - `error` - An error message with an `ArriRequestError` sent as JSON
212
- - `done` - A message to tell clients that there will be no more events
213
- - `ping` - A message periodically sent by the server to keep the connection alive.
214
-
215
- ```ts
216
- /// message event ///
217
- id: string | undefined;
218
- event: "message";
219
- data: Response; // whatever you have specified as the response serialized to json
220
-
221
- /// error event ///
222
- id: string | undefined;
223
- event: "error";
224
- data: ArriRequestError; // serialized to json
225
-
226
- /// done event ///
227
- event: "done";
228
- data: "this stream has ended";
229
-
230
- /// ping event ///
231
- event: "ping";
232
- data: "";
233
- ```
234
-
235
- ##### Example Usage:
236
-
237
- ```ts
238
- // procedures/users/watchUser.rpc.ts
239
- export default defineEventStreamRpc({
240
- params: a.object({
241
- userId: a.string(),
242
- }),
243
- response: a.object({
244
- id: a.string(),
245
- name: a.string(),
246
- createdAt: a.timestamp(),
247
- updatedAt: a.timestamp(),
248
- }),
249
- handler({ params, stream }) {
250
- // initialize the stream and send it to the client
251
- stream.send();
252
-
253
- // send a message every second
254
- const interval = setInterval(async () => {
255
- await stream.push({
256
- id: "1",
257
- name: "John Doe",
258
- createdAt: new Date(),
259
- updatedAt: new Date(),
260
- });
261
- }, 1000);
262
-
263
- // cleanup when the client disconnects
264
- stream.on("close", () => {
265
- clearInterval(interval);
266
- });
267
- },
268
- });
269
- ```
270
-
271
- #### EventStreamConnection methods
272
-
273
- ```ts
274
- stream.push(data: Data, eventId?: string)
275
- stream.pushError(error: ArriRequestError, eventId?: string)
276
- stream.send()
277
- stream.end()
278
- stream.on(e: 'request:close' | 'close', callback: () => any)
279
- ```
280
-
281
- ### Creating Websocket Procedures
282
-
283
- ```ts
284
- // Websocket procedures work really well with discriminated unions
285
- const IncomingMsg = a.discriminator('type', {
286
- FOO: a.object({
287
- message: a.string(),
288
- }),
289
- PING: a.object({
290
- message: a.string(),
291
- })
292
- });
293
-
294
- const OutgoingMsg = a.discriminator('type', {
295
- BAR: a.object({
296
- message: a.string(),
297
- }),
298
- PONG: a.object({
299
- message: a.string()
300
- })
301
- });
302
-
303
- export default defineWebsocketRpc(
304
- params: IncomingMsg,
305
- response: OutgoingMsg,
306
- handler: {
307
- onOpen: (peer) => {},
308
- onMessage: (peer, message) => {
309
- switch(message.type) {
310
- case "FOO":
311
- peer.send({
312
- type: "BAR",
313
- message: "You sent a FOO message"
314
- });
315
- break;
316
- case "PING":
317
- peer.send({
318
- type: "PONG",
319
- message: "You sent a PING message"
320
- });
321
- break;
322
- }
323
- },
324
- onError: (peer, error) => {}
325
- }
326
- )
327
- ```
328
-
329
- Under the hood Websocket RPCs use [crossws](https://crossws.unjs.io/).
330
-
331
- The possible payloads sent by the server will look like the following:
332
-
333
- ```
334
- event: message
335
- data: <response serialized to json>
336
- ```
337
-
338
- ```
339
- event: error
340
- data: {"code": <some-err-code>, "message": <some-error-msg>}
341
- ```
342
-
343
- ### Adding Non-RPC Routes
344
-
345
- You can also add generic endpoints for instances when a message-based RPC endpoint doesn't fit.
83
+ #### 3) Run codegen command
346
84
 
347
- ```ts
348
- // using the app instance
349
- const app = new ArriApp();
350
- app.route({
351
- method: "get",
352
- path: "/hello-world",
353
- handler(event) {
354
- return "hello world";
355
- },
356
- });
357
-
358
- // using a sub-router
359
- const app = new ArriApp();
360
- const router = new ArriRouter();
361
- router.route({
362
- method: "get",
363
- path: "/hello-world",
364
- handler(event) {
365
- return "hello world",
366
- }
367
- })
368
- app.use(router)
85
+ ```bash
86
+ arri codegen ./app-definition.ts
369
87
  ```
370
88
 
371
- ### Adding Middleware
89
+ #### Now you can use the generated clients in your application code
372
90
 
373
91
  ```ts
374
- const app = new ArriApp();
375
-
376
- const requestLoggerMiddleware = defineMiddleware((event) => {
377
- console.log(`new request at ${event.path}`);
92
+ // typescript
93
+ await client.sayHello({
94
+ name: "John Doe",
378
95
  });
379
-
380
- app.use(requestLoggerMiddleware);
381
96
  ```
382
97
 
383
- #### Adding to the RPC Context
384
-
385
- Any values added to `event.context` will become available in the rpc instance
386
-
387
- ```ts
388
- const authMiddleware = defineMiddleware(async (event) => {
389
- // assume you did something to get the user from the request
390
- event.context.user = {
391
- id: 1,
98
+ ```dart
99
+ // dart
100
+ await client.sayHello(
101
+ SayHelloParams(
392
102
  name: "John Doe",
393
- email: "johndoe@gmail.com",
394
- };
395
- });
396
-
397
- app.rpc("sayHello", {
398
- params: undefined,
399
- response: a.object({
400
- message: a.string(),
401
- }),
402
- // user is available here
403
- handler({ user }) {
404
- return {
405
- message: `Hello ${user.name}`,
406
- };
407
- },
408
- });
409
- ```
410
-
411
- To get type safety for these new properties create a `.d.ts` file and augment the `EventContext` provided by `H3`
412
-
413
- ```ts
414
- import "h3";
415
-
416
- declare module "h3" {
417
- interface H3EventContext {
418
- user?: {
419
- id: number;
420
- name: string;
421
- email: string;
422
- };
423
- }
424
- }
103
+ ),
104
+ );
425
105
  ```
426
106
 
427
- ### Adding Client Generators
428
-
429
- Right now Arri RPC has client generators for the following languages:
430
-
431
- - typescript
432
- - dart
433
-
434
- ```ts
435
- // arri.config.ts
436
- import { defineConfig } from "arri";
437
- import { typescriptClientGenerator, dartClientGenerator } from "arri/dist/codegen";
438
-
439
- export default defineConfig({
440
- // rest of config
441
- clientGenerators: [
442
- typescriptClientGenerator({...}),
443
- dartClientGenerator({...})
444
- ]
445
- });
446
- ```
447
-
448
- ## Key Concepts
449
-
450
- ### Arri Definition File
451
-
452
- The server generates a `__definition.json` file that acts as a schema for all of the procedures and models in the API. By default this schema is viewable from `/__definition` when the server is running, but it can be modified. The endpoint is also relative to the `rpcRoutePrefix` option.
453
-
454
- It looks something like this:
455
-
456
- ```json
457
- {
458
- "procedures": {
459
- "sayHello": {
460
- "path": "/say-hello",
461
- "method": "post",
462
- "params": "SayHelloParams",
463
- "response": "SayHelloResponse"
464
- }
465
- // rest of procedures
466
- },
467
- "models": {
468
- "SayHelloParams": {
469
- "properties": {
470
- "name": {
471
- "type": "string"
472
- }
473
- }
474
- },
475
- "SayHelloResponse": {
476
- "properties": {
477
- "message": {
478
- "type": "string"
479
- }
480
- }
481
- }
482
- // rest of models
483
- }
484
- }
485
- ```
486
-
487
- Arri is able to use this schema file to automatically generate clients in multiple languages. In this way it works similarly to an Open API schema, but with much better code-generation support. I've made a lot of deliberate choices in designing this schema to make code-generation easier and more consistent across languages. For example, Arri schemas use a superset of [JSON Type Definition](https://jsontypedef.com/) for their models instead of JSON Schema.
488
-
489
- ### How Procedures Map To Endpoints
490
-
491
- Every procedure maps to a different url based on it's name. For example given the following file structure:
492
-
493
- ```fs
494
- |--src
495
- |--procedures
496
- |--getStatus.rpc.ts
497
- |--users
498
- |--getUser.rpc.ts
499
- |--updateUser.rpc.ts
500
- ```
501
-
502
- We will get the following endpoints:
503
-
504
- ```txt
505
- POST /get-status
506
- POST /users/get-user
507
- POST /users/update-user
508
-
509
- (Note: these will always be relative to the `rpcRoutePrefix` option)
510
- ```
511
-
512
- By default all procedures will become post requests, but you can change this when creating a procedure:
513
-
514
- ```ts
515
- // procedures/users/getUser.rpc.ts
516
- export default defineRpc({
517
- method: "get",
518
- // rest of config
519
- });
520
- ```
521
-
522
- The supported HTTP methods are as follows:
523
-
524
- - post
525
- - get
526
- - delete
527
- - patch
528
- - put
529
-
530
- When using a get method the RPC params will be mapped as query parameters which will be coerced into their type using the `a.coerce` method from `arri-validate`. Get methods support all basic scalar types however arrays and nested objects are not supported.
531
-
532
- ### H3 Support
533
-
534
- Arri is built on top of [H3](https://h3.unjs.io/utils/request#getrequestipevent) so many of the concepts that apply to H3 also apply to Arri.
535
-
536
- #### Accessing Utilities
537
-
538
- Arri re-eports all of the H3 utilities.
539
-
540
- ```ts
541
- import { getRequestIP, setResponseHeader } from "arri";
542
- ```
543
-
544
- #### Accessing H3 Events
545
-
546
- You can access H3 events from inside procedures handlers.
547
-
548
- ```ts
549
- defineRpc({
550
- params: undefined,
551
- response: undefined,
552
- handler(_, event) {
553
- getRequestIP(event);
554
- }
107
+ ```kt
108
+ // kotlin
109
+ client.sayHello(
110
+ SayHelloParams(
111
+ name = "John Doe"
112
+ )
555
113
  )
556
-
557
- defineEventStreamRpc({
558
- params: undefined,
559
- response: undefined,
560
- handler(_, event) {
561
- getRequestIP(event);
562
- }
563
- )
564
- ```
565
-
566
- ## Arri CLI
567
-
568
- ```bash
569
- # start the dev server
570
- arri dev [flags]
571
-
572
- # create a production build
573
- arri build [flags]
574
-
575
- # create a new project
576
- arri init [dir]
577
-
578
- # run codegen
579
- arri codegen [path-to-definition-file]
580
114
  ```
581
-
582
- ## Development
583
-
584
- ### Building
585
-
586
- Run `nx build arri-rpc` to build the library.
587
-
588
- ### Running unit tests
589
-
590
- Run `nx test arri-rpc` to execute the unit tests via Vitest.