ts-procedures 2.1.0 → 3.0.0

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 (70) hide show
  1. package/build/errors.d.ts +2 -1
  2. package/build/errors.js +3 -2
  3. package/build/errors.js.map +1 -1
  4. package/build/errors.test.js +40 -0
  5. package/build/errors.test.js.map +1 -0
  6. package/build/implementations/http/express-rpc/index.d.ts +36 -35
  7. package/build/implementations/http/express-rpc/index.js +29 -13
  8. package/build/implementations/http/express-rpc/index.js.map +1 -1
  9. package/build/implementations/http/express-rpc/index.test.js +146 -92
  10. package/build/implementations/http/express-rpc/index.test.js.map +1 -1
  11. package/build/implementations/http/hono-rpc/index.d.ts +83 -0
  12. package/build/implementations/http/hono-rpc/index.js +148 -0
  13. package/build/implementations/http/hono-rpc/index.js.map +1 -0
  14. package/build/implementations/http/hono-rpc/index.test.js +647 -0
  15. package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
  16. package/build/implementations/http/hono-rpc/types.d.ts +28 -0
  17. package/build/implementations/http/hono-rpc/types.js.map +1 -0
  18. package/build/implementations/types.d.ts +1 -1
  19. package/build/index.d.ts +12 -0
  20. package/build/index.js +29 -7
  21. package/build/index.js.map +1 -1
  22. package/build/index.test.js +65 -0
  23. package/build/index.test.js.map +1 -1
  24. package/build/schema/parser.js +3 -0
  25. package/build/schema/parser.js.map +1 -1
  26. package/build/schema/parser.test.js +18 -0
  27. package/build/schema/parser.test.js.map +1 -1
  28. package/package.json +8 -2
  29. package/src/errors.test.ts +53 -0
  30. package/src/errors.ts +4 -2
  31. package/src/implementations/http/README.md +172 -0
  32. package/src/implementations/http/express-rpc/README.md +151 -228
  33. package/src/implementations/http/express-rpc/index.test.ts +167 -93
  34. package/src/implementations/http/express-rpc/index.ts +67 -38
  35. package/src/implementations/http/hono-rpc/README.md +293 -0
  36. package/src/implementations/http/hono-rpc/index.test.ts +847 -0
  37. package/src/implementations/http/hono-rpc/index.ts +202 -0
  38. package/src/implementations/http/hono-rpc/types.ts +33 -0
  39. package/src/implementations/types.ts +2 -1
  40. package/src/index.test.ts +83 -0
  41. package/src/index.ts +34 -8
  42. package/src/schema/parser.test.ts +26 -0
  43. package/src/schema/parser.ts +5 -1
  44. package/build/implementations/http/client/index.js +0 -2
  45. package/build/implementations/http/client/index.js.map +0 -1
  46. package/build/implementations/http/express/example/factories.d.ts +0 -97
  47. package/build/implementations/http/express/example/factories.js +0 -4
  48. package/build/implementations/http/express/example/factories.js.map +0 -1
  49. package/build/implementations/http/express/example/procedures/auth.d.ts +0 -1
  50. package/build/implementations/http/express/example/procedures/auth.js +0 -22
  51. package/build/implementations/http/express/example/procedures/auth.js.map +0 -1
  52. package/build/implementations/http/express/example/procedures/users.d.ts +0 -1
  53. package/build/implementations/http/express/example/procedures/users.js +0 -30
  54. package/build/implementations/http/express/example/procedures/users.js.map +0 -1
  55. package/build/implementations/http/express/example/server.d.ts +0 -3
  56. package/build/implementations/http/express/example/server.js +0 -49
  57. package/build/implementations/http/express/example/server.js.map +0 -1
  58. package/build/implementations/http/express/example/server.test.d.ts +0 -1
  59. package/build/implementations/http/express/example/server.test.js +0 -110
  60. package/build/implementations/http/express/example/server.test.js.map +0 -1
  61. package/build/implementations/http/express/index.d.ts +0 -35
  62. package/build/implementations/http/express/index.js +0 -75
  63. package/build/implementations/http/express/index.js.map +0 -1
  64. package/build/implementations/http/express/index.test.js +0 -329
  65. package/build/implementations/http/express/index.test.js.map +0 -1
  66. package/build/implementations/http/express/types.d.ts +0 -17
  67. package/build/implementations/http/express/types.js.map +0 -1
  68. /package/build/{implementations/http/client/index.d.ts → errors.test.d.ts} +0 -0
  69. /package/build/implementations/http/{express → hono-rpc}/index.test.d.ts +0 -0
  70. /package/build/implementations/http/{express → hono-rpc}/types.js +0 -0
