ts-procedures 5.3.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.
Files changed (60) hide show
  1. package/README.md +90 -0
  2. package/agent_config/claude-code/agents/ts-procedures-architect.md +15 -0
  3. package/agent_config/claude-code/skills/guide/anti-patterns.md +106 -0
  4. package/agent_config/claude-code/skills/guide/api-reference.md +150 -4
  5. package/agent_config/claude-code/skills/guide/patterns.md +155 -0
  6. package/agent_config/claude-code/skills/review/checklist.md +22 -0
  7. package/agent_config/claude-code/skills/scaffold/SKILL.md +3 -1
  8. package/agent_config/claude-code/skills/scaffold/templates/hono-api.md +169 -0
  9. package/agent_config/copilot/copilot-instructions.md +35 -0
  10. package/agent_config/cursor/cursorrules +35 -0
  11. package/build/implementations/http/hono-api/index.d.ts +102 -0
  12. package/build/implementations/http/hono-api/index.js +339 -0
  13. package/build/implementations/http/hono-api/index.js.map +1 -0
  14. package/build/implementations/http/hono-api/index.test.d.ts +1 -0
  15. package/build/implementations/http/hono-api/index.test.js +983 -0
  16. package/build/implementations/http/hono-api/index.test.js.map +1 -0
  17. package/build/implementations/http/hono-api/types.d.ts +13 -0
  18. package/build/implementations/http/hono-api/types.js +2 -0
  19. package/build/implementations/http/hono-api/types.js.map +1 -0
  20. package/build/implementations/types.d.ts +44 -0
  21. package/build/index.d.ts +28 -6
  22. package/build/index.js +28 -0
  23. package/build/index.js.map +1 -1
  24. package/build/schema/compute-schema.d.ts +5 -0
  25. package/build/schema/compute-schema.js +8 -1
  26. package/build/schema/compute-schema.js.map +1 -1
  27. package/build/schema/parser.d.ts +6 -5
  28. package/build/schema/parser.js +54 -0
  29. package/build/schema/parser.js.map +1 -1
  30. package/package.json +8 -4
  31. package/src/errors.test.ts +0 -163
  32. package/src/errors.ts +0 -107
  33. package/src/exports.ts +0 -7
  34. package/src/implementations/http/README.md +0 -217
  35. package/src/implementations/http/express-rpc/README.md +0 -281
  36. package/src/implementations/http/express-rpc/index.test.ts +0 -957
  37. package/src/implementations/http/express-rpc/index.ts +0 -265
  38. package/src/implementations/http/express-rpc/types.ts +0 -16
  39. package/src/implementations/http/hono-rpc/README.md +0 -358
  40. package/src/implementations/http/hono-rpc/index.test.ts +0 -1075
  41. package/src/implementations/http/hono-rpc/index.ts +0 -237
  42. package/src/implementations/http/hono-rpc/types.ts +0 -16
  43. package/src/implementations/http/hono-stream/README.md +0 -526
  44. package/src/implementations/http/hono-stream/index.test.ts +0 -1676
  45. package/src/implementations/http/hono-stream/index.ts +0 -435
  46. package/src/implementations/http/hono-stream/types.ts +0 -29
  47. package/src/implementations/types.ts +0 -75
  48. package/src/index.test.ts +0 -1194
  49. package/src/index.ts +0 -435
  50. package/src/schema/compute-schema.test.ts +0 -128
  51. package/src/schema/compute-schema.ts +0 -67
  52. package/src/schema/extract-json-schema.test.ts +0 -25
  53. package/src/schema/extract-json-schema.ts +0 -15
  54. package/src/schema/parser.test.ts +0 -182
  55. package/src/schema/parser.ts +0 -148
  56. package/src/schema/resolve-schema-lib.test.ts +0 -19
  57. package/src/schema/resolve-schema-lib.ts +0 -29
  58. package/src/schema/types.ts +0 -20
  59. package/src/stack-utils.test.ts +0 -94
  60. package/src/stack-utils.ts +0 -129
