ts-procedures 5.4.0 → 5.5.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 (50) hide show
  1. package/README.md +21 -0
  2. package/agent_config/claude-code/skills/guide/SKILL.md +1 -0
  3. package/agent_config/claude-code/skills/guide/anti-patterns.md +37 -4
  4. package/agent_config/claude-code/skills/guide/api-reference.md +86 -0
  5. package/agent_config/claude-code/skills/guide/patterns.md +33 -0
  6. package/agent_config/claude-code/skills/review/checklist.md +2 -0
  7. package/agent_config/claude-code/skills/scaffold/templates/hono-api.md +1 -1
  8. package/agent_config/copilot/copilot-instructions.md +4 -0
  9. package/agent_config/cursor/cursorrules +4 -0
  10. package/build/implementations/http/doc-registry.d.ts +12 -0
  11. package/build/implementations/http/doc-registry.js +114 -0
  12. package/build/implementations/http/doc-registry.js.map +1 -0
  13. package/build/implementations/http/doc-registry.test.d.ts +1 -0
  14. package/build/implementations/http/doc-registry.test.js +321 -0
  15. package/build/implementations/http/doc-registry.test.js.map +1 -0
  16. package/build/implementations/types.d.ts +31 -0
  17. package/package.json +5 -2
  18. package/src/errors.test.ts +0 -163
  19. package/src/errors.ts +0 -107
  20. package/src/exports.ts +0 -7
  21. package/src/implementations/http/README.md +0 -260
  22. package/src/implementations/http/express-rpc/README.md +0 -281
  23. package/src/implementations/http/express-rpc/index.test.ts +0 -957
  24. package/src/implementations/http/express-rpc/index.ts +0 -265
  25. package/src/implementations/http/express-rpc/types.ts +0 -16
  26. package/src/implementations/http/hono-api/index.test.ts +0 -1328
  27. package/src/implementations/http/hono-api/index.ts +0 -461
  28. package/src/implementations/http/hono-api/types.ts +0 -16
  29. package/src/implementations/http/hono-rpc/README.md +0 -358
  30. package/src/implementations/http/hono-rpc/index.test.ts +0 -1075
  31. package/src/implementations/http/hono-rpc/index.ts +0 -237
  32. package/src/implementations/http/hono-rpc/types.ts +0 -16
  33. package/src/implementations/http/hono-stream/README.md +0 -526
  34. package/src/implementations/http/hono-stream/index.test.ts +0 -1676
  35. package/src/implementations/http/hono-stream/index.ts +0 -435
  36. package/src/implementations/http/hono-stream/types.ts +0 -29
  37. package/src/implementations/types.ts +0 -127
  38. package/src/index.test.ts +0 -1194
  39. package/src/index.ts +0 -512
  40. package/src/schema/compute-schema.test.ts +0 -128
  41. package/src/schema/compute-schema.ts +0 -88
  42. package/src/schema/extract-json-schema.test.ts +0 -25
  43. package/src/schema/extract-json-schema.ts +0 -15
  44. package/src/schema/parser.test.ts +0 -182
  45. package/src/schema/parser.ts +0 -215
  46. package/src/schema/resolve-schema-lib.test.ts +0 -19
  47. package/src/schema/resolve-schema-lib.ts +0 -29
  48. package/src/schema/types.ts +0 -20
  49. package/src/stack-utils.test.ts +0 -94
  50. package/src/stack-utils.ts +0 -129