@@ -0,0 +1,172 @@
1
+ # HTTP-RPC Implementations
2
+
3
+ HTTP-RPC builders for `ts-procedures` that create type-safe, versioned RPC endpoints with automatic path generation, schema-based validation, and route documentation.
4
+
5
+ ## Available Implementations
6
+
7
+ | Framework | Package | Description |
8
+ |-----------|---------|-------------|
9
+ | [Express](./express-rpc/README.md) | `express-rpc` | Express.js integration |
10
+ | [Hono](./hono-rpc/README.md) | `hono-rpc` | Hono integration (Bun, Deno, Cloudflare Workers, Node.js) |
11
+
12
+ ## Core Concepts
13
+
14
+ ### RPCConfig Interface
15
+
16
+ All HTTP-RPC implementations use a shared configuration interface:
17
+
18
+ ```typescript
19
+ interface RPCConfig {
20
+ scope: string | string[] // Route path segment(s)
21
+ version: number // API version number
22
+ }
23
+ ```
24
+
25
+ ### Path Generation
26
+
27
+ Routes are generated using kebab-case conversion with the formula:
28
+
29
+ ```
30
+ /{pathPrefix}/{scope...}/{procedureName}/{version}
31
+ ```
32
+
33
+ **Conversion Examples:**
34
+
35
+ | Scope | Procedure Name | Version | Generated Path |
36
+ |-------|----------------|---------|----------------|
37
+ | `'users'` | `'Create'` | `1` | `/users/create/1` |
38
+ | `'users'` | `'GetById'` | `1` | `/users/get-by-id/1` |
39
+ | `['users', 'admin']` | `'List'` | `1` | `/users/admin/list/1` |
40
+ | `['UserModule', 'permissions']` | `'Update'` | `2` | `/user-module/permissions/update/2` |
41
+
42
+ **With pathPrefix `/api/v1`:**
43
+
44
+ | Scope | Procedure Name | Version | Generated Path |
45
+ |-------|----------------|---------|----------------|
46
+ | `'users'` | `'Create'` | `1` | `/api/v1/users/create/1` |
47
+ | `['users', 'admin']` | `'Delete'` | `2` | `/api/v1/users/admin/delete/2` |
48
+
49
+ ### Context Resolution Patterns
50
+
51
+ The `factoryContext` parameter supports three patterns:
52
+
53
+ ```typescript
54
+ // 1. Static object
55
+ builder.register(RPC, { userId: 'static-123' })
56
+
57
+ // 2. Sync function
58
+ builder.register(RPC, (req) => ({
59
+ userId: req.headers['x-user-id']
60
+ }))
61
+
62
+ // 3. Async function
63
+ builder.register(RPC, async (req) => {
64
+ const user = await validateToken(req.headers.authorization)
65
+ return { userId: user.id }
66
+ })
67
+ ```
68
+
69
+ ### Lifecycle Hooks
70
+
71
+ Hooks execute in the following order:
72
+
73
+ ```
74
+ onRequestStart → handler → onSuccess → onRequestEnd
75
+
76
+ (on error)
77
+
78
+ error handler
79
+
80
+ onRequestEnd
81
+ ```
82
+
83
+ | Hook | Trigger | Use Case |
84
+ |------|---------|----------|
85
+ | `onRequestStart` | Before route handler | Logging, request tracking |
86
+ | `onSuccess` | After successful handler execution | Metrics, audit logging |
87
+ | `onRequestEnd` | After response sent | Cleanup, timing metrics |
88
+ | `error` | On handler error | Custom error responses |
89
+
90
+ ### Route Documentation
91
+
92
+ Each registered procedure generates an `RPCHttpRouteDoc`:
93
+
94
+ ```typescript
95
+ interface RPCHttpRouteDoc {
96
+ path: string // Generated route path
97
+ method: 'post' // Always POST for RPC
98
+ jsonSchema: {
99
+ body?: object // JSON Schema from schema.params
100
+ response?: object // JSON Schema from schema.returnType
101
+ }
102
+ }
103
+ ```
104
+
105
+ Access documentation via `builder.docs` after calling `build()`:
106
+
107
+ ```typescript
108
+ const builder = new ExpressRPCAppBuilder()
109
+ builder.register(RPC, () => ({}))
110
+ builder.build()
111
+
112
+ // Generate OpenAPI documentation
113
+ const openApiPaths = builder.docs.reduce((acc, doc) => {
114
+ acc[doc.path] = {
115
+ post: {
116
+ requestBody: doc.jsonSchema.body ? {
117
+ content: { 'application/json': { schema: doc.jsonSchema.body } }
118
+ } : undefined,
119
+ responses: {
120
+ 200: doc.jsonSchema.response ? {
121
+ content: { 'application/json': { schema: doc.jsonSchema.response } }
122
+ } : undefined
123
+ }
124
+ }
125
+ }
126
+ return acc
127
+ }, {})
128
+ ```
129
+
130
+ ### Builder Pattern
131
+
132
+ All implementations follow the same builder pattern:
133
+
134
+ ```typescript
135
+ const builder = new RPCAppBuilder(config)
136
+ .register(PublicRPC, publicContextResolver)
137
+ .register(ProtectedRPC, protectedContextResolver)
138
+
139
+ const app = builder.build()
140
+ const docs = builder.docs
141
+ ```
142
+
143
+ **Key methods:**
144
+
145
+ | Method | Returns | Description |
146
+ |--------|---------|-------------|
147
+ | `register(factory, context)` | `this` | Register a procedure factory with context resolver |
148
+ | `build()` | Framework app | Create routes and return the application |
149
+ | `makeRPCHttpRoutePath(config)` | `string` | Generate path for an RPCConfig |
150
+
151
+ **Properties:**
152
+
153
+ | Property | Type | Description |
154
+ |----------|------|-------------|
155
+ | `app` | Framework app | The underlying framework application |
156
+ | `docs` | `RPCHttpRouteDoc[]` | Route documentation (populated after `build()`) |
157
+
158
+ ## Framework Comparison
159
+
160
+ | Aspect | Express | Hono |
161
+ |--------|---------|------|
162
+ | Context param | `req: express.Request` | `c: Context` |
163
+ | Error handler return | `void` (mutates res) | `Response` |
164
+ | Body access | `req.body` | `await c.req.json()` |
165
+ | Header access | `req.headers['x-id']` | `c.req.header('x-id')` |
166
+ | JSON middleware | Auto-added (or manual) | Built-in |
167
+
168
+ ## TypeScript Types
169
+
170
+ ```typescript
171
+ import { RPCConfig, RPCHttpRouteDoc } from 'ts-procedures/implementations/types'
172
+ ```
@@ -1,6 +1,6 @@
1
- # Express RPC Integration
1
+ # ExpressRPCAppBuilder
2
2
 