@@ -1,163 +0,0 @@
1
- import { describe, expect, test } from 'vitest'
2
- import {
3
- ProcedureError,
4
- ProcedureValidationError,
5
- ProcedureRegistrationError,
6
- } from './errors.js'
7
- import { DefinitionInfo } from './stack-utils.js'
8
-
9
- describe('Error Classes', () => {
10
- test('ProcedureError has correct properties', () => {
11
- const err = new ProcedureError('TestProc', 'Something failed', { code: 123 })
12
-
13
- expect(err.name).toBe('ProcedureError')
14
- expect(err.procedureName).toBe('TestProc')
15
- expect(err.message).toBe('Something failed')
16
- expect(err.meta).toEqual({ code: 123 })
17
- expect(err instanceof Error).toBe(true)
18
- })
19
-
20
- test('ProcedureValidationError extends ProcedureError', () => {
21
- const err = new ProcedureValidationError('TestProc', 'Validation failed', [])
22
-
23
- expect(err instanceof ProcedureError).toBe(true)
24
- expect(err instanceof Error).toBe(true)
25
- expect(err.name).toBe('ProcedureValidationError')
26
- })
27
-
28
- test('ProcedureRegistrationError extends ProcedureError', () => {
29
- const err = new ProcedureRegistrationError('TestProc', 'Registration failed')
30
-
31
- expect(err instanceof ProcedureError).toBe(true)
32
- expect(err instanceof Error).toBe(true)
33
- expect(err.name).toBe('ProcedureRegistrationError')
34
- expect(err.procedureName).toBe('TestProc')
35
- })
36
-
37
- test('ProcedureError supports cause property', () => {
38
- const cause = new Error('Original error')
39
- const err = new ProcedureError('TestProc', 'Wrapped error')
40
- err.cause = cause
41
-
42
- expect(err.cause).toBe(cause)
43
- })
44
-
45
- test('All error types have consistent procedureName property', () => {
46
- const baseErr = new ProcedureError('Proc1', 'message')
47
- const validationErr = new ProcedureValidationError('Proc2', 'message', [])
48
- const registrationErr = new ProcedureRegistrationError('Proc3', 'message')
49
-
50
- expect(baseErr.procedureName).toBe('Proc1')
51
- expect(validationErr.procedureName).toBe('Proc2')
52
- expect(registrationErr.procedureName).toBe('Proc3')
53
- })
54
- })
55
-
56
- describe('Error Classes - Definition Info', () => {
57
- const mockDefinitionInfo: DefinitionInfo = {
58
- definedAt: {
59
- file: '/app/procedures/user.ts',
60
- line: 25,
61
- column: 3,
62
- raw: 'at Object.<anonymous> (/app/procedures/user.ts:25:3)',
63
- },
64
- definitionStack: 'Error\n at Object.<anonymous> (/app/procedures/user.ts:25:3)',
65
- }
66
-
67
- test('ProcedureError includes definedAt when provided', () => {
68
- const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
69
-
70
- expect(err.definedAt).toBeDefined()
71
- expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
72
- expect(err.definedAt?.line).toBe(25)
73
- expect(err.definedAt?.column).toBe(3)
74
- })
75
-
76
- test('ProcedureError includes definitionStack when provided', () => {
77
- const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
78
-
79
- expect(err.definitionStack).toBeDefined()
80
- expect(err.definitionStack).toContain('/app/procedures/user.ts')
81
- })
82
-
83
- test('ProcedureError works without definitionInfo (backward compat)', () => {
84
- const err = new ProcedureError('TestProc', 'Something failed', { code: 123 })
85
-
86
- expect(err.definedAt).toBeUndefined()
87
- expect(err.definitionStack).toBeUndefined()
88
- expect(err.procedureName).toBe('TestProc')
89
- expect(err.message).toBe('Something failed')
90
- expect(err.meta).toEqual({ code: 123 })
91
- })
92
-
93
- test('ProcedureValidationError includes definedAt when provided', () => {
94
- const err = new ProcedureValidationError('TestProc', 'Validation failed', [], mockDefinitionInfo)
95
-
96
- expect(err.definedAt).toBeDefined()
97
- expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
98
- expect(err.definedAt?.line).toBe(25)
99
- })
100
-
101
- test('ProcedureValidationError works without definitionInfo (backward compat)', () => {
102
- const err = new ProcedureValidationError('TestProc', 'Validation failed', [])
103
-
104
- expect(err.definedAt).toBeUndefined()
105
- expect(err.definitionStack).toBeUndefined()
106
- expect(err.name).toBe('ProcedureValidationError')
107
- })
108
-
109
- test('ProcedureRegistrationError includes definedAt when provided', () => {
110
- const err = new ProcedureRegistrationError('TestProc', 'Registration failed', mockDefinitionInfo)
111
-
112
- expect(err.definedAt).toBeDefined()
113
- expect(err.definedAt?.file).toBe('/app/procedures/user.ts')
114
- })
115
-
116
- test('ProcedureRegistrationError works without definitionInfo (backward compat)', () => {
117
- const err = new ProcedureRegistrationError('TestProc', 'Registration failed')
118
-
119
- expect(err.definedAt).toBeUndefined()
120
- expect(err.definitionStack).toBeUndefined()
121
- expect(err.name).toBe('ProcedureRegistrationError')
122
- })
123
-
124
- test('getDefinitionLocation returns formatted location string', () => {
125
- const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
126
-
127
- const location = err.getDefinitionLocation()
128
-
129
- expect(location).toBe('/app/procedures/user.ts:25:3')
130
- })
131
-
132
- test('getDefinitionLocation returns undefined when no definedAt', () => {
133
- const err = new ProcedureError('TestProc', 'Something failed')
134
-
135
- const location = err.getDefinitionLocation()
136
-
137
- expect(location).toBeUndefined()
138
- })
139
-
140
- test('enhanced stack contains definition location', () => {
141
- const err = new ProcedureError('TestProc', 'Something failed', undefined, mockDefinitionInfo)
142
-
143
- expect(err.stack).toContain('--- Procedure "TestProc" defined at ---')
144
- expect(err.stack).toContain('/app/procedures/user.ts:25:3')
145
- })
146
-
147
- test('stack is not modified when no definitionInfo', () => {
148
- const err = new ProcedureError('TestProc', 'Something failed')
149
-
150
- expect(err.stack).toBeDefined()
151
- expect(err.stack).not.toContain('--- Procedure')
152
- })
153
-
154
- test('all error types enhance stack with definition location', () => {
155
- const baseErr = new ProcedureError('Proc1', 'message', undefined, mockDefinitionInfo)
156
- const validationErr = new ProcedureValidationError('Proc2', 'message', [], mockDefinitionInfo)
157
- const registrationErr = new ProcedureRegistrationError('Proc3', 'message', mockDefinitionInfo)
158
-
159
- expect(baseErr.stack).toContain('--- Procedure "Proc1" defined at ---')
160
- expect(validationErr.stack).toContain('--- Procedure "Proc2" defined at ---')
161
- expect(registrationErr.stack).toContain('--- Procedure "Proc3" defined at ---')
162
- })
163
- })
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,217 +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
- ## Procedure Types
25
-
26
- | Type | Created With | Handler Return | HTTP Methods | Use Case |
27
- |------|--------------|----------------|--------------|----------|
28
- | RPC | `Create()` | `Promise<T>` | POST | Standard request/response |
29
- | Stream | `CreateStream()` | `AsyncGenerator<T>` | GET, POST | Real-time updates, SSE |
30
-
31
- ## Core Concepts
32
-
33
- ### Config Interface
34
-
35
- All HTTP implementations use a shared configuration interface:
36
-
37
- ```typescript
38
- interface RPCConfig {
39
- scope: string | string[] // Route path segment(s)
40
- version: number // API version number
41
- }
42
- ```
43
-
44
- ### Path Generation
45
-
46
- Routes are generated using kebab-case conversion:
47
-
48
- ```
49
- /{pathPrefix}/{scope...}/{procedureName}/{version}
50
- ```
51
-
52
- **Examples:**
53
-
54
- | Scope | Procedure Name | Version | Generated Path |
55
- |-------|----------------|---------|----------------|
56
- | `'users'` | `'Create'` | `1` | `/users/create/1` |
57
- | `'users'` | `'GetById'` | `1` | `/users/get-by-id/1` |
58
- | `['users', 'admin']` | `'List'` | `1` | `/users/admin/list/1` |
59
- | `['UserModule', 'permissions']` | `'Update'` | `2` | `/user-module/permissions/update/2` |
60
-
61
- **With pathPrefix `/api/v1`:**
62
-
63
- | Scope | Procedure Name | Version | Generated Path |
64
- |-------|----------------|---------|----------------|
65
- | `'users'` | `'Create'` | `1` | `/api/v1/users/create/1` |
66
- | `['users', 'admin']` | `'Delete'` | `2` | `/api/v1/users/admin/delete/2` |
67
-
68
- ### Context Resolution
69
-
70
- The `factoryContext` parameter supports three patterns:
71
-
72
- ```typescript
73
- // 1. Static object
74
- builder.register(Factory, { userId: 'static-123' })
75
-
76
- // 2. Sync function
77
- builder.register(Factory, (c) => ({
78
- userId: c.req.header('x-user-id')
79
- }))
80
-
81
- // 3. Async function
82
- builder.register(Factory, async (c) => {
83
- const user = await validateToken(c.req.header('authorization'))
84
- return { userId: user.id }
85
- })
86
- ```
87
-
88
- ### Abort Signal
89
-
90
- 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.).
91
-
92
- | Framework | Signal Source | Behavior |
93
- |-----------|-------------|----------|
94
- | Hono RPC | `c.req.raw.signal` | Web standard Request signal |
95
- | Hono Stream | `c.req.raw.signal` | Combined with internal stream AbortController via `AbortSignal.any()` |
96
- | Express RPC | Lazy `AbortController` | Created on first `ctx.signal` access, wired to `req.on('close')` |
97
-
98
- For streaming procedures, `signal.reason` is `'stream-completed'` on normal completion, allowing handlers to distinguish from client disconnection.
99
-
100
- ### Lifecycle Hooks
101
-
102
- **RPC Implementations:**
103
-
104
- ```
105
- onRequestStart → handler → onSuccess → onRequestEnd
106
-
107
- (on error)
108
-
109
- onError handler
110
-
111
- onRequestEnd
112
- ```
113
-
114
- **Stream Implementations:**
115
-
116
- ```
117
- onRequestStart → onStreamStart → [yields...] → onStreamEnd → onRequestEnd
118
-
119
- (on error)
120
-
121
- error in stream
122
-
123
- onStreamEnd
124
- ```
125
-
126
- | Hook | Available In | Trigger |
127
- |------|--------------|---------|
128
- | `onRequestStart` | Both | Before route handler |
129
- | `onRequestEnd` | Both | After response sent |
130
- | `onSuccess` | RPC | After successful handler |
131
- | `onError` | RPC | On handler error |
132
- | `onStreamStart` | Stream | Before first yield |
133
- | `onStreamEnd` | Stream | After stream completes |
134
- | `onPreStreamError` | Stream | On pre-stream error (validation, auth) |
135
- | `onMidStreamError` | Stream | On mid-stream error (generator throws) |
136
-
137
- ### Route Documentation
138
-
139
- Each registered procedure generates documentation accessible via `builder.docs`.
140
-
141
- **RPC Documentation (`RPCHttpRouteDoc`):**
142
-
143
- ```typescript
144
- interface RPCHttpRouteDoc {
145
- name: string
146
- path: string
147
- method: 'post'
148
- scope: string | string[]
149
- version: number
150
- jsonSchema: {
151
- body?: object // From schema.params
152
- response?: object // From schema.returnType
153
- }
154
- }
155
- ```
156
-
157
- **Stream Documentation (`StreamHttpRouteDoc`):**
158
-
159
- ```typescript
160
- interface StreamHttpRouteDoc {
161
- name: string
162
- path: string
163
- methods: ('get' | 'post')[]
164
- streamMode: 'sse' | 'text'
165
- scope: string | string[]
166
- version: number
167
- jsonSchema: {
168
- params?: object // From schema.params
169
- yieldType?: object // From schema.yieldType
170
- returnType?: object // From schema.returnType
171
- }
172
- }
173
- ```
174
-
175
- ### Builder Pattern
176
-
177
- All implementations follow the same builder pattern:
178
-
179
- ```typescript
180
- const builder = new AppBuilder(config)
181
- .register(PublicFactory, publicContextResolver)
182
- .register(ProtectedFactory, protectedContextResolver)
183
-
184
- const app = builder.build()
185
- const docs = builder.docs
186
- ```
187
-
188
- **Key methods:**
189
-
190
- | Method | Returns | Description |
191
- |--------|---------|-------------|
192
- | `register(factory, context, options?)` | `this` | Register a procedure factory |
193
- | `build()` | Framework app | Create routes and return the application |
194
-
195
- **Properties:**
196
-
197
- | Property | Type | Description |
198
- |----------|------|-------------|
199
- | `app` | Framework app | The underlying framework application |
200
- | `docs` | Route doc array | Route documentation (populated after `build()`) |
201
-
202
- ## Framework Comparison
203
-
204
- | Aspect | Express | Hono |
205
- |--------|---------|------|
206
- | Context param | `req: express.Request` | `c: Context` |
207
- | Error handler return | `void` (mutates res) | `Response` |
208
- | Body access | `req.body` | `await c.req.json()` |
209
- | Header access | `req.headers['x-id']` | `c.req.header('x-id')` |
210
- | JSON middleware | Auto-added (or manual) | Built-in |
211
- | Streaming support | Not yet | `hono-stream` |
212
-
213
- ## TypeScript Types
214
-
215
- ```typescript
216
- import { RPCConfig, RPCHttpRouteDoc, StreamHttpRouteDoc, StreamMode } from 'ts-procedures/implementations/types'
217
- ```