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.
Files changed (63) hide show
  1. package/agent_config/claude-code/agents/ts-procedures-architect.md +1 -1
  2. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -1
  3. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +2 -2
  4. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +15 -27
  5. package/agent_config/claude-code/skills/ts-procedures/patterns.md +11 -4
  6. package/agent_config/claude-code/skills/ts-procedures-review/checklist.md +2 -2
  7. package/agent_config/copilot/copilot-instructions.md +3 -2
  8. package/agent_config/cursor/cursorrules +3 -2
  9. package/build/codegen/targets/kotlin/ajsc-adapter.d.ts +24 -0
  10. package/build/codegen/targets/kotlin/ajsc-adapter.js +33 -0
  11. package/build/codegen/targets/kotlin/ajsc-adapter.js.map +1 -0
  12. package/build/codegen/targets/kotlin/ajsc-adapter.test.d.ts +1 -0
  13. package/build/codegen/targets/kotlin/ajsc-adapter.test.js +19 -0
  14. package/build/codegen/targets/kotlin/ajsc-adapter.test.js.map +1 -0
  15. package/build/codegen/targets/kotlin/e2e-compile.test.d.ts +1 -0
  16. package/build/codegen/targets/kotlin/e2e-compile.test.js +43 -0
  17. package/build/codegen/targets/kotlin/e2e-compile.test.js.map +1 -0
  18. package/build/codegen/targets/kotlin/emit-route-kotlin.d.ts +11 -0
  19. package/build/codegen/targets/kotlin/emit-route-kotlin.js +73 -0
  20. package/build/codegen/targets/kotlin/emit-route-kotlin.js.map +1 -0
  21. package/build/codegen/targets/kotlin/emit-route-kotlin.test.d.ts +1 -0
  22. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js +88 -0
  23. package/build/codegen/targets/kotlin/emit-route-kotlin.test.js.map +1 -0
  24. package/build/codegen/targets/kotlin/emit-scope-kotlin.d.ts +11 -0
  25. package/build/codegen/targets/kotlin/emit-scope-kotlin.js +35 -0
  26. package/build/codegen/targets/kotlin/emit-scope-kotlin.js.map +1 -0
  27. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.d.ts +1 -0
  28. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js +52 -0
  29. package/build/codegen/targets/kotlin/emit-scope-kotlin.test.js.map +1 -0
  30. package/build/codegen/targets/kotlin/format-kotlin.d.ts +4 -0
  31. package/build/codegen/targets/kotlin/format-kotlin.js +20 -0
  32. package/build/codegen/targets/kotlin/format-kotlin.js.map +1 -0
  33. package/build/codegen/targets/kotlin/format-kotlin.test.d.ts +1 -0
  34. package/build/codegen/targets/kotlin/format-kotlin.test.js +24 -0
  35. package/build/codegen/targets/kotlin/format-kotlin.test.js.map +1 -0
  36. package/build/codegen/targets/kotlin/integration.test.d.ts +1 -0
  37. package/build/codegen/targets/kotlin/integration.test.js +34 -0
  38. package/build/codegen/targets/kotlin/integration.test.js.map +1 -0
  39. package/build/implementations/http/doc-registry.d.ts +14 -19
  40. package/build/implementations/http/doc-registry.js +41 -46
  41. package/build/implementations/http/doc-registry.js.map +1 -1
  42. package/build/implementations/http/doc-registry.test.js +141 -10
  43. package/build/implementations/http/doc-registry.test.js.map +1 -1
  44. package/build/implementations/http/error-taxonomy.d.ts +11 -2
  45. package/build/implementations/http/error-taxonomy.js +24 -2
  46. package/build/implementations/http/error-taxonomy.js.map +1 -1
  47. package/build/implementations/http/route-errors.test.js +5 -6
  48. package/build/implementations/http/route-errors.test.js.map +1 -1
  49. package/build/implementations/types.d.ts +13 -1
  50. package/docs/http-integrations.md +39 -5
  51. package/docs/superpowers/plans/2026-04-24-doc-registry-simplification.md +886 -0
  52. package/docs/superpowers/plans/2026-04-24-kotlin-codegen-target.md +1265 -0
  53. package/docs/superpowers/specs/2026-04-24-kotlin-swift-codegen-design.md +401 -0
  54. package/package.json +1 -1
  55. package/src/implementations/http/README.md +4 -3
  56. package/src/implementations/http/doc-registry.test.ts +154 -10
  57. package/src/implementations/http/doc-registry.ts +46 -53
  58. package/src/implementations/http/error-taxonomy.ts +26 -2
  59. package/src/implementations/http/express-rpc/README.md +2 -2
  60. package/src/implementations/http/hono-rpc/README.md +2 -2
  61. package/src/implementations/http/hono-stream/README.md +15 -0
  62. package/src/implementations/http/route-errors.test.ts +5 -6
  63. 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
- ErrorTaxonomy,
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
- * `ProcedureRegistrationError` is thrown at procedure-definition time (never at
28
- * request time), so it doesn't appear in the runtime taxonomy. It is documented
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
- const PROCEDURE_REGISTRATION_ERROR_DOC: ErrorDoc = {
32
- name: 'ProcedureRegistrationError',
33
- statusCode: 500,
34
- description:
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 readonly errors: ErrorDoc[]
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
- this.errors = config?.errors ?? []
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 for the DocEnvelope derived from
87
- * {@link defaultErrorTaxonomy} so the documented shape cannot drift from what
88
- * the HTTP builders actually emit at runtime. `ProcedureRegistrationError` is
89
- * appended because it's thrown at registration time (never at request time)
90
- * and therefore lives only in the catalog, not the runtime taxonomy.
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
- * Single source of truth so the runtime mapping and the documented shape
188
- * cannot drift apart.
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
- error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void
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
- | `error` | `(proc, req, res, err) => void` | Custom error handler |
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
- error?: (
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
- | `error` | `(proc, c, err) => Response` | Custom error handler (must return Response) |
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.fromTaxonomy', () => {
120
+ describe('DocRegistry with taxonomy input', () => {
121
121
  test('seeds envelope errors from the taxonomy + framework defaults', () => {
122
- const registry = DocRegistry.fromTaxonomy(appErrors, { basePath: '/api' })
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.fromTaxonomy(appErrors, { includeDefaults: false })
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.fromTaxonomy(overridden).toJSON()
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.fromTaxonomy(appErrors).from(app).toJSON()
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
- errors?: ErrorDoc[]
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> {