3
- RPC-style HTTP integration for `ts-procedures` using Express. Creates POST routes at `/rpc/{name}/{version}` paths with automatic JSON schema documentation.
3
+ Express.js integration for `ts-procedures` that creates type-safe RPC endpoints as POST routes.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,25 +8,17 @@ RPC-style HTTP integration for `ts-procedures` using Express. Creates POST route
8
8
  npm install ts-procedures express
9
9
  ```
10
10
 
11
- ## Import
12
-
13
- ```typescript
14
- import { ExpressRPCAppBuilder } from 'ts-procedures/express-rpc'
15
- ```
16
-
17
11
  ## Quick Start
18
12
 
19
13
  ```typescript
14
+ import express from 'express'
20
15
  import { Procedures } from 'ts-procedures'
21
- import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/express-rpc'
16
+ import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/implementations/http/express-rpc'
22
17
  import { v } from 'suretype'
23
18
 
24
19
  // Define your context type
25
20
  type AppContext = { userId: string }
26
21
 
27
- // RPC config type
28
- // type RPCConfig = { name: string | string[]; version: number }
29
-
30
22
  // Create a procedure factory
31
23
  const RPC = Procedures<AppContext, RPCConfig>()
32
24
 
@@ -34,12 +26,12 @@ const RPC = Procedures<AppContext, RPCConfig>()
34
26
  RPC.Create(
35
27
  'GetUser',
36
28
  {
37
- name: ['users', 'get'],
29
+ scope: ['users', 'profile'],
38
30
  version: 1,
39
31
  schema: {
40
32
  params: v.object({ id: v.string() }),
41
- returnType: v.object({ id: v.string(), name: v.string() }),
42
- },
33
+ returnType: v.object({ id: v.string(), name: v.string() })
34
+ }
43
35
  },
