ts-procedures 6.0.0 → 6.0.2
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/agent_config/claude-code/agents/ts-procedures-architect.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +2 -2
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +15 -27
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +11 -4
- package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +2 -2
- package/agent_config/copilot/copilot-instructions.md +3 -2
- package/agent_config/cursor/cursorrules +3 -2
- package/build/codegen/targets/kotlin/ajsc-adapter.d.ts +24 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.js +33 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.js.map +1 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.js +19 -0
- package/build/codegen/targets/kotlin/ajsc-adapter.test.js.map +1 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.js +43 -0
- package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +11 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.js +73 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -0
- package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.d.ts +11 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js +35 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +52 -0
- package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.d.ts +4 -0
- package/build/codegen/targets/kotlin/format-kotlin.js +20 -0
- package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.js +24 -0
- package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -0
- package/build/codegen/targets/kotlin/integration.test.d.ts +1 -0
- package/build/codegen/targets/kotlin/integration.test.js +34 -0
- package/build/codegen/targets/kotlin/integration.test.js.map +1 -0
- package/build/implementations/http/doc-registry.d.ts +14 -19
- package/build/implementations/http/doc-registry.js +41 -46
- package/build/implementations/http/doc-registry.js.map +1 -1
- package/build/implementations/http/doc-registry.test.js +141 -10
- package/build/implementations/http/doc-registry.test.js.map +1 -1
- package/build/implementations/http/error-taxonomy.d.ts +11 -2
- package/build/implementations/http/error-taxonomy.js +24 -2
- package/build/implementations/http/error-taxonomy.js.map +1 -1
- package/build/implementations/http/route-errors.test.js +5 -6
- package/build/implementations/http/route-errors.test.js.map +1 -1
- package/build/implementations/types.d.ts +13 -1
- package/docs/http-integrations.md +39 -5
- package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +886 -0
- package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +1265 -0
- package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +401 -0
- package/package.json +1 -1
- package/src/implementations/http/README.md +4 -3
- package/src/implementations/http/doc-registry.test.ts +154 -10
- package/src/implementations/http/doc-registry.ts +46 -53
- package/src/implementations/http/error-taxonomy.ts +26 -2
- package/src/implementations/http/express-rpc/README.md +2 -2
- package/src/implementations/http/hono-rpc/README.md +2 -2
- package/src/implementations/http/hono-stream/README.md +15 -0
- package/src/implementations/http/route-errors.test.ts +5 -6
- package/src/implementations/types.ts +13 -1
|
@@ -8,9 +8,10 @@ import type {
|
|
|
8
8
|
HeaderDoc,
|
|
9
9
|
} from '../types.js'
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
PROCEDURE_REGISTRATION_ERROR_DOC,
|
|
12
12
|
defaultErrorTaxonomy,
|
|
13
13
|
taxonomyToErrorDocs,
|
|
14
|
+
type ErrorTaxonomy,
|
|
14
15
|
} from './error-taxonomy.js'
|
|
15
16
|
|
|
16
17
|
export type {
|
|
@@ -23,37 +24,44 @@ export type {
|
|
|
23
24
|
HeaderDoc,
|
|
24
25
|
} from '../types.js'
|
|
25
26
|
|
|
27
|
+
function isTaxonomy(input: ErrorTaxonomy | ErrorDoc[]): input is ErrorTaxonomy {
|
|
28
|
+
return !Array.isArray(input)
|
|
29
|
+
}
|
|
30
|
+
|
|
26
31
|
/**
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
* here so consumers still see it in the error catalog.
|
|
32
|
+
* Dedupes ErrorDocs by `name`, last occurrence wins. Map insertion order
|
|
33
|
+
* preserves the latest position of each name.
|
|
30
34
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
'An invalid schema or configuration was detected at procedure registration time.',
|
|
36
|
-
schema: {
|
|
37
|
-
type: 'object',
|
|
38
|
-
properties: {
|
|
39
|
-
name: { type: 'string', const: 'ProcedureRegistrationError' },
|
|
40
|
-
procedureName: { type: 'string' },
|
|
41
|
-
message: { type: 'string' },
|
|
42
|
-
},
|
|
43
|
-
required: ['name', 'procedureName', 'message'],
|
|
44
|
-
},
|
|
35
|
+
function dedupeByName(docs: ErrorDoc[]): ErrorDoc[] {
|
|
36
|
+
const byName = new Map<string, ErrorDoc>()
|
|
37
|
+
for (const doc of docs) byName.set(doc.name, doc)
|
|
38
|
+
return Array.from(byName.values())
|
|
45
39
|
}
|
|
46
40
|
|
|
47
41
|
export class DocRegistry {
|
|
48
42
|
private readonly basePath: string
|
|
49
43
|
private readonly headers: HeaderDoc[]
|
|
50
|
-
private
|
|
44
|
+
private errors: ErrorDoc[]
|
|
51
45
|
private readonly sources: DocSource<AnyHttpRouteDoc>[] = []
|
|
52
46
|
|
|
53
47
|
constructor(config?: DocRegistryConfig) {
|
|
54
48
|
this.basePath = config?.basePath ?? ''
|
|
55
49
|
this.headers = config?.headers ?? []
|
|
56
|
-
|
|
50
|
+
|
|
51
|
+
const includeDefaults = config?.includeDefaults ?? true
|
|
52
|
+
const userErrors: ErrorDoc[] = config?.errors
|
|
53
|
+
? isTaxonomy(config.errors)
|
|
54
|
+
? taxonomyToErrorDocs(config.errors)
|
|
55
|
+
: config.errors
|
|
56
|
+
: []
|
|
57
|
+
|
|
58
|
+
// Precedence: defaults come first, user errors override via dedupe
|
|
59
|
+
// (last-write-wins). Matches runtime resolution order.
|
|
60
|
+
const merged = includeDefaults
|
|
61
|
+
? [...DocRegistry.defaultErrors(), ...userErrors]
|
|
62
|
+
: userErrors
|
|
63
|
+
|
|
64
|
+
this.errors = dedupeByName(merged)
|
|
57
65
|
}
|
|
58
66
|
|
|
59
67
|
from(source: DocSource<AnyHttpRouteDoc>): this {
|
|
@@ -61,6 +69,18 @@ export class DocRegistry {
|
|
|
61
69
|
return this
|
|
62
70
|
}
|
|
63
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Adds one or more {@link ErrorDoc} entries to the envelope. Use for errors
|
|
74
|
+
* outside your runtime taxonomy — middleware-level errors, infrastructure
|
|
75
|
+
* errors (502/503/504), or doc-only meta errors.
|
|
76
|
+
*
|
|
77
|
+
* Deduped by `name` — last write wins.
|
|
78
|
+
*/
|
|
79
|
+
documentError(...docs: ErrorDoc[]): this {
|
|
80
|
+
this.errors = dedupeByName([...this.errors, ...docs])
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
|
|
64
84
|
toJSON<T = DocEnvelope>(options?: DocRegistryOutputOptions<T>): T {
|
|
65
85
|
let routes = this.sources.flatMap((source) => source.docs)
|
|
66
86
|
|
|
@@ -83,11 +103,12 @@ export class DocRegistry {
|
|
|
83
103
|
}
|
|
84
104
|
|
|
85
105
|
/**
|
|
86
|
-
* Framework error defaults
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
106
|
+
* Framework error defaults — derived from {@link defaultErrorTaxonomy} plus
|
|
107
|
+
* `ProcedureRegistrationError` (which is thrown only at registration time
|
|
108
|
+
* and therefore lives in the catalog, not the runtime taxonomy).
|
|
109
|
+
*
|
|
110
|
+
* Most consumers do not need to call this directly — the `DocRegistry`
|
|
111
|
+
* constructor auto-includes these unless `includeDefaults: false` is passed.
|
|
91
112
|
*/
|
|
92
113
|
static defaultErrors(): ErrorDoc[] {
|
|
93
114
|
return [
|
|
@@ -95,32 +116,4 @@ export class DocRegistry {
|
|
|
95
116
|
PROCEDURE_REGISTRATION_ERROR_DOC,
|
|
96
117
|
]
|
|
97
118
|
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Convenience constructor that seeds `config.errors` from a taxonomy so the
|
|
101
|
-
* DocEnvelope automatically documents every error class registered with the
|
|
102
|
-
* HTTP builders. Framework defaults (including `ProcedureRegistrationError`)
|
|
103
|
-
* are included unless `includeDefaults: false` is passed.
|
|
104
|
-
*
|
|
105
|
-
* @example
|
|
106
|
-
* const registry = DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' })
|
|
107
|
-
* .from(apiApp)
|
|
108
|
-
*/
|
|
109
|
-
static fromTaxonomy(
|
|
110
|
-
taxonomy: ErrorTaxonomy,
|
|
111
|
-
config?: Omit<DocRegistryConfig, 'errors'> & { includeDefaults?: boolean }
|
|
112
|
-
): DocRegistry {
|
|
113
|
-
const { includeDefaults = true, ...rest } = config ?? {}
|
|
114
|
-
const errors: ErrorDoc[] = [
|
|
115
|
-
...taxonomyToErrorDocs(taxonomy),
|
|
116
|
-
...(includeDefaults ? DocRegistry.defaultErrors() : []),
|
|
117
|
-
]
|
|
118
|
-
// Dedupe by name — user entries take precedence over defaults with the
|
|
119
|
-
// same key, matching runtime resolution order.
|
|
120
|
-
const seen = new Set<string>()
|
|
121
|
-
const deduped = errors.filter((e) =>
|
|
122
|
-
seen.has(e.name) ? false : (seen.add(e.name), true)
|
|
123
|
-
)
|
|
124
|
-
return new DocRegistry({ ...rest, errors: deduped })
|
|
125
|
-
}
|
|
126
119
|
}
|
|
@@ -182,10 +182,34 @@ export const defaultErrorTaxonomy = defineErrorTaxonomy({
|
|
|
182
182
|
},
|
|
183
183
|
})
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Doc-only entry for `ProcedureRegistrationError`, which is thrown at
|
|
187
|
+
* procedure-definition time (never at request time) and therefore doesn't
|
|
188
|
+
* appear in the runtime taxonomy. Consumers still see it in the error catalog
|
|
189
|
+
* via `DocRegistry.defaultErrors()`.
|
|
190
|
+
*/
|
|
191
|
+
export const PROCEDURE_REGISTRATION_ERROR_DOC: ErrorDoc = {
|
|
192
|
+
name: 'ProcedureRegistrationError',
|
|
193
|
+
statusCode: 500,
|
|
194
|
+
description:
|
|
195
|
+
'An invalid schema or configuration was detected at procedure registration time.',
|
|
196
|
+
schema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
name: { type: 'string', const: 'ProcedureRegistrationError' },
|
|
200
|
+
procedureName: { type: 'string' },
|
|
201
|
+
message: { type: 'string' },
|
|
202
|
+
},
|
|
203
|
+
required: ['name', 'procedureName', 'message'],
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
|
|
185
207
|
/**
|
|
186
208
|
* Converts a taxonomy into {@link ErrorDoc} objects suitable for a DocEnvelope.
|
|
187
|
-
*
|
|
188
|
-
*
|
|
209
|
+
*
|
|
210
|
+
* @internal Used by `DocRegistry` to merge taxonomy entries into the envelope.
|
|
211
|
+
* Consumers should pass their taxonomy directly to `new DocRegistry({ errors: taxonomy })`
|
|
212
|
+
* rather than calling this helper — the constructor handles the conversion.
|
|
189
213
|
*/
|
|
190
214
|
export function taxonomyToErrorDocs(taxonomy: ErrorTaxonomy): ErrorDoc[] {
|
|
191
215
|
return Object.entries(taxonomy).map(([key, entry]) => ({
|
|
@@ -59,7 +59,7 @@ type ExpressRPCAppBuilderConfig = {
|
|
|
59
59
|
onRequestStart?: (req: express.Request) => void
|
|
60
60
|
onRequestEnd?: (req: express.Request, res: express.Response) => void
|
|
61
61
|
onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void
|
|
62
|
-
|
|
62
|
+
onError?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void
|
|
63
63
|
}
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -70,7 +70,7 @@ type ExpressRPCAppBuilderConfig = {
|
|
|
70
70
|
| `onRequestStart` | `(req) => void` | Called at start of each request |
|
|
71
71
|
| `onRequestEnd` | `(req, res) => void` | Called after response finishes |
|
|
72
72
|
| `onSuccess` | `(proc, req, res) => void` | Called on successful handler execution |
|
|
73
|
-
| `
|
|
73
|
+
| `onError` | `(proc, req, res, err) => void` | Imperative error handler (peer of `errors` taxonomy — see Error Handling) |
|
|
74
74
|
|
|
75
75
|
## Context Resolution
|
|
76
76
|
|
|
@@ -64,7 +64,7 @@ type HonoRPCAppBuilderConfig = {
|
|
|
64
64
|
onRequestStart?: (c: Context) => void
|
|
65
65
|
onRequestEnd?: (c: Context) => void
|
|
66
66
|
onSuccess?: (procedure: TProcedureRegistration, c: Context) => void
|
|
67
|
-
|
|
67
|
+
onError?: (
|
|
68
68
|
procedure: TProcedureRegistration,
|
|
69
69
|
c: Context,
|
|
70
70
|
error: Error
|
|
@@ -79,7 +79,7 @@ type HonoRPCAppBuilderConfig = {
|
|
|
79
79
|
| `onRequestStart` | `(c) => void` | Called at start of each request |
|
|
80
80
|
| `onRequestEnd` | `(c) => void` | Called after handler completes |
|
|
81
81
|
| `onSuccess` | `(proc, c) => void` | Called on successful handler execution |
|
|
82
|
-
| `
|
|
82
|
+
| `onError` | `(proc, c, err) => Response` | Imperative error handler (peer of `errors` taxonomy — see Error Handling) |
|
|
83
83
|
|
|
84
84
|
## Context Resolution
|
|
85
85
|
|
|
@@ -221,6 +221,21 @@ builder.register(factory, context, {
|
|
|
221
221
|
})
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
## Using an Existing Hono App
|
|
225
|
+
|
|
226
|
+
Pass an existing `Hono` instance to mount stream routes alongside other routes — including those registered by `HonoRPCAppBuilder` and `HonoAPIAppBuilder`. One Hono server can host all three builders.
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const app = new Hono()
|
|
230
|
+
app.use('*', cors())
|
|
231
|
+
|
|
232
|
+
new HonoStreamAppBuilder({ app })
|
|
233
|
+
.register(StreamFactory, contextResolver)
|
|
234
|
+
.build()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
For the full single-server pattern (RPC + API + Stream + `DocRegistry` on one app), see [docs/http-integrations.md § One Hono Server, Multiple Builders](../../../../docs/http-integrations.md#one-hono-server-multiple-builders).
|
|
238
|
+
|
|
224
239
|
## Lifecycle Hooks
|
|
225
240
|
|
|
226
241
|
Hooks execute in the following order:
|
|
@@ -117,9 +117,9 @@ describe('per-route errors declaration', () => {
|
|
|
117
117
|
})
|
|
118
118
|
})
|
|
119
119
|
|
|
120
|
-
describe('DocRegistry
|
|
120
|
+
describe('DocRegistry with taxonomy input', () => {
|
|
121
121
|
test('seeds envelope errors from the taxonomy + framework defaults', () => {
|
|
122
|
-
const registry = DocRegistry
|
|
122
|
+
const registry = new DocRegistry({ errors: appErrors, basePath: '/api' })
|
|
123
123
|
const envelope = registry.toJSON()
|
|
124
124
|
const names = envelope.errors.map((e) => e.name)
|
|
125
125
|
expect(names).toContain('UseCaseError')
|
|
@@ -130,7 +130,7 @@ describe('DocRegistry.fromTaxonomy', () => {
|
|
|
130
130
|
})
|
|
131
131
|
|
|
132
132
|
test('includeDefaults: false omits framework entries', () => {
|
|
133
|
-
const registry = DocRegistry
|
|
133
|
+
const registry = new DocRegistry({ errors: appErrors, includeDefaults: false })
|
|
134
134
|
const names = registry.toJSON().errors.map((e) => e.name)
|
|
135
135
|
expect(names).toEqual(['UseCaseError', 'AuthError'])
|
|
136
136
|
})
|
|
@@ -143,11 +143,10 @@ describe('DocRegistry.fromTaxonomy', () => {
|
|
|
143
143
|
description: 'custom override',
|
|
144
144
|
},
|
|
145
145
|
})
|
|
146
|
-
const envelope = DocRegistry
|
|
146
|
+
const envelope = new DocRegistry({ errors: overridden }).toJSON()
|
|
147
147
|
const proc = envelope.errors.find((e) => e.name === 'ProcedureError')
|
|
148
148
|
expect(proc?.statusCode).toBe(418)
|
|
149
149
|
expect(proc?.description).toBe('custom override')
|
|
150
|
-
// no duplicates
|
|
151
150
|
const count = envelope.errors.filter((e) => e.name === 'ProcedureError').length
|
|
152
151
|
expect(count).toBe(1)
|
|
153
152
|
})
|
|
@@ -170,7 +169,7 @@ describe('DocRegistry.fromTaxonomy', () => {
|
|
|
170
169
|
const app = new HonoAPIAppBuilder().register(API, () => ({}))
|
|
171
170
|
app.build()
|
|
172
171
|
|
|
173
|
-
const envelope = DocRegistry
|
|
172
|
+
const envelope = new DocRegistry({ errors: appErrors }).from(app).toJSON()
|
|
174
173
|
const route = envelope.routes.find((r) => r.kind === 'api' && r.name === 'GetUser')
|
|
175
174
|
expect(route?.errors).toEqual(['UseCaseError'])
|
|
176
175
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Procedures } from '../index.js'
|
|
2
|
+
import type { ErrorTaxonomy } from './http/error-taxonomy.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @typeParam TErrorKey - Union of valid taxonomy keys. Defaults to `string`
|
|
@@ -186,7 +187,18 @@ export interface ErrorDoc {
|
|
|
186
187
|
export interface DocRegistryConfig {
|
|
187
188
|
basePath?: string
|
|
188
189
|
headers?: HeaderDoc[]
|
|
189
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Errors to document in the envelope. Accepts either your runtime
|
|
192
|
+
* {@link ErrorTaxonomy} (from `defineErrorTaxonomy`) — the common case — or
|
|
193
|
+
* a raw `ErrorDoc[]` for consumers who aren't using a taxonomy.
|
|
194
|
+
*
|
|
195
|
+
* Framework defaults (`ProcedureError`, `ProcedureValidationError`,
|
|
196
|
+
* `ProcedureYieldValidationError`, `ProcedureRegistrationError`) are merged
|
|
197
|
+
* in automatically and deduped. Opt out via `includeDefaults: false`.
|
|
198
|
+
*/
|
|
199
|
+
errors?: ErrorTaxonomy | ErrorDoc[]
|
|
200
|
+
/** Whether to auto-merge framework error defaults. Defaults to `true`. */
|
|
201
|
+
includeDefaults?: boolean
|
|
190
202
|
}
|
|
191
203
|
|
|
192
204
|
export interface DocRegistryOutputOptions<TEnvelope = DocEnvelope> {
|