@sx3/ultra 0.0.1 → 0.0.3

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 (50) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +512 -512
  3. package/dist/auth.d.mts +16 -16
  4. package/dist/auth.mjs +1 -84
  5. package/dist/client.d.mts +9 -9
  6. package/dist/client.mjs +1 -131
  7. package/dist/context-BAOh_2qI.mjs +1 -0
  8. package/dist/context.d.mts +1 -1
  9. package/dist/context.mjs +1 -10
  10. package/dist/cors.d.mts +3 -3
  11. package/dist/cors.mjs +1 -58
  12. package/dist/crypto-DrpstqPB.mjs +1 -0
  13. package/dist/crypto.mjs +1 -44
  14. package/dist/error-CvEWFOYT.mjs +1 -0
  15. package/dist/error.mjs +1 -3
  16. package/dist/{http-BqWCMASL.d.mts → http-CD8MvQOV.d.mts} +2 -2
  17. package/dist/http.d.mts +2 -2
  18. package/dist/http.mjs +1 -1
  19. package/dist/{middleware-COKreBGP.d.mts → middleware-DVREjjkO.d.mts} +4 -4
  20. package/dist/middleware.d.mts +3 -3
  21. package/dist/middleware.mjs +1 -1
  22. package/dist/procedure-BekIoOQX.mjs +1 -0
  23. package/dist/procedure.d.mts +3 -3
  24. package/dist/procedure.mjs +1 -3
  25. package/dist/response-D9PTLpdq.mjs +1 -0
  26. package/dist/response.mjs +1 -3
  27. package/dist/rpc-D9H6IkRD.mjs +1 -0
  28. package/dist/{rpc-Ch2UXReT.d.mts → rpc-DADpT17P.d.mts} +1 -1
  29. package/dist/rpc.d.mts +1 -1
  30. package/dist/rpc.mjs +1 -3
  31. package/dist/session.d.mts +4 -4
  32. package/dist/session.mjs +1 -181
  33. package/dist/test.d.mts +1 -0
  34. package/dist/test.mjs +1 -0
  35. package/dist/types.d.mts +1 -1
  36. package/dist/types.mjs +1 -1
  37. package/dist/ultra.d.mts +7 -7
  38. package/dist/ultra.mjs +1 -273
  39. package/dist/validation-s1s8P8HO.mjs +1 -0
  40. package/dist/validation.d.mts +1 -1
  41. package/dist/validation.mjs +1 -3
  42. package/package.json +10 -3
  43. package/dist/error-CII1zMOR.mjs +0 -45
  44. package/dist/procedure-BN1rLLRX.mjs +0 -86
  45. package/dist/response-CNhIkAYG.mjs +0 -59
  46. package/dist/rpc-_rBI0z-9.mjs +0 -7
  47. package/dist/validation-Cop5Wvlr.mjs +0 -12
  48. /package/dist/{context-ChCsZh7S.d.mts → context-DNamt_XE.d.mts} +0 -0
  49. /package/dist/{types-Cn69QrjS.d.mts → types-CzIiTHWF.d.mts} +0 -0
  50. /package/dist/{validation-CkRfxQJ_.d.mts → validation-Dfgqsq2f.d.mts} +0 -0