44
36
  async (ctx, params) => {
45
37
  return { id: params.id, name: 'John Doe' }
@@ -47,285 +39,216 @@ RPC.Create(
47
39
  )
48
40
 
49
41
  // Build the Express app
50
- const builder = new ExpressRPCAppBuilder()
51
- .register(RPC, (req) => ({ userId: req.headers['x-user-id'] as string }))
52
- .build()
42
+ const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' })
43
+ .register(RPC, (req) => ({
44
+ userId: req.headers['x-user-id'] as string
45
+ }))
53
46
 
54
- // Start the server
55
- builder.listen(3000, () => {
56
- console.log('RPC server running on http://localhost:3000')
57
- })
47
+ const app = builder.build()
48
+ app.listen(3000)
58
49
 
59
- // Route created: POST /rpc/users/get/1
50
+ // POST /rpc/users/profile/get-user/1 → { id: "123", name: "John Doe" }
60
51
  ```
61
52
 
62
- ## API Reference
63
-
64
- ### `ExpressRPCAppBuilder`
65
-
66
- Builder class for creating an Express application with RPC routes.
67
-
68
- #### Constructor
53
+ ## Configuration
69
54
 
70
55
  ```typescript
71
- new ExpressRPCAppBuilder(config?: {
72
- app?: express.Express
56
+ type ExpressRPCAppBuilderConfig = {
57
+ app?: express.Express // Existing Express app (optional)
58
+ pathPrefix?: string // Prefix for all routes (e.g., '/rpc/v1')
73
59
  onRequestStart?: (req: express.Request) => void
74
60
  onRequestEnd?: (req: express.Request, res: express.Response) => void
75
61
  onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void
76
62
  error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void
77
- })
63
+ }
78
64
  ```
79
65
 
80
- | Option | Type | Description |
81
- |--------|------|-------------|
82
- | `app` | `express.Express` | Existing Express app to use. When provided, you must configure middleware (e.g., `express.json()`) yourself. If omitted, a new app with JSON middleware is created. |
83
- | `onRequestStart` | `(req) => void` | Called at the start of each request. |
84
- | `onRequestEnd` | `(req, res) => void` | Called after the response finishes (via `res.on('finish')`). |
85
- | `onSuccess` | `(procedure, req, res) => void` | Called after successful procedure execution. |
86
- | `error` | `(procedure, req, res, error) => void` | Custom error handler. When provided, you control the response. |
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 |
87
74
 
88
- #### Methods
75
+ ## Context Resolution
89
76
 
90
- ##### `register<C>(factory, factoryContext): this`
91
-
92
- Registers a procedure factory with its context.
77
+ The context resolver receives the Express `Request` object:
93
78
 
94
79
  ```typescript
95
- // Direct value
96
- builder.register(RPC, { userId: 'system' })
97
-
98
- // Sync function
99
- builder.register(RPC, (req) => ({ userId: req.user.id }))
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
+ }))
100
85
 
101
- // Async function
86
+ // Async context resolution
102
87
  builder.register(RPC, async (req) => {
103
- const user = await getUser(req.headers.authorization)
104
- return { userId: user.id }
88
+ const token = req.headers.authorization?.replace('Bearer ', '')
89
+ const user = await verifyToken(token)
90
+ return { userId: user.id, roles: user.roles }
105
91
  })
106
92
  ```
107
93
 
108
- - **factory**: The procedure factory created by `Procedures<Context, RPCConfig>()`
109
- - **factoryContext**: The context for procedure handlers. Can be:
110
- - A direct value matching the factory's context type
111
- - A sync function `(req: express.Request) => Context`
112
- - An async function `(req: express.Request) => Promise<Context>`
113
- - **Returns**: `this` for method chaining
114
-
115
- ##### `build(): express.Application`
94
+ ## Error Handling
116
95
 
117
- Builds and returns the Express application with all registered RPC routes.
96
+ Custom error handler receives the procedure, request, response, and error:
118
97
 
119
98
  ```typescript