package/src/errors.ts DELETED
@@ -1,107 +0,0 @@
1
- import { TSchemaValidationError } from './schema/parser.js'
2
- import { DefinitionInfo, DefinitionLocation, formatDefinitionInfo } from './stack-utils.js'
3
- import { kebabCase } from 'es-toolkit/string'
4
-
5
- export class ProcedureError extends Error {
6
- cause?: unknown
7
- readonly definedAt?: DefinitionLocation
8
- readonly definitionStack?: string
9
-
10
- constructor(
11
- readonly procedureName: string,
12
- readonly message: string,
13
- readonly meta?: object,
14
- // Used for error stack trace details
15
- definitionInfo?: DefinitionInfo
16
- ) {
17
- super(message)
18
- this.name = 'ProcedureError'
19
-
20
- if (definitionInfo) {
21
- this.definedAt = definitionInfo.definedAt
22
- this.definitionStack = definitionInfo.definitionStack
23
- this.enhanceStack()
24
- }
25
-
26
- // https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
27
- Object.setPrototypeOf(this, ProcedureError.prototype)
28
- }
29
-
30
- /**
31
- * Returns a formatted string showing where the procedure was defined.
32
- */
33
- getDefinitionLocation(): string | undefined {
34
- if (!this.definedAt) {
35
- return undefined
36
- }
37
- return `${this.definedAt.file}:${this.definedAt.line}:${this.definedAt.column}`
38
- }
39
-
40
- /**
41
- * Enhances the error stack with definition location information.
42
- */
43
- private enhanceStack(): void {
44
- if (!this.stack || !this.definedAt) {
45
- return
46
- }
47
-
48
- const definitionSection = formatDefinitionInfo(
49
- { definedAt: this.definedAt, definitionStack: this.definitionStack },
50
- this.procedureName
51
- )
52
-
53
- if (definitionSection) {
54
- this.stack = this.stack + definitionSection
55
- }
56
- }
57
- }
58
-
59
- export class ProcedureValidationError extends ProcedureError {
60
- constructor(
61
- readonly procedureName: string,
62
- message: string,
63
- readonly errors?: TSchemaValidationError[],
64
- // Used for error stack trace details
65
- definitionInfo?: DefinitionInfo
66
- ) {
67
- const readableErrors = errors
68
- ?.map((err) => `- ${kebabCase(err.instancePath).replace('-', '.')} ${err.message}`)
69
- .join(', ')
70
- super(procedureName, message + ' ' + readableErrors, { errors }, definitionInfo)
71
- this.name = 'ProcedureValidationError'
72
-
73
- // https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
74
- Object.setPrototypeOf(this, ProcedureValidationError.prototype)
75
- }
76
- }
77
-
78
- export class ProcedureRegistrationError extends ProcedureError {
79
- constructor(
80
- readonly procedureName: string,
81
- message: string,
82
- // Used for error stack trace details
83
- definitionInfo?: DefinitionInfo
84
- ) {
85
- super(procedureName, message, undefined, definitionInfo)
86
- this.name = 'ProcedureRegistrationError'
87
-
88
- // https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
89
- Object.setPrototypeOf(this, ProcedureRegistrationError.prototype)
90
- }
91
- }
92
-
93
- export class ProcedureYieldValidationError extends ProcedureError {
94
- constructor(
95
- readonly procedureName: string,
96
- message: string,
97
- readonly errors?: TSchemaValidationError[],
98
- // Used for error stack trace details
99
- definitionInfo?: DefinitionInfo
100
- ) {
101
- super(procedureName, message, undefined, definitionInfo)
102
- this.name = 'ProcedureYieldValidationError'
103
-
104
- // https://www.dannyguo.com/blog/how-to-fix-instanceof-not-working-for-custom-errors-in-typescript/
105
- Object.setPrototypeOf(this, ProcedureYieldValidationError.prototype)
106
- }
107
- }
package/src/exports.ts DELETED
@@ -1,7 +0,0 @@
1
- export * from './index.js'
2
- export * from './errors.js'
3
- export * from './stack-utils.js'
4
- export * from './schema/extract-json-schema.js'
5
- export * from './schema/parser.js'
6
- export * from './schema/resolve-schema-lib.js'
7
- export * from './schema/types.js'
@@ -1,260 +0,0 @@
1
- # HTTP Implementations
2
-
3
- HTTP implementation builders for `ts-procedures` that create type-safe, versioned endpoints with automatic path generation, schema-based validation, and route documentation.
4
-
5
- ## Available Implementations
6
-
7
- ### RPC (Request/Response)
8
-
9
- For procedures created with `Create()` - standard request/response pattern using POST.
10
-
11
- | Framework | Package | Description |
12
- |-----------|---------|-------------|
13
- | [Express RPC](./express-rpc/README.md) | `express-rpc` | Express.js integration |
14
- | [Hono RPC](./hono-rpc/README.md) | `hono-rpc` | Hono integration (Bun, Deno, Cloudflare Workers, Node.js) |
15
-
16
- ### Streaming
17
-
18
- For procedures created with `CreateStream()` - server-sent events and streaming responses.
19
-
20
- | Framework | Package | Description |
21
- |-----------|---------|-------------|
22
- | [Hono Stream](./hono-stream/README.md) | `hono-stream` | SSE and text streaming for async generators |
23
-
24
- ### API (REST-style)
25
-
26
- For procedures using `schema.input` — per-channel input validation with standard HTTP methods.
27
-
28
- | Framework | Package | Description |
29
- |-----------|---------|-------------|
30
- | [Hono API](./hono-api/) | `hono-api` | REST-style routing by HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD) |
31
-
32
- ## Procedure Types
33
-
34
- | Type | Created With | Handler Return | HTTP Methods | Use Case |
35
- |------|--------------|----------------|--------------|----------|
36
- | RPC | `Create()` | `Promise<T>` | POST | Standard request/response |
37
- | Stream | `CreateStream()` | `AsyncGenerator<T>` | GET, POST | Real-time updates, SSE |
38
- | API | `Create()` | `Promise<T>` | GET, POST, PUT, DELETE, PATCH, HEAD | REST-style endpoints with per-channel input |
39
-
40
- ## Core Concepts
41
-
42
- ### Config Interface
43
-
44
- All HTTP implementations use a shared configuration interface:
45
-
46
- ```typescript
47
- interface RPCConfig {
48
- scope: string | string[] // Route path segment(s)
49
- version: number // API version number
50
- }
51
- ```
52
-
53
- #### APIConfig (REST-style)
54
-
55
- ```typescript
56
- interface APIConfig {
57
- path: string // Route path with Hono params (e.g., '/users/:id')
58
- method: HttpMethod // 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'
59
- successStatus?: number // Default: POST→201, DELETE→204, others→200
60
- }
61
- ```
62
-
63
- API routes use developer-defined paths — no auto-generation from scope/version.
64
-
65
- ### Path Generation
66
-
67
- Routes are generated using kebab-case conversion:
68
-
69
- ```
70
- /{pathPrefix}/{scope...}/{procedureName}/{version}
71
- ```
72
-
73
- **Examples:**
74
-
75
- | Scope | Procedure Name | Version | Generated Path |
76
- |-------|----------------|---------|----------------|
77
- | `'users'` | `'Create'` | `1` | `/users/create/1` |
78
- | `'users'` | `'GetById'` | `1` | `/users/get-by-id/1` |
79
- | `['users', 'admin']` | `'List'` | `1` | `/users/admin/list/1` |
80
- | `['UserModule', 'permissions']` | `'Update'` | `2` | `/user-module/permissions/update/2` |
81
-
82
- **With pathPrefix `/api/v1`:**
83
-
84
- | Scope | Procedure Name | Version | Generated Path |
85
- |-------|----------------|---------|----------------|
86
- | `'users'` | `'Create'` | `1` | `/api/v1/users/create/1` |
87
- | `['users', 'admin']` | `'Delete'` | `2` | `/api/v1/users/admin/delete/2` |
88
-
89
- ### Context Resolution
90
-
91
- The `factoryContext` parameter supports three patterns:
92
-
93
- ```typescript
94
- // 1. Static object
95
- builder.register(Factory, { userId: 'static-123' })
96
-
97
- // 2. Sync function
98
- builder.register(Factory, (c) => ({
99
- userId: c.req.header('x-user-id')
100
- }))
101
-
102
- // 3. Async function
103
- builder.register(Factory, async (c) => {
104
- const user = await validateToken(c.req.header('authorization'))
105
- return { userId: user.id }
106
- })
107
- ```
108
-
109
- ### Abort Signal
110
-
111
- All HTTP implementations automatically inject an `AbortSignal` into the handler context as `ctx.signal`. This signal aborts when the client disconnects, enabling handlers to cancel in-flight work (fetch calls, database queries, etc.).
112
-
113
- | Framework | Signal Source | Behavior |
114
- |-----------|-------------|----------|
115
- | Hono RPC | `c.req.raw.signal` | Web standard Request signal |
116
- | Hono Stream | `c.req.raw.signal` | Combined with internal stream AbortController via `AbortSignal.any()` |
117
- | Express RPC | Lazy `AbortController` | Created on first `ctx.signal` access, wired to `req.on('close')` |
118
-
119
- For streaming procedures, `signal.reason` is `'stream-completed'` on normal completion, allowing handlers to distinguish from client disconnection.
120
-
121
- ### Lifecycle Hooks
122
-
123
- **RPC Implementations:**
124
-
125
- ```
126
- onRequestStart → handler → onSuccess → onRequestEnd
127
-
128
- (on error)
129
-
130
- onError handler
131
-
132
- onRequestEnd
133
- ```
134
-
135
- **Stream Implementations:**
136
-
137
- ```
138
- onRequestStart → onStreamStart → [yields...] → onStreamEnd → onRequestEnd
139
-
140
- (on error)
141
-
142
- error in stream
143
-
144
- onStreamEnd
145
- ```
146
-
147
- | Hook | Available In | Trigger |
148
- |------|--------------|---------|
149
- | `onRequestStart` | Both | Before route handler |
150
- | `onRequestEnd` | Both | After response sent |
151
- | `onSuccess` | RPC, API | After successful handler |
152
- | `onError` | RPC, API | On handler error |
153
- | `onStreamStart` | Stream | Before first yield |
154
- | `onStreamEnd` | Stream | After stream completes |
155
- | `onPreStreamError` | Stream | On pre-stream error (validation, auth) |
156
- | `onMidStreamError` | Stream | On mid-stream error (generator throws) |
157
-
158
- ### Route Documentation
159
-
160
- Each registered procedure generates documentation accessible via `builder.docs`.
161
-
162
- **RPC Documentation (`RPCHttpRouteDoc`):**
163
-
164
- ```typescript
165
- interface RPCHttpRouteDoc {
166
- name: string
167
- path: string
168
- method: 'post'
169
- scope: string | string[]
170
- version: number
171
- jsonSchema: {
172
- body?: object // From schema.params
173
- response?: object // From schema.returnType
174
- }
175
- }
176
- ```
177
-
178
- **Stream Documentation (`StreamHttpRouteDoc`):**
179
-
180
- ```typescript
181
- interface StreamHttpRouteDoc {
182
- name: string
183
- path: string
184
- methods: ('get' | 'post')[]
185
- streamMode: 'sse' | 'text'
186
- scope: string | string[]
187
- version: number
188
- jsonSchema: {
189
- params?: object // From schema.params
190
- yieldType?: object // From schema.yieldType
191
- returnType?: object // From schema.returnType
192
- }
193
- }
194
- ```
195
-
196
- **API Documentation (`APIHttpRouteDoc`):**
197
-
198
- ```typescript
199
- interface APIHttpRouteDoc {
200
- name: string
201
- path: string
202
- method: HttpMethod
203
- fullPath: string
204
- successStatus?: number
205
- jsonSchema: {
206
- pathParams?: object // From schema.input.pathParams
207
- query?: object // From schema.input.query
208
- body?: object // From schema.input.body
209
- headers?: object // From schema.input.headers
210
- response?: object // From schema.returnType
211
- }
212
- }
213
- ```
214
-
215
- ### Builder Pattern
216
-
217
- All implementations follow the same builder pattern:
218
-
219
- ```typescript
220
- const builder = new AppBuilder(config)
221
- .register(PublicFactory, publicContextResolver)
222
- .register(ProtectedFactory, protectedContextResolver)
223
-
224
- const app = builder.build()
225
- const docs = builder.docs
226
- ```
227
-
228
- **Note:** `HonoAPIAppBuilder.build()` is async (resolves query parser on first call).
229
-
230
- **Key methods:**
231
-
232
- | Method | Returns | Description |
233
- |--------|---------|-------------|
234
- | `register(factory, context, options?)` | `this` | Register a procedure factory |
235
- | `build()` | Framework app | Create routes and return the application |
236
-
237
- **Properties:**
238
-
239
- | Property | Type | Description |
240
- |----------|------|-------------|
241
- | `app` | Framework app | The underlying framework application |
242
- | `docs` | Route doc array | Route documentation (populated after `build()`) |
243
-
244
- ## Framework Comparison
245
-
246
- | Aspect | Express | Hono |
247
- |--------|---------|------|
248
- | Context param | `req: express.Request` | `c: Context` |
249
- | Error handler return | `void` (mutates res) | `Response` |
250
- | Body access | `req.body` | `await c.req.json()` |
251
- | Header access | `req.headers['x-id']` | `c.req.header('x-id')` |
252
- | JSON middleware | Auto-added (or manual) | Built-in |
253
- | Streaming support | Not yet | `hono-stream` |
254
-
255
- ## TypeScript Types
256
-
257
- ```typescript
258
- import { RPCConfig, RPCHttpRouteDoc, StreamHttpRouteDoc, StreamMode } from 'ts-procedures/implementations/types'
259
- import type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod } from 'ts-procedures/http'
260
- ```
@@ -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
- ```