package/README.md CHANGED
@@ -1,512 +1,512 @@
1
- # Ultra
2
-
3
- Type-safe and fast RPC over HTTP/WebSocket for [Bun](https://bun.sh).
4
-
5
- ## Оглавление
6
-
7
- - [Install](#install)
8
- - [Quick start](#quick-start)
9
- - [Core concepts](#core-concepts)
10
- - [Modules](#modules)
11
- - [Type safety](#type-safety)
12
- - [Protocol independence](#protocol-independence)
13
- - [Middleware](#middleware)
14
- - [Validation](#validation)
15
- - [Context](#context)
16
- - [Built-in features](#built-in-features)
17
- - [CORS](#cors)
18
- - [Sessions](#sessions)
19
- - [Authentication](#authentication)
20
- - [Crypto](#crypto)
21
-
22
- ---
23
-
24
- ## Install
25
-
26
- ```bash
27
- bun add @sx3/ultra
28
- ```
29
-
30
- ## Quick start
31
-
32
- Write a simple server with two modules: users and books.
33
-
34
- ```ts
35
- // server.ts
36
- import { Ultra } from '@sx3/ultra';
37
-
38
- // User module
39
- const users = new Ultra().routes(input => ({
40
- users: {
41
- list: input().http().handler(() => [
42
- { id: 1, name: 'Alice' },
43
- { id: 2, name: 'Bob' }
44
- ])
45
- }
46
- }));
47
-
48
- // Book module
49
- const books = new Ultra().routes(input => ({
50
- books: {
51
- list: input().http().handler(() => [
52
- { id: 1, title: 'TypeScript' },
53
- { id: 2, title: 'Brave New World' }
54
- ]),
55
- }
56
- }));
57
-
58
- // Root module
59
- const server = new Ultra()
60
- .use(users)
61
- .use(books)
62
- .on('server:started', (bunServer) => {
63
- console.log(`Server started at ${bunServer.url}`);
64
- })
65
- .start();
66
-
67
- // Type for client usage
68
- export type Server = typeof server;
69
- ```
70
-
71
- Create a client to call the server methods.
72
-
73
- ```ts
74
- // clients.ts
75
- import type { Server } from './server';
76
- import { createHTTPClient } from '@sx3/ultra/client';
77
-
78
- const http = createHTTPClient<Server>({
79
- baseUrl: 'http://localhost:3000',
80
- });
81
-
82
- const users = await http.users.list(); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
83
- const books = await http.books.list(); // [{ id: 1, title: 'TypeScript' }, { id: 2, title: 'Brave New World' }]
84
- ```
85
-
86
- Create WebSocket client to call server methods over WebSocket.
87
-
88
- ```ts
89
- // clients.ts
90
- import type { Server } from './server';
91
- import { createWebSocketClient } from '@sx3/ultra/client';
92
-
93
- let socket = new WebSocket('ws://localhost:3000/ws');
94
- const ws = createWebSocketClient<Server>({
95
- // Socket getter function
96
- socket: () => socket,
97
- });
98
-
99
- const users = await ws.users.list(); // [{ id: 1, name: 'Alice' } ...]
100
- ```
101
-
102
- Or create [super client](#protocol-independence) for dynamic transport switching.
103
-
104
- ## Middleware
105
-
106
- Middleware just functions that run before your route handlers. You can use them to add authentication, logging, error handling, etc.
107
-
108
- ```ts
109
- import { Ultra } from '@sx3/ultra';
110
- import { UnauthorizedError } from '@sx3/ultra/error';
111
-
112
- // Simple authentication middleware
113
- async function isAuthenticated({ context, next }) {
114
- if (!await context.auth.check()) return new UnauthorizedError();
115
- return next();
116
- }
117
-
118
- const app = new Ultra()
119
- .use(isAuthenticated) // Apply middleware globally
120
- .routes(input => ({
121
- profile: {
122
- get: input()
123
- .use(isAuthenticated) // Apply middleware to specific route
124
- .http()
125
- .handler(({ context }) => {
126
- return context.auth.user; // Access authenticated user
127
- }),
128
- },
129
- }));
130
- ```
131
-
132
- ## Validation
133
-
134
- Ultra supports any library compatible with [Standard Schema](https://standardschema.dev/schema#what-schema-libraries-implement-the-spec).
135
-
136
- ```ts
137
- import { Ultra } from '@sx3/ultra';
138
- import { createHTTPClient } from '@sx3/ultra/client';
139
- // import * as z from 'zod';
140
- import * as s from 'sury';
141
-
142
- const LoginSchema = s.schema({
143
- name: s.string(),
144
- password: s.string(),
145
- });
146
-
147
- const UserSchema = s.schema({
148
- id: s.number(),
149
- name: s.string(),
150
- });
151
-
152
- const auth = new Ultra().routes(input => ({
153
- auth: {
154
- // Schema for runtime input validation
155
- login: input(LoginSchema)
156
- // Schema for runtime output validation
157
- .output(UserSchema)
158
- .http()
159
- .handler(({ input }) => {
160
- // input is typed and validated as { name: string; password: string }
161
- const user = { id: 1, name: input.name };
162
-
163
- return user;
164
- }),
165
- }
166
- }));
167
-
168
- const client = createHTTPClient<typeof auth>({
169
- baseUrl: 'http://localhost:3000',
170
- });
171
-
172
- const user = await client.auth.login({ name: 'Alice', password: 'secret' }); // user is typed as { id: number; name: string }
173
- ```
174
-
175
- Difference between runtime validation and TypeScript types:
176
-
177
- ```ts
178
- import * as z from 'zod';
179
-
180
- const api = new Ultra().routes(input => ({
181
- // Ultra checks input and output data
182
- validated: input(z.object({ a: z.number(), b: z.number() }))
183
- .output(z.number())
184
- .http()
185
- .handler(({ input }) => {
186
- // input is typed and validated as { a: number; b: number }
187
- return input.a + input.b;
188
- }),
189
-
190
- // You are confident in your types and don't want to waste CPU/memory on validation.
191
- typed: input<{ a: number; b: number }>()
192
- .output<number>()
193
- .http()
194
- .handler(({ input }) => {
195
- // input is typed as { a: number; b: number } but NOT validated
196
- return input.a + input.b;
197
- }),
198
- }));
199
- ```
200
-
201
- ## Context
202
-
203
- Ultra provides context system that allows you to share data across your application.
204
- You can extend the context with a function or value. Example from session module:
205
-
206
- ```ts
207
- // session.ts
208
-
209
- export function createSessionModule<S extends Record<string, SessionStoreFactory>>(config: SessionConfig<S>) {
210
- // Create module
211
- return new Ultra()
212
- // deriveWS to add sessionId to WebSocket data object | run each socket connection | use for store data in WS connection
213
- .deriveWS((context: HTTPContext) => ({ sessionId: Session.getOrCreateId(context.request, config) }))
214
- // derive function add session instance to context | run each request
215
- .derive(context => ({ session: new Session(config, context) }))
216
- // Middleware to initiate and commit session on each request
217
- .use(async ({ context, next }) => {
218
- await context.session.initiate();
219
- const response = await next();
220
- await context.session.commit();
221
- return response;
222
- });
223
- }
224
- ```
225
-
226
- You can add a static value for each request:
227
-
228
- ```ts
229
- const app = new Ultra().derive({ appName: 'My Ultra App' });
230
- ```
231
-
232
- ## Core concepts
233
-
234
- ### Modules
235
-
236
- Each module is a self-contained application.
237
-
238
- ```ts
239
- // auth.ts
240
-
241
- // This is a self-contained application. It declares all its dependencies.
242
- const auth = new Ultra()
243
- .use(cors) // Use CORS middleware
244
- .use(session) // Use session module
245
- .routes(input => ({
246
- auth: {
247
- login: input(s.schema({ login: s.string(), password: s.string() }))
248
- .http()
249
- .handler(({ input }) => {
250
- // Handle login
251
- }),
252
- }
253
- }));
254
-
255
- // You can run it independently
256
- auth.start();
257
-
258
- // Or use it as a module in another application
259
- const main = new Ultra()
260
- .use(auth)
261
- .start();
262
- ```
263
-
264
- You can use modules as many times as you like.
265
-
266
- ```ts
267
- const moduleA = new Ultra();
268
-
269
- const moduleB = new Ultra()
270
- .use(moduleA); // Use first time
271
-
272
- const moduleC = new Ultra()
273
- .use(moduleB)
274
- .use(moduleA); // Use second time
275
-
276
- const mainApp = new Ultra()
277
- .use(moduleA)
278
- .use(moduleB)
279
- .use(moduleC)
280
- .start();
281
- ```
282
-
283
- It may seem like modules will be duplicated and cause conflicts, but Ultra, under the hood, deduplicates everything that is connected to it.
284
-
285
- This applies not only to modules:
286
-
287
- ```ts
288
- // Derive function
289
- function requestIdDerive(context) {
290
- console.log('Deriving!');
291
- return { requestId: crypto.randomUUID() };
292
- }
293
-
294
- // Middleware
295
- async function logger({ next }) {
296
- console.log('Request!');
297
- return next();
298
- }
299
-
300
- // Routes
301
- function routes(input) {
302
- return {
303
- ping: input().http().handler(() => {
304
- console.log('Handling ping!');
305
- return 'pong';
306
- }),
307
- };
308
- }
309
-
310
- const a = new Ultra()
311
- .derive(requestIdDerive)
312
- .use(logger)
313
- .routes(routes);
314
-
315
- const b = new Ultra()
316
- .derive(requestIdDerive)
317
- .use(logger)
318
- .routes(routes);
319
-
320
- const app = new Ultra()
321
- .derive(requestIdDerive)
322
- .use(logger)
323
- .use(a)
324
- .use(b)
325
- .routes(routes)
326
- .start();
327
-
328
- fetch('http://localhost:3000/ping'); // Printed: Deriving!, Request!, Handling ping!
329
- ```
330
-
331
- ### Type safety
332
-
333
- Ultra provides end-to-end type safety for your server and clients.
334
-
335
- ```ts
336
- import { Ultra } from '@sx3/ultra';
337
- import { createSuperClient } from '@sx3/ultra/client';
338
-
339
- const math = new Ultra().routes(input => ({
340
- math: {
341
- add: input<{ a: number; b: number }>()
342
- .http()
343
- .handler(({ input }) => {
344
- // input is typed as { a: number; b: number }
345
- return input.a + input.b;
346
- }),
347
- }
348
- }));
349
-
350
- const client = createSuperClient<typeof math>({/** ... */});
351
-
352
- const result = await client.math.add({ a: 1, b: 2 }); // the result is automatically inferred as a number
353
- ```
354
-
355
- ### Protocol independence
356
-
357
- Ultra strives to be independent of specific protocols. You simply call functions and the application decides how to send the data.
358
-
359
- ```ts
360
- // clients.ts
361
- import type { Server } from './server';
362
- import { createHTTPClient, createSuperClient, createWebSocketClient } from '@sx3/ultra/client';
363
-
364
- const http = createHTTPClient<Server>({
365
- baseUrl: 'http://localhost:3000',
366
- });
367
-
368
- let socket = new WebSocket('ws://localhost:3000/ws');
369
- const ws = createWebSocketClient<Server>({
370
- socket: () => socket,
371
- });
372
-
373
- const api = createSuperClient<Server>({
374
- // Transport picker function | if WebSocket is open, use it; otherwise, use HTTP
375
- pick: (method: string, params: unknown, options?: any) => {
376
- if (socket.readyState === WebSocket.OPEN) return ws;
377
- return http;
378
- }
379
- });
380
-
381
- const users = await api.users.list(); // [{ id: 1, name: 'Alice' } ...]
382
- ```
383
-
384
- Currently only HTTP and WebSockets are supported.
385
-
386
- ## Built-in features
387
-
388
- Ultra has several built-in features to make your life easier.
389
-
390
- ### CORS
391
-
392
- ```ts
393
- import { Ultra } from '@sx3/ultra';
394
- import { createCORSMiddleware } from '@sx3/ultra/cors';
395
-
396
- const cors = createCORSMiddleware({
397
- origin: ['http://localhost:5173'],
398
- credentials: true,
399
- // methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
400
- // allowedHeaders: ['Content-Type', 'Authorization'],
401
- // exposedHeaders: ['X-Custom-Header'],
402
- // maxAge: 3600,
403
- });
404
-
405
- const app = new Ultra().use(cors); // Apply CORS middleware globally
406
- ```
407
-
408
- ### Sessions
409
-
410
- Multiple session stores are supported: in-memory, Redis, and custom stores.
411
-
412
- ```ts
413
- // session.ts
414
- import { env } from '#app/env';
415
- import { createSessionModule, defineConfig, MemorySessionStore, RedisSessionStore } from '@/sx3/ultra/session';
416
-
417
- export const config = defineConfig({
418
- // Name for cookie or prefix for redis key
419
- name: 'session',
420
- ttl: 3600, // 1 hour
421
- store: 'redis',
422
- secret: env.APP_KEY,
423
- cookie: {
424
- path: '/',
425
- httpOnly: true,
426
- secure: env.NODE_ENV === 'production',
427
- sameSite: 'lax',
428
- },
429
- stores: {
430
- redis: config => new RedisSessionStore(config, redis),
431
- memory: config => new MemorySessionStore(config),
432
- },
433
- });
434
-
435
- export const session = createSessionModule(config);
436
-
437
- // server.ts
438
- import { Ultra } from '@sx3/ultra';
439
- import { session } from './session';
440
-
441
- const app = new Ultra().use(session).routes(input => ({
442
- profile: {
443
- get: input().http().handler(({ context: { session } }) => {
444
- // Access session data
445
- session.get('user');
446
- session.set('user', { id: 1, name: 'Alice' });
447
- }),
448
- },
449
- })).start();
450
- ```
451
-
452
- ### Authentication
453
-
454
- Auth module requires [session module](#sessions).
455
-
456
- ```ts
457
- // auth.ts
458
- import { Ultra } from '@sx3/ultra';
459
- import { createAuthModule, defineConfig, SessionAuthProvider } from '@sx3/ultra/auth';
460
-
461
- interface User {
462
- name: string;
463
- age: number;
464
- }
465
-
466
- const config = defineConfig<User>({
467
- provider: 'session',
468
- providers: {
469
- session: context => new SessionAuthProvider<User>(context),
470
- },
471
- });
472
-
473
- export const auth = createAuthModule<User>(config);
474
-
475
- // server.ts
476
- import { auth } from './auth';
477
- import { session } from './session';
478
- import { isAuthenticated, isGuest } from '@sx3/ultra/auth';
479
-
480
- const app = new Ultra()
481
- .use(session)
482
- .use(auth)
483
- .routes(input => ({
484
- // Just example
485
- auth: {
486
- login: input(LoginSchema)
487
- .output(UserSchema)
488
- .http()
489
- .use(isGuest)
490
- .handler(async ({ input, context }) => {
491
- // ... check credentials logic
492
- // then
493
- await context.auth.login(user);
494
- return user;
495
- }),
496
-
497
- logout: input()
498
- .http()
499
- .use(isAuthenticated)
500
- .handler(({ context }) => context.auth.logout()),
501
-
502
- profile: input().use(isAuthenticated).http().handler(({ context }) => context.auth.user!),
503
- }
504
- }))
505
- .start();
506
- ```
507
-
508
- ### Crypto
509
-
510
- Crypto functions are available [here](https://github.com/SX-3/ultra/blob/main/src/crypto.ts).
511
-
512
- Inspired by [Elysia](https://elysiajs.com/) and [oRPC](https://orpc.dev/) powered by [Bun](https://bun.sh).
1
+ [![npm](https://img.shields.io/npm/v/@sx3/ultra)](https://www.npmjs.com/package/@sx3/ultra)
2
+
3
+ # Ultra
4
+
5
+ Type-safe and fast RPC over HTTP/WebSocket for [Bun](https://bun.sh).
6
+
7
+ ## Table of contents
8
+
9
+ - [Install](#install)
10
+ - [Quick start](#quick-start)
11
+ - [Core concepts](#core-concepts)
12
+ - [Modules](#modules)
13
+ - [Type safety](#type-safety)
14
+ - [Protocol independence](#protocol-independence)
15
+ - [Middleware](#middleware)
16
+ - [Validation](#validation)
17
+ - [Context](#context)
18
+ - [Built-in features](#built-in-features)
19
+ - [CORS](#cors)
20
+ - [Sessions](#sessions)
21
+ - [Authentication](#authentication)
22
+ - [Crypto](#crypto)
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ bun add @sx3/ultra
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ Write a simple server with two modules: users and books.
33
+
34
+ ```ts
35
+ // server.ts
36
+ import { Ultra } from '@sx3/ultra';
37
+
38
+ // User module
39
+ const users = new Ultra().routes(input => ({
40
+ users: {
41
+ list: input().http().handler(() => [
42
+ { id: 1, name: 'Alice' },
43
+ { id: 2, name: 'Bob' }
44
+ ])
45
+ }
46
+ }));
47
+
48
+ // Book module
49
+ const books = new Ultra().routes(input => ({
50
+ books: {
51
+ list: input().http().handler(() => [
52
+ { id: 1, title: 'TypeScript' },
53
+ { id: 2, title: 'Brave New World' }
54
+ ]),
55
+ }
56
+ }));
57
+
58
+ // Root module
59
+ const server = new Ultra()
60
+ .use(users)
61
+ .use(books)
62
+ .on('server:started', (bunServer) => {
63
+ console.log(`Server started at ${bunServer.url}`);
64
+ })
65
+ .start();
66
+
67
+ // Type for client usage
68
+ export type Server = typeof server;
69
+ ```
70
+
71
+ Create a client to call the server methods.
72
+
73
+ ```ts
74
+ // clients.ts
75
+ import type { Server } from './server';
76
+ import { createHTTPClient } from '@sx3/ultra/client';
77
+
78
+ const http = createHTTPClient<Server>({
79
+ baseUrl: 'http://localhost:3000',
80
+ });
81
+
82
+ const users = await http.users.list(); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
83
+ const books = await http.books.list(); // [{ id: 1, title: 'TypeScript' }, ...]
84
+ ```
85
+
86
+ Create WebSocket client to call server methods over WebSocket.
87
+
88
+ ```ts
89
+ // clients.ts
90
+ import type { Server } from './server';
91
+ import { createWebSocketClient } from '@sx3/ultra/client';
92
+
93
+ let socket = new WebSocket('ws://localhost:3000/ws');
94
+ const ws = createWebSocketClient<Server>({
95
+ // Socket getter function
96
+ socket: () => socket,
97
+ });
98
+
99
+ const users = await ws.users.list(); // [{ id: 1, name: 'Alice' } ...]
100
+ ```
101
+
102
+ Or create [super client](#protocol-independence) for dynamic transport switching.
103
+
104
+ ## Middleware
105
+
106
+ Middleware just functions that run before your route handlers. You can use them to add authentication, logging, error handling, etc.
107
+
108
+ ```ts
109
+ import { Ultra } from '@sx3/ultra';
110
+ import { UnauthorizedError } from '@sx3/ultra/error';
111
+
112
+ // Simple authentication middleware
113
+ async function isAuthenticated({ context, next }) {
114
+ if (!await context.auth.check()) return new UnauthorizedError();
115
+ return next();
116
+ }
117
+
118
+ const app = new Ultra()
119
+ .use(isAuthenticated) // Apply middleware globally
120
+ .routes(input => ({
121
+ profile: {
122
+ get: input()
123
+ .use(isAuthenticated) // Apply middleware to specific route
124
+ .http()
125
+ .handler(({ context }) => {
126
+ return context.auth.user; // Access authenticated user
127
+ }),
128
+ },
129
+ }));
130
+ ```
131
+
132
+ ## Validation
133
+
134
+ Ultra supports any library compatible with [Standard Schema](https://standardschema.dev/schema#what-schema-libraries-implement-the-spec).
135
+
136
+ ```ts
137
+ import { Ultra } from '@sx3/ultra';
138
+ import { createHTTPClient } from '@sx3/ultra/client';
139
+ // import * as z from 'zod';
140
+ import * as s from 'sury';
141
+
142
+ const LoginSchema = s.schema({
143
+ name: s.string(),
144
+ password: s.string(),
145
+ });
146
+
147
+ const UserSchema = s.schema({
148
+ id: s.number(),
149
+ name: s.string(),
150
+ });
151
+
152
+ const auth = new Ultra().routes(input => ({
153
+ auth: {
154
+ // Schema for runtime input validation
155
+ login: input(LoginSchema)
156
+ // Schema for runtime output validation
157
+ .output(UserSchema)
158
+ .http()
159
+ .handler(({ input }) => {
160
+ // input is typed and validated as { name: string; password: string }
161
+ const user = { id: 1, name: input.name };
162
+
163
+ return user;
164
+ }),
165
+ }
166
+ }));
167
+
168
+ const client = createHTTPClient<typeof auth>({
169
+ baseUrl: 'http://localhost:3000',
170
+ });
171
+
172
+ const user = await client.auth.login({ name: 'Alice', password: 'secret' }); // user is typed as { id: number; name: string }
173
+ ```
174
+
175
+ Difference between runtime validation and TypeScript types:
176
+
177
+ ```ts
178
+ import * as z from 'zod';
179
+
180
+ const api = new Ultra().routes(input => ({
181
+ // Ultra checks input and output data
182
+ validated: input(z.object({ a: z.number(), b: z.number() }))
183
+ .output(z.number())
184
+ .http()
185
+ .handler(({ input }) => {
186
+ // input is typed and validated as { a: number; b: number }
187
+ return input.a + input.b;
188
+ }),
189
+
190
+ // You are confident in your types and don't want to waste CPU/memory on validation.
191
+ typed: input<{ a: number; b: number }>()
192
+ .output<number>()
193
+ .http()
194
+ .handler(({ input }) => {
195
+ // input is typed as { a: number; b: number } but NOT validated
196
+ return input.a + input.b;
197
+ }),
198
+ }));
199
+ ```
200
+
201
+ ## Context
202
+
203
+ Ultra provides context system that allows you to share data across your application.
204
+ You can extend the context with a function or value. Example from session module:
205
+
206
+ ```ts
207
+ // session.ts
208
+
209
+ export function createSessionModule<S extends Record<string, SessionStoreFactory>>(config: SessionConfig<S>) {
210
+ // Create module
211
+ return new Ultra()
212
+ // deriveWS to add sessionId to WebSocket data object | run each socket connection | use for store data in WS connection
213
+ .deriveWS((context: HTTPContext) => ({ sessionId: Session.getOrCreateId(context.request, config) }))
214
+ // derive function add session instance to context | run each request
215
+ .derive(context => ({ session: new Session(config, context) }))
216
+ // Middleware to initiate and commit session on each request
217
+ .use(async ({ context, next }) => {
218
+ await context.session.initiate();
219
+ const response = await next();
220
+ await context.session.commit();
221
+ return response;
222
+ });
223
+ }
224
+ ```
225
+
226
+ You can add a static value for each request:
227
+
228
+ ```ts
229
+ const app = new Ultra().derive({ appName: 'My Ultra App' });
230
+ ```
231
+
232
+ ## Core concepts
233
+
234
+ ### Modules
235
+
236
+ Each module is a self-contained application.
237
+
238
+ ```ts
239
+ // auth.ts
240
+
241
+ // This is a self-contained application. It declares all its dependencies.
242
+ const auth = new Ultra()
243
+ .use(cors) // Use CORS middleware
244
+ .use(session) // Use session module
245
+ .routes(input => ({
246
+ auth: {
247
+ login: input(s.schema({ login: s.string(), password: s.string() }))
248
+ .http()
249
+ .handler(({ input }) => {
250
+ // Handle login
251
+ }),
252
+ }
253
+ }));
254
+
255
+ // You can run it independently
256
+ auth.start();
257
+
258
+ // Or use it as a module in another application
259
+ const main = new Ultra()
260
+ .use(auth)
261
+ .start();
262
+ ```
263
+
264
+ You can use modules as many times as you like.
265
+
266
+ ```ts
267
+ const moduleA = new Ultra();
268
+
269
+ const moduleB = new Ultra()
270
+ .use(moduleA); // Use first time
271
+
272
+ const moduleC = new Ultra()
273
+ .use(moduleB)
274
+ .use(moduleA); // Use second time
275
+
276
+ const mainApp = new Ultra()
277
+ .use(moduleA)
278
+ .use(moduleB)
279
+ .use(moduleC)
280
+ .start();
281
+ ```
282
+
283
+ It may seem like modules will be duplicated and cause conflicts, but Ultra, under the hood, deduplicates everything that is connected to it.
284
+
285
+ This applies not only to modules:
286
+
287
+ ```ts
288
+ // Derive function
289
+ function requestIdDerive(context) {
290
+ console.log('Deriving!');
291
+ return { requestId: crypto.randomUUID() };
292
+ }
293
+
294
+ // Middleware
295
+ async function logger({ next }) {
296
+ console.log('Request!');
297
+ return next();
298
+ }
299
+
300
+ // Routes
301
+ function routes(input) {
302
+ return {
303
+ ping: input().http().handler(() => {
304
+ console.log('Handling ping!');
305
+ return 'pong';
306
+ }),
307
+ };
308
+ }
309
+
310
+ const a = new Ultra()
311
+ .derive(requestIdDerive)
312
+ .use(logger)
313
+ .routes(routes);
314
+
315
+ const b = new Ultra()
316
+ .derive(requestIdDerive)
317
+ .use(logger)
318
+ .routes(routes);
319
+
320
+ const app = new Ultra()
321
+ .derive(requestIdDerive)
322
+ .use(logger)
323
+ .use(a)
324
+ .use(b)
325
+ .routes(routes)
326
+ .start();
327
+
328
+ fetch('http://localhost:3000/ping'); // Printed: Deriving!, Request!, Handling ping!
329
+ ```
330
+
331
+ ### Type safety
332
+
333
+ Ultra provides end-to-end type safety for your server and clients.
334
+
335
+ ```ts
336
+ import { Ultra } from '@sx3/ultra';
337
+ import { createSuperClient } from '@sx3/ultra/client';
338
+
339
+ const math = new Ultra().routes(input => ({
340
+ math: {
341
+ add: input<{ a: number; b: number }>()
342
+ .http()
343
+ .handler(({ input }) => {
344
+ // input is typed as { a: number; b: number }
345
+ return input.a + input.b;
346
+ }),
347
+ }
348
+ }));
349
+
350
+ const client = createSuperClient<typeof math>({/** ... */});
351
+
352
+ const result = await client.math.add({ a: 1, b: 2 }); // the result is automatically inferred as a number
353
+ ```
354
+
355
+ ### Protocol independence
356
+
357
+ Ultra strives to be independent of specific protocols. You simply call functions and the application decides how to send the data.
358
+
359
+ ```ts
360
+ // clients.ts
361
+ import type { Server } from './server';
362
+ import { createHTTPClient, createSuperClient, createWebSocketClient } from '@sx3/ultra/client';
363
+
364
+ const http = createHTTPClient<Server>({
365
+ baseUrl: 'http://localhost:3000',
366
+ });
367
+
368
+ let socket = new WebSocket('ws://localhost:3000/ws');
369
+ const ws = createWebSocketClient<Server>({
370
+ socket: () => socket,
371
+ });
372
+
373
+ const api = createSuperClient<Server>({
374
+ // Transport picker function | if WebSocket is open, use it; otherwise, use HTTP
375
+ pick: (method: string, params: unknown, options?: any) => {
376
+ if (socket.readyState === WebSocket.OPEN) return ws;
377
+ return http;
378
+ }
379
+ });
380
+
381
+ const users = await api.users.list(); // [{ id: 1, name: 'Alice' } ...]
382
+ ```
383
+
384
+ Currently only HTTP and WebSockets are supported.
385
+
386
+ ## Built-in features
387
+
388
+ Ultra has several built-in features to make your life easier.
389
+
390
+ ### CORS
391
+
392
+ ```ts
393
+ import { Ultra } from '@sx3/ultra';
394
+ import { createCORSMiddleware } from '@sx3/ultra/cors';
395
+
396
+ const cors = createCORSMiddleware({
397
+ origin: ['http://localhost:5173'],
398
+ credentials: true,
399
+ // methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
400
+ // allowedHeaders: ['Content-Type', 'Authorization'],
401
+ // exposedHeaders: ['X-Custom-Header'],
402
+ // maxAge: 3600,
403
+ });
404
+
405
+ const app = new Ultra().use(cors); // Apply CORS middleware globally
406
+ ```
407
+
408
+ ### Sessions
409
+
410
+ Multiple session stores are supported: in-memory, Redis, and custom stores.
411
+
412
+ ```ts
413
+ // session.ts
414
+ import { env } from '#app/env';
415
+ import { createSessionModule, defineConfig, MemorySessionStore, RedisSessionStore } from '@/sx3/ultra/session';
416
+
417
+ export const config = defineConfig({
418
+ // Name for cookie or prefix for redis key
419
+ name: 'session',
420
+ ttl: 3600, // 1 hour
421
+ store: 'redis',
422
+ secret: env.APP_KEY,
423
+ cookie: {
424
+ path: '/',
425
+ httpOnly: true,
426
+ secure: env.NODE_ENV === 'production',
427
+ sameSite: 'lax',
428
+ },
429
+ stores: {
430
+ redis: config => new RedisSessionStore(config, redis),
431
+ memory: config => new MemorySessionStore(config),
432
+ },
433
+ });
434
+
435
+ export const session = createSessionModule(config);
436
+
437
+ // server.ts
438
+ import { Ultra } from '@sx3/ultra';
439
+ import { session } from './session';
440
+
441
+ const app = new Ultra().use(session).routes(input => ({
442
+ profile: {
443
+ get: input().http().handler(({ context: { session } }) => {
444
+ // Access session data
445
+ session.get('user');
446
+ session.set('user', { id: 1, name: 'Alice' });
447
+ }),
448
+ },
449
+ })).start();
450
+ ```
451
+
452
+ ### Authentication
453
+
454
+ Auth module requires [session module](#sessions).
455
+
456
+ ```ts
457
+ // auth.ts
458
+ import { Ultra } from '@sx3/ultra';
459
+ import { createAuthModule, defineConfig, SessionAuthProvider } from '@sx3/ultra/auth';
460
+
461
+ interface User {
462
+ name: string;
463
+ age: number;
464
+ }
465
+
466
+ const config = defineConfig<User>({
467
+ provider: 'session',
468
+ providers: {
469
+ session: context => new SessionAuthProvider<User>(context),
470
+ },
471
+ });
472
+
473
+ export const auth = createAuthModule<User>(config);
474
+
475
+ // server.ts
476
+ import { auth } from './auth';
477
+ import { session } from './session';
478
+ import { isAuthenticated, isGuest } from '@sx3/ultra/auth';
479
+
480
+ const app = new Ultra()
481
+ .use(session)
482
+ .use(auth)
483
+ .routes(input => ({
484
+ // Just example
485
+ auth: {
486
+ login: input(LoginSchema)
487
+ .output(UserSchema)
488
+ .http()
489
+ .use(isGuest)
490
+ .handler(async ({ input, context }) => {
491
+ // ... check credentials logic
492
+ // then
493
+ await context.auth.login(user);
494
+ return user;
495
+ }),
496
+
497
+ logout: input()
498
+ .http()
499
+ .use(isAuthenticated)
500
+ .handler(({ context }) => context.auth.logout()),
501
+
502
+ profile: input().use(isAuthenticated).http().handler(({ context }) => context.auth.user!),
503
+ }
504
+ }))
505
+ .start();
506
+ ```
507
+
508
+ ### Crypto
509
+
510
+ Crypto functions are available [here](https://github.com/SX-3/ultra/blob/main/src/crypto.ts).
511
+
512
+ Inspired by [Elysia](https://elysiajs.com/) and [oRPC](https://orpc.dev/) powered by [Bun](https://bun.sh).