120
- const app = builder.build()
121
- app.listen(3000)
122
- ```
123
-
124
- #### Properties
125
-
126
- ##### `app: express.Express`
127
-
128
- The underlying Express application instance.
99
+ const builder = new ExpressRPCAppBuilder({
100
+ error: (procedure, req, res, error) => {
101
+ console.error(`Error in ${procedure.name}:`, error)
129
102
 
130
- ##### `docs: RPCHttpRouteDoc[]`
103
+ if (error instanceof ValidationError) {
104
+ res.status(400).json({ error: error.message, code: 'VALIDATION_ERROR' })
105
+ return
106
+ }
131
107
 
132
- Array of route documentation objects, populated after `build()` is called.
108
+ if (error instanceof AuthError) {
109
+ res.status(401).json({ error: 'Unauthorized', code: 'AUTH_ERROR' })
110
+ return
111
+ }
133
112
 
134
- ```typescript
135
- interface RPCHttpRouteDoc {
136
- path: string // e.g., '/rpc/users/get/1'
137
- method: 'post'
138
- jsonSchema: {
139
- body?: object // JSON Schema for request params
140
- response?: object // JSON Schema for return type
113
+ res.status(500).json({ error: 'Internal server error' })
141
114
  }
142
- }
115
+ })
143
116
  ```
144
117
 
145
- ### `makeRPCHttpRoutePath(config: RPCConfig): string`
146
-
147
- Generates the RPC route path from config. Exposed for testing/utilities.
148
-
149
- ### `buildRpcHttpRouteDoc(procedure): RPCHttpRouteDoc`
150
-
151
- Generates route documentation for a procedure. Exposed for testing/utilities.
152
-
153
- ## Route Path Generation
154
-
155
- Routes are generated at `/rpc/{name-segments}/{version}`:
118
+ **Default error handling:** Returns `{ error: message }` with status 500.
156
119
 
157
- | Config Name | Version | Generated Path |
158
- |-------------|---------|----------------|
159
- | `'users'` | `1` | `/rpc/users/1` |
160
- | `['users', 'get-by-id']` | `1` | `/rpc/users/get-by-id/1` |
161
- | `'getUserById'` | `2` | `/rpc/get-user-by-id/2` |
162
- | `'GetUserById'` | `1` | `/rpc/get-user-by-id/1` |
120
+ ## Using Existing Express App
163
121
 
164
- - Names are converted to kebab-case
165
- - Array names create nested path segments
166
- - Version is appended as the final segment (raw number, not `v1`)
167
-
168
- ## Multiple Factories
169
-
170
- Register multiple factories with different contexts:
122
+ When providing an existing Express app, **you must set up JSON parsing middleware**:
171
123
 
172
124
  ```typescript
173
- type PublicContext = { source: 'public' }
174
- type AuthContext = { source: 'auth'; userId: string }
175
-
176
- const PublicRPC = Procedures<PublicContext, RPCConfig>()
177
- const AuthRPC = Procedures<AuthContext, RPCConfig>()
178
-
179
- // Define public procedures
180
- PublicRPC.Create('HealthCheck', { name: 'health', version: 1 }, async () => ({ status: 'ok' }))
125
+ const app = express()
126
+ app.use(express.json()) // Required!
127
+ app.use(cors())
128
+ app.use(helmet())
181
129
 
182
- // Define authenticated procedures
183
- AuthRPC.Create('GetProfile', { name: ['users', 'profile'], version: 1 }, async (ctx) => ({
184
- userId: ctx.userId,
185
- }))
130
+ const builder = new ExpressRPCAppBuilder({ app })
131
+ .register(RPC, contextResolver)
186
132
 
