@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.
- package/LICENSE +20 -20
- package/README.md +512 -512
- package/dist/auth.d.mts +16 -16
- package/dist/auth.mjs +1 -84
- package/dist/client.d.mts +9 -9
- package/dist/client.mjs +1 -131
- package/dist/context-BAOh_2qI.mjs +1 -0
- package/dist/context.d.mts +1 -1
- package/dist/context.mjs +1 -10
- package/dist/cors.d.mts +3 -3
- package/dist/cors.mjs +1 -58
- package/dist/crypto-DrpstqPB.mjs +1 -0
- package/dist/crypto.mjs +1 -44
- package/dist/error-CvEWFOYT.mjs +1 -0
- package/dist/error.mjs +1 -3
- package/dist/{http-BqWCMASL.d.mts → http-CD8MvQOV.d.mts} +2 -2
- package/dist/http.d.mts +2 -2
- package/dist/http.mjs +1 -1
- package/dist/{middleware-COKreBGP.d.mts → middleware-DVREjjkO.d.mts} +4 -4
- package/dist/middleware.d.mts +3 -3
- package/dist/middleware.mjs +1 -1
- package/dist/procedure-BekIoOQX.mjs +1 -0
- package/dist/procedure.d.mts +3 -3
- package/dist/procedure.mjs +1 -3
- package/dist/response-D9PTLpdq.mjs +1 -0
- package/dist/response.mjs +1 -3
- package/dist/rpc-D9H6IkRD.mjs +1 -0
- package/dist/{rpc-Ch2UXReT.d.mts → rpc-DADpT17P.d.mts} +1 -1
- package/dist/rpc.d.mts +1 -1
- package/dist/rpc.mjs +1 -3
- package/dist/session.d.mts +4 -4
- package/dist/session.mjs +1 -181
- package/dist/test.d.mts +1 -0
- package/dist/test.mjs +1 -0
- package/dist/types.d.mts +1 -1
- package/dist/types.mjs +1 -1
- package/dist/ultra.d.mts +7 -7
- package/dist/ultra.mjs +1 -273
- package/dist/validation-s1s8P8HO.mjs +1 -0
- package/dist/validation.d.mts +1 -1
- package/dist/validation.mjs +1 -3
- package/package.json +10 -3
- package/dist/error-CII1zMOR.mjs +0 -45
- package/dist/procedure-BN1rLLRX.mjs +0 -86
- package/dist/response-CNhIkAYG.mjs +0 -59
- package/dist/rpc-_rBI0z-9.mjs +0 -7
- package/dist/validation-Cop5Wvlr.mjs +0 -12
- /package/dist/{context-ChCsZh7S.d.mts → context-DNamt_XE.d.mts} +0 -0
- /package/dist/{types-Cn69QrjS.d.mts → types-CzIiTHWF.d.mts} +0 -0
- /package/dist/{validation-CkRfxQJ_.d.mts → validation-Dfgqsq2f.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -1,512 +1,512 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- [
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
- [
|
|
13
|
-
- [
|
|
14
|
-
- [
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
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' },
|
|
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
|
+
[](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).
|