ts-procedures 5.4.0 → 5.4.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/package.json +1 -2
- package/src/errors.test.ts +0 -163
- package/src/errors.ts +0 -107
- package/src/exports.ts +0 -7
- package/src/implementations/http/README.md +0 -260
- package/src/implementations/http/express-rpc/README.md +0 -281
- package/src/implementations/http/express-rpc/index.test.ts +0 -957
- package/src/implementations/http/express-rpc/index.ts +0 -265
- package/src/implementations/http/express-rpc/types.ts +0 -16
- package/src/implementations/http/hono-api/index.test.ts +0 -1328
- package/src/implementations/http/hono-api/index.ts +0 -461
- package/src/implementations/http/hono-api/types.ts +0 -16
- package/src/implementations/http/hono-rpc/README.md +0 -358
- package/src/implementations/http/hono-rpc/index.test.ts +0 -1075
- package/src/implementations/http/hono-rpc/index.ts +0 -237
- package/src/implementations/http/hono-rpc/types.ts +0 -16
- package/src/implementations/http/hono-stream/README.md +0 -526
- package/src/implementations/http/hono-stream/index.test.ts +0 -1676
- package/src/implementations/http/hono-stream/index.ts +0 -435
- package/src/implementations/http/hono-stream/types.ts +0 -29
- package/src/implementations/types.ts +0 -127
- package/src/index.test.ts +0 -1194
- package/src/index.ts +0 -512
- package/src/schema/compute-schema.test.ts +0 -128
- package/src/schema/compute-schema.ts +0 -88
- package/src/schema/extract-json-schema.test.ts +0 -25
- package/src/schema/extract-json-schema.ts +0 -15
- package/src/schema/parser.test.ts +0 -182
- package/src/schema/parser.ts +0 -215
- package/src/schema/resolve-schema-lib.test.ts +0 -19
- package/src/schema/resolve-schema-lib.ts +0 -29
- package/src/schema/types.ts +0 -20
- package/src/stack-utils.test.ts +0 -94
- package/src/stack-utils.ts +0 -129
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# ExpressRPCAppBuilder
|
|
2
|
-
|
|
3
|
-
Express.js integration for `ts-procedures` that creates type-safe RPC endpoints as POST routes.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install ts-procedures express
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import express from 'express'
|
|
15
|
-
import { Procedures } from 'ts-procedures'
|
|
16
|
-
import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/express-rpc'
|
|
17
|
-
import { v } from 'suretype'
|
|
18
|
-
|
|
19
|
-
// Define your context type
|
|
20
|
-
type AppContext = { userId: string }
|
|
21
|
-
|
|
22
|
-
// Create a procedure factory
|
|
23
|
-
const RPC = Procedures<AppContext, RPCConfig>()
|
|
24
|
-
|
|
25
|
-
// Define procedures
|
|
26
|
-
RPC.Create(
|
|
27
|
-
'GetUser',
|
|
28
|
-
{
|
|
29
|
-
scope: ['users', 'profile'],
|
|
30
|
-
version: 1,
|
|
31
|
-
schema: {
|
|
32
|
-
params: v.object({ id: v.string() }),
|
|
33
|
-
returnType: v.object({ id: v.string(), name: v.string() })
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
async (ctx, params) => {
|
|
37
|
-
return { id: params.id, name: 'John Doe' }
|
|
38
|
-
}
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
// Build the Express app
|
|
42
|
-
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' })
|
|
43
|
-
.register(RPC, (req) => ({
|
|
44
|
-
userId: req.headers['x-user-id'] as string
|
|
45
|
-
}))
|
|
46
|
-
|
|
47
|
-
const app = builder.build()
|
|
48
|
-
app.listen(3000)
|
|
49
|
-
|
|
50
|
-
// POST /rpc/users/profile/get-user/1 → { id: "123", name: "John Doe" }
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Configuration
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
type ExpressRPCAppBuilderConfig = {
|
|
57
|
-
app?: express.Express // Existing Express app (optional)
|
|
58
|
-
pathPrefix?: string // Prefix for all routes (e.g., '/rpc/v1')
|
|
59
|
-
onRequestStart?: (req: express.Request) => void
|
|
60
|
-
onRequestEnd?: (req: express.Request, res: express.Response) => void
|
|
61
|
-
onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void
|
|
62
|
-
error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
| Option | Type | Description |
|
|
67
|
-
|--------|------|------------------------------------------------------|
|
|
68
|
-
| `app` | `express.Express` | Use existing Express app instead of creating new one |
|
|
69
|
-
| `pathPrefix` | `string` | Prefix all routes (e.g., `/rpc/v1`) |
|
|
70
|
-
| `onRequestStart` | `(req) => void` | Called at start of each request |
|
|
71
|
-
| `onRequestEnd` | `(req, res) => void` | Called after response finishes |
|
|
72
|
-
| `onSuccess` | `(proc, req, res) => void` | Called on successful handler execution |
|
|
73
|
-
| `error` | `(proc, req, res, err) => void` | Custom error handler |
|
|
74
|
-
|
|
75
|
-
## Context Resolution
|
|
76
|
-
|
|
77
|
-
The context resolver receives the Express `Request` object:
|
|
78
|
-
|
|
79
|
-
```typescript
|
|
80
|
-
builder.register(RPC, (req: express.Request) => ({
|
|
81
|
-
userId: req.headers['x-user-id'] as string,
|
|
82
|
-
sessionId: req.cookies?.sessionId,
|
|
83
|
-
ip: req.ip
|
|
84
|
-
}))
|
|
85
|
-
|
|
86
|
-
// Async context resolution
|
|
87
|
-
builder.register(RPC, async (req) => {
|
|
88
|
-
const token = req.headers.authorization?.replace('Bearer ', '')
|
|
89
|
-
const user = await verifyToken(token)
|
|
90
|
-
return { userId: user.id, roles: user.roles }
|
|
91
|
-
})
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Abort Signal
|
|
95
|
-
|
|
96
|
-
`ExpressRPCAppBuilder` provides a lazy `AbortSignal` on `ctx.signal`. The underlying `AbortController` and `req.on('close')` listener are only created when `ctx.signal` is first accessed, so handlers that don't use it pay no overhead.
|
|
97
|
-
|
|
98
|
-
The signal aborts when the client disconnects before the response finishes (premature close). Normal response completion does not trigger an abort.
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
RPC.Create(
|
|
102
|
-
'SlowQuery',
|
|
103
|
-
{ scope: 'data', version: 1 },
|
|
104
|
-
async (ctx, params) => {
|
|
105
|
-
// Automatically cancelled if client disconnects
|
|
106
|
-
const response = await fetch('https://slow-api.example.com/data', {
|
|
107
|
-
signal: ctx.signal,
|
|
108
|
-
})
|
|
109
|
-
return response.json()
|
|
110
|
-
}
|
|
111
|
-
)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
To use `ctx.signal` with type safety, include `signal: AbortSignal` in your context type:
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
type AppContext = { userId: string; signal: AbortSignal }
|
|
118
|
-
const RPC = Procedures<AppContext, RPCConfig>()
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Error Handling
|
|
122
|
-
|
|
123
|
-
Custom error handler receives the procedure, request, response, and error:
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
const builder = new ExpressRPCAppBuilder({
|
|
127
|
-
onError: (procedure, req, res, error) => {
|
|
128
|
-
console.error(`Error in ${procedure.name}:`, error)
|
|
129
|
-
|
|
130
|
-
if (error instanceof ValidationError) {
|
|
131
|
-
res.status(400).json({ error: error.message, code: 'VALIDATION_ERROR' })
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (error instanceof AuthError) {
|
|
136
|
-
res.status(401).json({ error: 'Unauthorized', code: 'AUTH_ERROR' })
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
res.status(500).json({ error: 'Internal server error' })
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
**Default error handling:** Returns `{ error: message }` with status 500.
|
|
146
|
-
|
|
147
|
-
## Using Existing Express App
|
|
148
|
-
|
|
149
|
-
When providing an existing Express app, **you must set up JSON parsing middleware**:
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
const app = express()
|
|
153
|
-
app.use(express.json()) // Required!
|
|
154
|
-
app.use(cors())
|
|
155
|
-
app.use(helmet())
|
|
156
|
-
|
|
157
|
-
const builder = new ExpressRPCAppBuilder({ app })
|
|
158
|
-
.register(RPC, contextResolver)
|
|
159
|
-
|
|
160
|
-
builder.build() // Adds RPC routes to existing app
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
When no `app` is provided, `express.json()` middleware is added automatically.
|
|
164
|
-
|
|
165
|
-
## API Reference
|
|
166
|
-
|
|
167
|
-
### Constructor
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
new ExpressRPCAppBuilder(config?: ExpressRPCAppBuilderConfig)
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Methods
|
|
174
|
-
|
|
175
|
-
| Method | Signature | Description |
|
|
176
|
-
|--------|-----------|-------------|
|
|
177
|
-
| `register` | `register<T>(factory, context): this` | Register procedure factory with context |
|
|
178
|
-
| `build` | `build(): express.Application` | Build routes and return app |
|
|
179
|
-
| `makeRPCHttpRoutePath` | `makeRPCHttpRoutePath(config: RPCConfig): string` | Generate route path |
|
|
180
|
-
|
|
181
|
-
### Static Methods
|
|
182
|
-
|
|
183
|
-
| Method | Signature | Description |
|
|
184
|
-
|--------|-----------|-------------|
|
|
185
|
-
| `makeRPCHttpRoutePath` | `static makeRPCHttpRoutePath({ config, prefix }): string` | Generate route path with custom prefix |
|
|
186
|
-
|
|
187
|
-
### Properties
|
|
188
|
-
|
|
189
|
-
| Property | Type | Description |
|
|
190
|
-
|----------|------|-------------|
|
|
191
|
-
| `app` | `express.Express` | The Express application instance |
|
|
192
|
-
| `docs` | `RPCHttpRouteDoc[]` | Route documentation (after `build()`) |
|
|
193
|
-
| `config` | `ExpressRPCAppBuilderConfig` | The configuration object |
|
|
194
|
-
|
|
195
|
-
## TypeScript Types
|
|
196
|
-
|
|
197
|
-
```typescript
|
|
198
|
-
import {
|
|
199
|
-
ExpressRPCAppBuilder,
|
|
200
|
-
ExpressRPCAppBuilderConfig,
|
|
201
|
-
RPCConfig,
|
|
202
|
-
RPCHttpRouteDoc
|
|
203
|
-
} from 'ts-procedures/express-rpc'
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
## Full Example
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
import express from 'express'
|
|
210
|
-
import { Procedures } from 'ts-procedures'
|
|
211
|
-
import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/express-rpc'
|
|
212
|
-
import { v } from 'suretype'
|
|
213
|
-
|
|
214
|
-
// Context types
|
|
215
|
-
type PublicContext = { source: 'public' }
|
|
216
|
-
type AuthContext = { source: 'auth'; userId: string }
|
|
217
|
-
|
|
218
|
-
// Create factories
|
|
219
|
-
const PublicRPC = Procedures<PublicContext, RPCConfig>()
|
|
220
|
-
const AuthRPC = Procedures<AuthContext, RPCConfig>()
|
|
221
|
-
|
|
222
|
-
// Public procedures
|
|
223
|
-
PublicRPC.Create('HealthCheck', { scope: 'health', version: 1 }, async () => ({
|
|
224
|
-
status: 'ok'
|
|
225
|
-
}))
|
|
226
|
-
|
|
227
|
-
PublicRPC.Create('GetVersion', { scope: ['system', 'version'], version: 1 }, async () => ({
|
|
228
|
-
version: '1.0.0'
|
|
229
|
-
}))
|
|
230
|
-
|
|
231
|
-
// Authenticated procedures
|
|
232
|
-
AuthRPC.Create(
|
|
233
|
-
'GetProfile',
|
|
234
|
-
{
|
|
235
|
-
scope: ['users', 'profile'],
|
|
236
|
-
version: 1,
|
|
237
|
-
schema: { returnType: v.object({ userId: v.string() }) }
|
|
238
|
-
},
|
|
239
|
-
async (ctx) => ({ userId: ctx.userId })
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
AuthRPC.Create(
|
|
243
|
-
'UpdateProfile',
|
|
244
|
-
{
|
|
245
|
-
scope: ['users', 'profile'],
|
|
246
|
-
version: 2,
|
|
247
|
-
schema: { params: v.object({ name: v.string() }) }
|
|
248
|
-
},
|
|
249
|
-
async (ctx, params) => ({ userId: ctx.userId, name: params.name })
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
// Build app
|
|
253
|
-
const builder = new ExpressRPCAppBuilder({
|
|
254
|
-
pathPrefix: '/rpc',
|
|
255
|
-
onRequestStart: (req) => console.log(`→ ${req.method} ${req.path}`),
|
|
256
|
-
onRequestEnd: (req, res) => console.log(`← ${res.statusCode}`),
|
|
257
|
-
onSuccess: (proc) => console.log(`✓ ${proc.name}`),
|
|
258
|
-
onError: (proc, req, res, err) => {
|
|
259
|
-
console.error(`✗ ${proc.name}:`, err.message)
|
|
260
|
-
res.status(500).json({ error: err.message })
|
|
261
|
-
}
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
builder
|
|
265
|
-
.register(PublicRPC, () => ({ source: 'public' as const }))
|
|
266
|
-
.register(AuthRPC, (req) => ({
|
|
267
|
-
source: 'auth' as const,
|
|
268
|
-
userId: req.headers['x-user-id'] as string || 'anonymous'
|
|
269
|
-
}))
|
|
270
|
-
|
|
271
|
-
const app = builder.build()
|
|
272
|
-
|
|
273
|
-
// Generated routes:
|
|
274
|
-
// POST /rpc/health/1
|
|
275
|
-
// POST /rpc/system/version/get-version/1
|
|
276
|
-
// POST /rpc/users/profile/get-user/1
|
|
277
|
-
// POST /rpc/users/profile/get-user/2
|
|
278
|
-
|
|
279
|
-
console.log('Routes:', builder.docs.map(d => d.path))
|
|
280
|
-
app.listen(3000)
|
|
281
|
-
```
|