187
- // Build with different factoryContext forms
188
- const app = new ExpressRPCAppBuilder()
189
- .register(PublicRPC, { source: 'public' }) // Direct value
190
- .register(AuthRPC, (req) => ({ // Sync function
191
- source: 'auth',
192
- userId: req.headers['x-user-id'] as string,
193
- }))
194
- .build()
133
+ builder.build() // Adds RPC routes to existing app
195
134
  ```
196
135
 
197
- ## Lifecycle Hooks
136
+ When no `app` is provided, `express.json()` middleware is added automatically.
198
137
 
199
- ```typescript
200
- const app = new ExpressRPCAppBuilder({
201
- onRequestStart: (req) => {
202
- console.log(`[${req.method}] ${req.path}`)
203
- },
138
+ ## API Reference
204
139
 
205
- onRequestEnd: (req, res) => {
206
- console.log(`Response: ${res.statusCode}`)
207
- },
140
+ ### Constructor
208
141
 
209
- onSuccess: (procedure, req, res) => {
210
- console.log(`Procedure ${procedure.name} succeeded`)
211
- },
212
- })
142
+ ```typescript
143
+ new ExpressRPCAppBuilder(config?: ExpressRPCAppBuilderConfig)
213
144
  ```
214
145
 
215
- **Execution order:** `onRequestStart` → handler → `onSuccess` → `onRequestEnd`
146
+ ### Methods
216
147
 
217
- Note: `onSuccess` is only called on successful execution. It is NOT called when the handler throws.
218
-
219
- ## Error Handling
220
-
221
- ### Custom Error Handler
222
-
223
- ```typescript
224
- const app = new ExpressRPCAppBuilder({
225
- error: (procedure, req, res, error) => {
226
- console.error(`Error in ${procedure.name}:`, error.message)
227
-
228
- if (error instanceof ProcedureValidationError) {
229
- res.status(400).json({ error: 'Validation failed', details: error.errors })
230
- } else if (error instanceof ProcedureError) {
231
- res.status(422).json({ error: error.message })
232
- } else {
233
- res.status(500).json({ error: 'Internal server error' })
234
- }
235
- },
236
- })
237
- ```
148
+ | Method | Signature | Description |
149
+ |--------|-----------|-------------|
150
+ | `register` | `register<T>(factory, context): this` | Register procedure factory with context |
151
+ | `build` | `build(): express.Application` | Build routes and return app |
152
+ | `makeRPCHttpRoutePath` | `makeRPCHttpRoutePath(config: RPCConfig): string` | Generate route path |
238
153
 
239
- ### Default Error Handling
154
+ ### Static Methods
240
155
 
241
- Without a custom error handler, errors return a JSON response with the error message:
156
+ | Method | Signature | Description |
157
+ |--------|-----------|-------------|
158
+ | `makeRPCHttpRoutePath` | `static makeRPCHttpRoutePath({ config, prefix }): string` | Generate route path with custom prefix |
242
159
 
243
- ```json
244
- { "error": "Error message here" }
245
- ```
160
+ ### Properties
246
161
 
247
- ## Route Documentation
162
+ | Property | Type | Description |
163
+ |----------|------|-------------|
164
+ | `app` | `express.Express` | The Express application instance |
165
+ | `docs` | `RPCHttpRouteDoc[]` | Route documentation (after `build()`) |
166
+ | `config` | `ExpressRPCAppBuilderConfig` | The configuration object |
248
167
 
249
- Access generated documentation after building:
168
+ ## TypeScript Types
250
169
 
251
170
  ```typescript
252
- const builder = new ExpressRPCAppBuilder()
253
- builder.register(RPC, factoryContext)
254
- builder.build()
255
-
256
- // Documentation is now available
257
- console.log(builder.docs)
258
- // [
259
- // {
260
- // path: '/rpc/users/get/1',
261
- // method: 'post',
262
- // jsonSchema: {
263
- // body: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
264
- // response: { type: 'object', properties: { id: { type: 'string' }, name: { type: 'string' } } }
265
- // }
266
- // }
267
- // ]
171
+ import {
172
+ ExpressRPCAppBuilder,
173
+ ExpressRPCAppBuilderConfig,
174
+ RPCConfig,
175
+ RPCHttpRouteDoc
176
+ } from 'ts-procedures/implementations/http/express-rpc'
268
177
  ```
269
178
 
270
- Use `docs` to generate OpenAPI specs, API documentation, or client SDKs.
271
-
272
- ## Using an Existing Express App
179
+ ## Full Example
273
180
 
274
181
  ```typescript
275
182
  import express from 'express'
183
+ import { Procedures } from 'ts-procedures'
184
+ import { ExpressRPCAppBuilder, RPCConfig } from 'ts-procedures/implementations/http/express-rpc'
185
+ import { v } from 'suretype'
276
186
 
277
- const app = express()
278
-
279
- // Add your own middleware
280
- app.use(express.json())
281
- app.use(cors())
282
- app.use(helmet())
283
-
284
- // Mount RPC routes
285
- const rpcApp = new ExpressRPCAppBuilder({ app })
286
- .register(RPC, factoryContext)
287
- .build()
288
-
289
- // Add other routes
290
- app.get('/health', (req, res) => res.json({ status: 'ok' }))
187
+ // Context types
188
+ type PublicContext = { source: 'public' }
189
+ type AuthContext = { source: 'auth'; userId: string }
291
190
 
292
- app.listen(3000)
293
- ```
191
+ // Create factories
192
+ const PublicRPC = Procedures<PublicContext, RPCConfig>()
193
+ const AuthRPC = Procedures<AuthContext, RPCConfig>()
294
194
 
295
- **Important:** When providing your own Express app, you must set up middleware like `express.json()` yourself.
195
+ // Public procedures
196
+ PublicRPC.Create('HealthCheck', { scope: 'health', version: 1 }, async () => ({
197
+ status: 'ok'
198
+ }))
296
199
 
297
- ## Types
200
+ PublicRPC.Create('GetVersion', { scope: ['system', 'version'], version: 1 }, async () => ({
201
+ version: '1.0.0'
202
+ }))
298
203
 
299
- ```typescript
300
- import type { RPCConfig, RPCHttpRouteDoc } from 'ts-procedures/express-rpc'
204
+ // Authenticated procedures
205
+ AuthRPC.Create(
206
+ 'GetProfile',
207
+ {
208
+ scope: ['users', 'profile'],
209
+ version: 1,
210
+ schema: { returnType: v.object({ userId: v.string() }) }
211
+ },
212
+ async (ctx) => ({ userId: ctx.userId })
213
+ )
301
214
 
302
- // RPCConfig - Required config shape for procedures
303
- interface RPCConfig {
304
- name: string | string[]
305
- version: number
306
- }
215
+ AuthRPC.Create(
216
+ 'UpdateProfile',
217
+ {
218
+ scope: ['users', 'profile'],
219
+ version: 2,
220
+ schema: { params: v.object({ name: v.string() }) }
221
+ },
222
+ async (ctx, params) => ({ userId: ctx.userId, name: params.name })
223
+ )
307
224
 
308
- // RPCHttpRouteDoc - Route documentation
309
- interface RPCHttpRouteDoc {
310
- path: string
311
- method: 'post'
312
- jsonSchema: {
313
- body?: object
314
- response?: object
225
+ // Build app
226
+ const builder = new ExpressRPCAppBuilder({
227
+ pathPrefix: '/rpc',
228
+ onRequestStart: (req) => console.log(`→ ${req.method} ${req.path}`),
229
+ onRequestEnd: (req, res) => console.log(`← ${res.statusCode}`),
230
+ onSuccess: (proc) => console.log(`✓ ${proc.name}`),
231
+ error: (proc, req, res, err) => {
232
+ console.error(`✗ ${proc.name}:`, err.message)
233
+ res.status(500).json({ error: err.message })
315
234
  }
316
- }
317
- ```
235
+ })
318
236
 
319
- ## HTTP Method
237
+ builder
238
+ .register(PublicRPC, () => ({ source: 'public' as const }))
239
+ .register(AuthRPC, (req) => ({
240
+ source: 'auth' as const,
241
+ userId: req.headers['x-user-id'] as string || 'anonymous'
242
+ }))
320
243
 
321
- All RPC routes use **POST** method only. GET requests to RPC paths return 404.
244
+ const app = builder.build()
322
245
 
323
- ```bash
324
- # Works
325
- curl -X POST http://localhost:3000/rpc/users/get/1 \
326
- -H "Content-Type: application/json" \
327
- -d '{"id": "123"}'
246
+ // Generated routes:
247
+ // POST /rpc/health/1
248
+ // POST /rpc/system/version/get-version/1
249
+ // POST /rpc/users/profile/get-user/1
250
+ // POST /rpc/users/profile/get-user/2
328
251
 
329
- # Returns 404
330
- curl http://localhost:3000/rpc/users/get/1
252
+ console.log('Routes:', builder.docs.map(d => d.path))
253
+ app.listen(3000)
331
254
  ```