ts-procedures 10.1.2 → 10.2.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 (272) hide show
  1. package/CHANGELOG.md +33 -86
  2. package/README.md +11 -6
  3. package/agent_config/bin/postinstall.mjs +20 -89
  4. package/agent_config/bin/setup.mjs +51 -264
  5. package/agent_config/lib/install-skills.mjs +108 -0
  6. package/agent_config/{claude-code/skills → skills}/ts-procedures/anti-patterns.md +8 -0
  7. package/agent_config/{claude-code/skills → skills}/ts-procedures/api-reference.md +25 -22
  8. package/agent_config/{claude-code/skills → skills}/ts-procedures/patterns.md +24 -13
  9. package/build/adapters/hono/envelope-parity.test.js +6 -17
  10. package/build/adapters/hono/envelope-parity.test.js.map +1 -1
  11. package/build/adapters/hono/handlers/http-stream.d.ts +2 -1
  12. package/build/adapters/hono/handlers/http-stream.js +9 -2
  13. package/build/adapters/hono/handlers/http-stream.js.map +1 -1
  14. package/build/adapters/hono/handlers/http.d.ts +2 -1
  15. package/build/adapters/hono/handlers/http.js +9 -2
  16. package/build/adapters/hono/handlers/http.js.map +1 -1
  17. package/build/adapters/hono/handlers/rpc.d.ts +2 -1
  18. package/build/adapters/hono/handlers/rpc.js +9 -2
  19. package/build/adapters/hono/handlers/rpc.js.map +1 -1
  20. package/build/adapters/hono/handlers/stream.d.ts +2 -1
  21. package/build/adapters/hono/handlers/stream.js +9 -2
  22. package/build/adapters/hono/handlers/stream.js.map +1 -1
  23. package/build/adapters/hono/index.d.ts +14 -7
  24. package/build/adapters/hono/index.js +45 -5
  25. package/build/adapters/hono/index.js.map +1 -1
  26. package/build/adapters/hono/index.test.js +55 -0
  27. package/build/adapters/hono/index.test.js.map +1 -1
  28. package/build/adapters/hono/request.d.ts +7 -0
  29. package/build/adapters/hono/request.js +15 -0
  30. package/build/adapters/hono/request.js.map +1 -1
  31. package/build/adapters/hono/types.d.ts +31 -0
  32. package/build/adapters/hono/types.js.map +1 -1
  33. package/build/codegen/bin/cli.js +1 -1
  34. package/build/codegen/bin/cli.js.map +1 -1
  35. package/build/codegen/constants.js +2 -2
  36. package/build/codegen/constants.js.map +1 -1
  37. package/build/codegen/emit/api-route.js +1 -1
  38. package/build/codegen/emit/api-route.js.map +1 -1
  39. package/build/codegen/emit/scope-file.js +1 -1
  40. package/build/codegen/emit/scope-file.js.map +1 -1
  41. package/build/codegen/emit-errors.integration.test.js +2 -2
  42. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  43. package/build/codegen/resolve-envelope.js +1 -1
  44. package/build/codegen/resolve-envelope.js.map +1 -1
  45. package/build/codegen/test-helpers/run-tsc.js +1 -1
  46. package/build/codegen/test-helpers/run-tsc.js.map +1 -1
  47. package/build/core/create-http-stream.d.ts +3 -14
  48. package/build/core/create-http-stream.js +4 -2
  49. package/build/core/create-http-stream.js.map +1 -1
  50. package/build/core/create-http.d.ts +2 -9
  51. package/build/core/create-http.js +3 -2
  52. package/build/core/create-http.js.map +1 -1
  53. package/build/core/create-http.test-d.js +104 -0
  54. package/build/core/create-http.test-d.js.map +1 -0
  55. package/build/core/create-stream.js +6 -2
  56. package/build/core/create-stream.js.map +1 -1
  57. package/build/core/create.js +5 -4
  58. package/build/core/create.js.map +1 -1
  59. package/build/core/create.test.js +63 -0
  60. package/build/core/create.test.js.map +1 -1
  61. package/build/core/internal.d.ts +16 -0
  62. package/build/core/internal.js +22 -0
  63. package/build/core/internal.js.map +1 -1
  64. package/build/core/procedures.d.ts +9 -16
  65. package/build/core/procedures.js +4 -0
  66. package/build/core/procedures.js.map +1 -1
  67. package/build/core/types.d.ts +85 -4
  68. package/build/errors.d.ts +33 -0
  69. package/build/errors.js +91 -0
  70. package/build/errors.js.map +1 -0
  71. package/build/errors.test.d.ts +1 -0
  72. package/build/errors.test.js +122 -0
  73. package/build/errors.test.js.map +1 -0
  74. package/build/exports.d.ts +1 -2
  75. package/build/exports.js +0 -4
  76. package/build/exports.js.map +1 -1
  77. package/build/implementations/http/doc-registry.d.ts +28 -0
  78. package/build/implementations/http/doc-registry.js +96 -0
  79. package/build/implementations/http/doc-registry.js.map +1 -0
  80. package/build/implementations/http/doc-registry.test.d.ts +1 -0
  81. package/build/implementations/http/doc-registry.test.js +516 -0
  82. package/build/implementations/http/doc-registry.test.js.map +1 -0
  83. package/build/implementations/http/error-taxonomy.d.ts +249 -0
  84. package/build/implementations/http/error-taxonomy.js +252 -0
  85. package/build/implementations/http/error-taxonomy.js.map +1 -0
  86. package/build/implementations/http/error-taxonomy.test.d.ts +1 -0
  87. package/build/implementations/http/error-taxonomy.test.js +399 -0
  88. package/build/implementations/http/error-taxonomy.test.js.map +1 -0
  89. package/build/implementations/http/express-rpc/error-taxonomy.test.d.ts +1 -0
  90. package/build/implementations/http/express-rpc/error-taxonomy.test.js +83 -0
  91. package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +1 -0
  92. package/build/implementations/http/express-rpc/index.d.ts +125 -0
  93. package/build/implementations/http/express-rpc/index.js +216 -0
  94. package/build/implementations/http/express-rpc/index.js.map +1 -0
  95. package/build/implementations/http/express-rpc/index.test.d.ts +1 -0
  96. package/build/implementations/http/express-rpc/index.test.js +684 -0
  97. package/build/implementations/http/express-rpc/index.test.js.map +1 -0
  98. package/build/implementations/http/express-rpc/types.d.ts +11 -0
  99. package/build/implementations/http/express-rpc/types.js +2 -0
  100. package/build/implementations/http/express-rpc/types.js.map +1 -0
  101. package/build/implementations/http/hono-api/error-taxonomy.test.d.ts +1 -0
  102. package/build/implementations/http/hono-api/error-taxonomy.test.js +137 -0
  103. package/build/implementations/http/hono-api/error-taxonomy.test.js.map +1 -0
  104. package/build/implementations/http/hono-api/index.d.ts +151 -0
  105. package/build/implementations/http/hono-api/index.js +344 -0
  106. package/build/implementations/http/hono-api/index.js.map +1 -0
  107. package/build/implementations/http/hono-api/index.test.d.ts +1 -0
  108. package/build/implementations/http/hono-api/index.test.js +992 -0
  109. package/build/implementations/http/hono-api/index.test.js.map +1 -0
  110. package/build/implementations/http/hono-api/types.d.ts +13 -0
  111. package/build/implementations/http/hono-api/types.js +2 -0
  112. package/build/implementations/http/hono-api/types.js.map +1 -0
  113. package/build/implementations/http/hono-rpc/error-taxonomy.test.d.ts +1 -0
  114. package/build/implementations/http/hono-rpc/error-taxonomy.test.js +64 -0
  115. package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +1 -0
  116. package/build/implementations/http/hono-rpc/index.d.ts +130 -0
  117. package/build/implementations/http/hono-rpc/index.js +209 -0
  118. package/build/implementations/http/hono-rpc/index.js.map +1 -0
  119. package/build/implementations/http/hono-rpc/index.test.d.ts +1 -0
  120. package/build/implementations/http/hono-rpc/index.test.js +828 -0
  121. package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
  122. package/build/implementations/http/hono-rpc/types.d.ts +11 -0
  123. package/build/implementations/http/hono-rpc/types.js +2 -0
  124. package/build/implementations/http/hono-rpc/types.js.map +1 -0
  125. package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
  126. package/build/implementations/http/hono-stream/error-taxonomy.test.js +159 -0
  127. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
  128. package/build/implementations/http/hono-stream/index.d.ts +171 -0
  129. package/build/implementations/http/hono-stream/index.js +415 -0
  130. package/build/implementations/http/hono-stream/index.js.map +1 -0
  131. package/build/implementations/http/hono-stream/index.test.d.ts +1 -0
  132. package/build/implementations/http/hono-stream/index.test.js +1383 -0
  133. package/build/implementations/http/hono-stream/index.test.js.map +1 -0
  134. package/build/implementations/http/hono-stream/types.d.ts +15 -0
  135. package/build/implementations/http/hono-stream/types.js +2 -0
  136. package/build/implementations/http/hono-stream/types.js.map +1 -0
  137. package/build/implementations/http/on-request-error.test.d.ts +1 -0
  138. package/build/implementations/http/on-request-error.test.js +173 -0
  139. package/build/implementations/http/on-request-error.test.js.map +1 -0
  140. package/build/implementations/http/route-errors.test.d.ts +1 -0
  141. package/build/implementations/http/route-errors.test.js +139 -0
  142. package/build/implementations/http/route-errors.test.js.map +1 -0
  143. package/build/implementations/types.d.ts +192 -0
  144. package/build/implementations/types.js +2 -0
  145. package/build/implementations/types.js.map +1 -0
  146. package/build/index.d.ts +165 -0
  147. package/build/index.js +258 -0
  148. package/build/index.js.map +1 -0
  149. package/build/index.test.d.ts +1 -0
  150. package/build/index.test.js +920 -0
  151. package/build/index.test.js.map +1 -0
  152. package/build/schema/compute-schema.js +1 -5
  153. package/build/schema/compute-schema.js.map +1 -1
  154. package/build/schema/compute-schema.test.js +0 -13
  155. package/build/schema/compute-schema.test.js.map +1 -1
  156. package/build/schema/extract-json-schema.d.ts +2 -0
  157. package/build/schema/extract-json-schema.js +12 -0
  158. package/build/schema/extract-json-schema.js.map +1 -0
  159. package/build/schema/extract-json-schema.test.d.ts +1 -0
  160. package/build/schema/extract-json-schema.test.js +23 -0
  161. package/build/schema/extract-json-schema.test.js.map +1 -0
  162. package/build/schema/parser.d.ts +28 -0
  163. package/build/schema/parser.js +170 -0
  164. package/build/schema/parser.js.map +1 -0
  165. package/build/schema/parser.test.d.ts +1 -0
  166. package/build/schema/parser.test.js +120 -0
  167. package/build/schema/parser.test.js.map +1 -0
  168. package/build/schema/resolve-schema-lib.d.ts +12 -0
  169. package/build/schema/resolve-schema-lib.js +11 -0
  170. package/build/schema/resolve-schema-lib.js.map +1 -0
  171. package/build/schema/resolve-schema-lib.test.d.ts +1 -0
  172. package/build/schema/resolve-schema-lib.test.js +17 -0
  173. package/build/schema/resolve-schema-lib.test.js.map +1 -0
  174. package/build/schema/typebox.d.ts +1 -1
  175. package/build/schema/typebox.js +2 -20
  176. package/build/schema/typebox.js.map +1 -1
  177. package/build/schema/typebox.test.js +0 -10
  178. package/build/schema/typebox.test.js.map +1 -1
  179. package/build/schema/types.d.ts +8 -0
  180. package/build/schema/types.js +2 -0
  181. package/build/schema/types.js.map +1 -0
  182. package/build/server/context.d.ts +12 -2
  183. package/build/server/context.js +49 -4
  184. package/build/server/context.js.map +1 -1
  185. package/build/server/context.test.js +32 -1
  186. package/build/server/context.test.js.map +1 -1
  187. package/build/server/doc-registry.js +0 -3
  188. package/build/server/doc-registry.js.map +1 -1
  189. package/build/server/doc-registry.test.js +0 -31
  190. package/build/server/doc-registry.test.js.map +1 -1
  191. package/build/server/index.d.ts +0 -1
  192. package/build/server/index.js +0 -1
  193. package/build/server/index.js.map +1 -1
  194. package/build/server/types.d.ts +6 -27
  195. package/build/stack-utils.d.ts +25 -0
  196. package/build/stack-utils.js +95 -0
  197. package/build/stack-utils.js.map +1 -0
  198. package/build/stack-utils.test.d.ts +1 -0
  199. package/build/stack-utils.test.js +80 -0
  200. package/build/stack-utils.test.js.map +1 -0
  201. package/docs/ai-agent-setup.md +19 -27
  202. package/docs/client-error-handling.md +1 -1
  203. package/docs/core.md +8 -0
  204. package/docs/http-integrations.md +42 -28
  205. package/package.json +8 -8
  206. package/src/adapters/astro/README.md +2 -0
  207. package/src/adapters/hono/envelope-parity.test.ts +6 -18
  208. package/src/adapters/hono/handlers/http-stream.ts +12 -3
  209. package/src/adapters/hono/handlers/http.ts +12 -3
  210. package/src/adapters/hono/handlers/rpc.ts +12 -3
  211. package/src/adapters/hono/handlers/stream.ts +12 -3
  212. package/src/adapters/hono/index.test.ts +70 -0
  213. package/src/adapters/hono/index.ts +53 -11
  214. package/src/adapters/hono/request.ts +18 -0
  215. package/src/adapters/hono/types.ts +37 -0
  216. package/src/codegen/bin/cli.ts +1 -1
  217. package/src/codegen/constants.ts +2 -2
  218. package/src/codegen/emit/api-route.ts +1 -1
  219. package/src/codegen/emit/scope-file.ts +1 -2
  220. package/src/codegen/emit-errors.integration.test.ts +2 -2
  221. package/src/codegen/resolve-envelope.ts +1 -1
  222. package/src/codegen/test-helpers/run-tsc.ts +0 -1
  223. package/src/core/create-http-stream.ts +20 -13
  224. package/src/core/create-http.test-d.ts +154 -0
  225. package/src/core/create-http.ts +19 -8
  226. package/src/core/create-stream.ts +12 -4
  227. package/src/core/create.test.ts +74 -0
  228. package/src/core/create.ts +10 -5
  229. package/src/core/internal.ts +35 -0
  230. package/src/core/procedures.ts +4 -0
  231. package/src/core/types.ts +99 -4
  232. package/src/exports.ts +6 -4
  233. package/src/schema/compute-schema.test.ts +0 -16
  234. package/src/schema/compute-schema.ts +1 -5
  235. package/src/schema/typebox.test.ts +0 -12
  236. package/src/schema/typebox.ts +2 -21
  237. package/src/server/context.test.ts +39 -1
  238. package/src/server/context.ts +54 -4
  239. package/src/server/doc-registry.test.ts +0 -34
  240. package/src/server/doc-registry.ts +0 -3
  241. package/src/server/index.ts +0 -1
  242. package/src/server/types.ts +8 -27
  243. package/agent_config/claude-code/.claude-plugin/plugin.json +0 -5
  244. package/agent_config/claude-code/agents/ts-procedures-architect.md +0 -138
  245. package/agent_config/copilot/copilot-instructions.md +0 -521
  246. package/agent_config/cursor/cursorrules +0 -521
  247. package/agent_config/lib/install-claude.mjs +0 -57
  248. package/build/schema/strip-internal-keywords.d.ts +0 -18
  249. package/build/schema/strip-internal-keywords.js +0 -33
  250. package/build/schema/strip-internal-keywords.js.map +0 -1
  251. package/build/schema/strip-internal-keywords.test.js +0 -38
  252. package/build/schema/strip-internal-keywords.test.js.map +0 -1
  253. package/build/server/spec-version.d.ts +0 -24
  254. package/build/server/spec-version.js +0 -26
  255. package/build/server/spec-version.js.map +0 -1
  256. package/build/version.d.ts +0 -9
  257. package/build/version.js +0 -11
  258. package/build/version.js.map +0 -1
  259. package/docs/doc-envelope-spec-v1.md +0 -296
  260. package/docs/doc-envelope-v1.schema.json +0 -253
  261. package/src/schema/strip-internal-keywords.test.ts +0 -43
  262. package/src/schema/strip-internal-keywords.ts +0 -36
  263. package/src/server/spec-version.ts +0 -27
  264. package/src/version.ts +0 -11
  265. /package/agent_config/{claude-code/skills → skills}/ts-procedures/SKILL.md +0 -0
  266. /package/agent_config/{claude-code/skills → skills}/ts-procedures/checklist.md +0 -0
  267. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/astro-catchall.md +0 -0
  268. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/client.md +0 -0
  269. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/hono.md +0 -0
  270. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/procedure.md +0 -0
  271. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/stream-procedure.md +0 -0
  272. /package/build/{schema/strip-internal-keywords.test.d.ts → core/create-http.test-d.d.ts} +0 -0
@@ -0,0 +1,96 @@
1
+ import { PROCEDURE_REGISTRATION_ERROR_DOC, defaultErrorTaxonomy, taxonomyToErrorDocs, } from './error-taxonomy.js';
2
+ function isTaxonomy(input) {
3
+ return !Array.isArray(input);
4
+ }
5
+ /**
6
+ * Dedupes ErrorDocs by `name`, last occurrence wins. Map insertion order
7
+ * preserves the latest position of each name.
8
+ */
9
+ function dedupeByName(docs) {
10
+ const byName = new Map();
11
+ for (const doc of docs)
12
+ byName.set(doc.name, doc);
13
+ return Array.from(byName.values());
14
+ }
15
+ export class DocRegistry {
16
+ basePath;
17
+ headers;
18
+ errors;
19
+ sources = [];
20
+ constructor(config) {
21
+ this.basePath = config?.basePath ?? '';
22
+ this.headers = config?.headers ?? [];
23
+ const includeDefaults = config?.includeDefaults ?? true;
24
+ const userErrors = config?.errors
25
+ ? isTaxonomy(config.errors)
26
+ ? taxonomyToErrorDocs(config.errors)
27
+ : config.errors
28
+ : [];
29
+ // Precedence: defaults come first, user errors override via dedupe
30
+ // (last-write-wins). Matches runtime resolution order.
31
+ const merged = includeDefaults
32
+ ? [...DocRegistry.defaultErrors(), ...userErrors]
33
+ : userErrors;
34
+ this.errors = dedupeByName(merged);
35
+ }
36
+ from(source) {
37
+ this.sources.push(source);
38
+ return this;
39
+ }
40
+ /**
41
+ * Adds one or more {@link ErrorDoc} entries to the envelope. Use for errors
42
+ * outside your runtime taxonomy — middleware-level errors, infrastructure
43
+ * errors (502/503/504), or doc-only meta errors.
44
+ *
45
+ * Deduped by `name` — last write wins.
46
+ */
47
+ documentError(...docs) {
48
+ this.errors = dedupeByName([...this.errors, ...docs]);
49
+ return this;
50
+ }
51
+ toJSON(options) {
52
+ let routes = this.sources.flatMap((source) => source.docs);
53
+ if (options?.filter) {
54
+ routes = routes.filter(options.filter);
55
+ }
56
+ // Surface coverage gaps — when a builder skipped procedures (e.g. a
57
+ // streaming procedure registered with HonoRPCAppBuilder), the per-builder
58
+ // warning fires once at `build()` time, but consumers who only call
59
+ // `registry.toJSON()` (e.g. to dump the envelope to disk for codegen)
60
+ // wouldn't see it without this aggregate. Single warning per call,
61
+ // suppressed when the env var is set so CI pipelines stay quiet.
62
+ if (process.env.TS_PROCEDURES_SUPPRESS_COVERAGE_WARNINGS !== '1') {
63
+ const skipped = this.sources.flatMap((source) => source.skippedProcedures ?? []);
64
+ if (skipped.length > 0) {
65
+ const lines = skipped.map(({ name, reason }) => ` - "${name}": ${reason}`);
66
+ console.warn(`[ts-procedures DocRegistry] ${skipped.length} procedure(s) registered with a builder but not served — they will be missing from the doc envelope:\n${lines.join('\n')}\n` +
67
+ `(Suppress with TS_PROCEDURES_SUPPRESS_COVERAGE_WARNINGS=1)`);
68
+ }
69
+ }
70
+ const envelope = {
71
+ basePath: this.basePath,
72
+ headers: [...this.headers],
73
+ errors: [...this.errors],
74
+ routes,
75
+ };
76
+ if (options?.transform) {
77
+ return options.transform(envelope);
78
+ }
79
+ return envelope;
80
+ }
81
+ /**
82
+ * Framework error defaults — derived from {@link defaultErrorTaxonomy} plus
83
+ * `ProcedureRegistrationError` (which is thrown only at registration time
84
+ * and therefore lives in the catalog, not the runtime taxonomy).
85
+ *
86
+ * Most consumers do not need to call this directly — the `DocRegistry`
87
+ * constructor auto-includes these unless `includeDefaults: false` is passed.
88
+ */
89
+ static defaultErrors() {
90
+ return [
91
+ ...taxonomyToErrorDocs(defaultErrorTaxonomy),
92
+ PROCEDURE_REGISTRATION_ERROR_DOC,
93
+ ];
94
+ }
95
+ }
96
+ //# sourceMappingURL=doc-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-registry.js","sourceRoot":"","sources":["../../../src/implementations/http/doc-registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,516 @@
1
+ import { describe, expect, it, test, vi, afterEach } from 'vitest';
2
+ import { v } from 'suretype';
3
+ import { Procedures } from '../../index.js';
4
+ import { HonoRPCAppBuilder } from './hono-rpc/index.js';
5
+ import { DocRegistry } from './doc-registry.js';
6
+ import { defineErrorTaxonomy } from './error-taxonomy.js';
7
+ // ---------------------------------------------------------------------------
8
+ // Helpers — minimal doc fixtures
9
+ // ---------------------------------------------------------------------------
10
+ const rpcDoc = {
11
+ kind: 'rpc',
12
+ name: 'Echo',
13
+ path: '/echo/1',
14
+ method: 'post',
15
+ scope: 'echo',
16
+ version: 1,
17
+ jsonSchema: { body: { type: 'object' } },
18
+ };
19
+ const apiDoc = {
20
+ kind: 'api',
21
+ name: 'GetUser',
22
+ path: '/users/:id',
23
+ method: 'get',
24
+ fullPath: '/api/users/:id',
25
+ jsonSchema: { pathParams: { type: 'object' } },
26
+ };
27
+ const streamDoc = {
28
+ kind: 'stream',
29
+ name: 'Feed',
30
+ path: '/feed/1',
31
+ methods: ['get'],
32
+ streamMode: 'sse',
33
+ scope: 'feed',
34
+ version: 1,
35
+ jsonSchema: { yieldType: { type: 'object' } },
36
+ };
37
+ function makeSource(docs) {
38
+ return { docs };
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // Tests
42
+ // ---------------------------------------------------------------------------
43
+ describe('DocRegistry', () => {
44
+ // --------------------------------------------------------------------------
45
+ // Constructor
46
+ // --------------------------------------------------------------------------
47
+ describe('constructor', () => {
48
+ test('uses defaults when no config provided', () => {
49
+ const registry = new DocRegistry({ includeDefaults: false });
50
+ const out = registry.toJSON();
51
+ expect(out.basePath).toBe('');
52
+ expect(out.headers).toEqual([]);
53
+ expect(out.errors).toEqual([]);
54
+ });
55
+ test('accepts partial config', () => {
56
+ const registry = new DocRegistry({ basePath: '/v1', includeDefaults: false });
57
+ const out = registry.toJSON();
58
+ expect(out.basePath).toBe('/v1');
59
+ expect(out.headers).toEqual([]);
60
+ expect(out.errors).toEqual([]);
61
+ });
62
+ test('accepts full config', () => {
63
+ const headers = [{ name: 'Authorization', description: 'Bearer token', required: true }];
64
+ const errors = [{ name: 'Unauthorized', statusCode: 401, description: 'Missing token' }];
65
+ const registry = new DocRegistry({ basePath: '/api', headers, errors, includeDefaults: false });
66
+ const out = registry.toJSON();
67
+ expect(out.basePath).toBe('/api');
68
+ expect(out.headers).toEqual(headers);
69
+ expect(out.errors).toEqual(errors);
70
+ });
71
+ });
72
+ // --------------------------------------------------------------------------
73
+ // from()
74
+ // --------------------------------------------------------------------------
75
+ describe('from()', () => {
76
+ test('returns this for chaining', () => {
77
+ const registry = new DocRegistry();
78
+ const result = registry.from(makeSource([rpcDoc]));
79
+ expect(result).toBe(registry);
80
+ });
81
+ test('accepts RPC builder source', () => {
82
+ const registry = new DocRegistry().from(makeSource([rpcDoc]));
83
+ expect(registry.toJSON().routes).toEqual([rpcDoc]);
84
+ });
85
+ test('accepts API builder source', () => {
86
+ const registry = new DocRegistry().from(makeSource([apiDoc]));
87
+ expect(registry.toJSON().routes).toEqual([apiDoc]);
88
+ });
89
+ test('accepts Stream builder source', () => {
90
+ const registry = new DocRegistry().from(makeSource([streamDoc]));
91
+ expect(registry.toJSON().routes).toEqual([streamDoc]);
92
+ });
93
+ test('accepts plain { docs } object', () => {
94
+ const plain = { docs: [rpcDoc, apiDoc] };
95
+ const registry = new DocRegistry().from(plain);
96
+ expect(registry.toJSON().routes).toHaveLength(2);
97
+ });
98
+ test('builder not yet built returns empty routes', () => {
99
+ const emptySource = makeSource([]);
100
+ const registry = new DocRegistry().from(emptySource);
101
+ expect(registry.toJSON().routes).toEqual([]);
102
+ });
103
+ test('same builder twice duplicates routes', () => {
104
+ const source = makeSource([rpcDoc]);
105
+ const registry = new DocRegistry().from(source).from(source);
106
+ expect(registry.toJSON().routes).toHaveLength(2);
107
+ expect(registry.toJSON().routes[0]).toEqual(registry.toJSON().routes[1]);
108
+ });
109
+ test('reads docs lazily at toJSON() time', () => {
110
+ const mutableDocs = [];
111
+ const source = { get docs() { return mutableDocs; } };
112
+ const registry = new DocRegistry().from(source);
113
+ expect(registry.toJSON().routes).toHaveLength(0);
114
+ mutableDocs.push(rpcDoc);
115
+ expect(registry.toJSON().routes).toHaveLength(1);
116
+ });
117
+ });
118
+ // --------------------------------------------------------------------------
119
+ // toJSON()
120
+ // --------------------------------------------------------------------------
121
+ describe('toJSON()', () => {
122
+ test('returns correct DocEnvelope shape', () => {
123
+ const registry = new DocRegistry({ basePath: '/api' });
124
+ const out = registry.toJSON();
125
+ expect(out).toHaveProperty('basePath');
126
+ expect(out).toHaveProperty('headers');
127
+ expect(out).toHaveProperty('errors');
128
+ expect(out).toHaveProperty('routes');
129
+ });
130
+ test('collects routes from all sources', () => {
131
+ const registry = new DocRegistry()
132
+ .from(makeSource([rpcDoc]))
133
+ .from(makeSource([apiDoc]))
134
+ .from(makeSource([streamDoc]));
135
+ const out = registry.toJSON();
136
+ expect(out.routes).toHaveLength(3);
137
+ expect(out.routes).toEqual([rpcDoc, apiDoc, streamDoc]);
138
+ });
139
+ test('empty when no sources', () => {
140
+ const out = new DocRegistry().toJSON();
141
+ expect(out.routes).toEqual([]);
142
+ });
143
+ test('headers and errors are copies', () => {
144
+ const headers = [{ name: 'X-Custom' }];
145
+ const errors = [{ name: 'E', statusCode: 500, description: 'd' }];
146
+ const registry = new DocRegistry({ headers, errors, includeDefaults: false });
147
+ const out = registry.toJSON();
148
+ expect(out.headers).toEqual(headers);
149
+ expect(out.headers).not.toBe(headers);
150
+ expect(out.errors).toEqual(errors);
151
+ expect(out.errors).not.toBe(errors);
152
+ });
153
+ });
154
+ // --------------------------------------------------------------------------
155
+ // toJSON() filter
156
+ // --------------------------------------------------------------------------
157
+ describe('toJSON() filter', () => {
158
+ test('excludes routes by name', () => {
159
+ const registry = new DocRegistry()
160
+ .from(makeSource([rpcDoc]))
161
+ .from(makeSource([apiDoc]));
162
+ const out = registry.toJSON({ filter: (r) => r.name !== 'Echo' });
163
+ expect(out.routes).toHaveLength(1);
164
+ expect(out.routes[0].name).toBe('GetUser');
165
+ });
166
+ test('excludes routes by type field', () => {
167
+ const registry = new DocRegistry()
168
+ .from(makeSource([rpcDoc]))
169
+ .from(makeSource([apiDoc]));
170
+ const out = registry.toJSON({
171
+ filter: (r) => 'method' in r && r.method === 'post' && 'scope' in r,
172
+ });
173
+ expect(out.routes).toHaveLength(1);
174
+ expect(out.routes[0].name).toBe('Echo');
175
+ });
176
+ test('all filtered returns empty routes array', () => {
177
+ const registry = new DocRegistry().from(makeSource([rpcDoc]));
178
+ const out = registry.toJSON({ filter: () => false });
179
+ expect(out.routes).toEqual([]);
180
+ });
181
+ });
182
+ // --------------------------------------------------------------------------
183
+ // toJSON() transform
184
+ // --------------------------------------------------------------------------
185
+ describe('toJSON() transform', () => {
186
+ test('adds fields', () => {
187
+ const registry = new DocRegistry({ basePath: '/api' }).from(makeSource([rpcDoc]));
188
+ const out = registry.toJSON({
189
+ transform: (envelope) => ({ ...envelope, version: '2.0' }),
190
+ });
191
+ expect(out.version).toBe('2.0');
192
+ expect(out.basePath).toBe('/api');
193
+ });
194
+ test('reshapes envelope', () => {
195
+ const registry = new DocRegistry().from(makeSource([rpcDoc]));
196
+ const out = registry.toJSON({
197
+ transform: (envelope) => ({ count: envelope.routes.length }),
198
+ });
199
+ expect(out).toEqual({ count: 1 });
200
+ });
201
+ test('runs after filter', () => {
202
+ const registry = new DocRegistry()
203
+ .from(makeSource([rpcDoc]))
204
+ .from(makeSource([apiDoc]));
205
+ const out = registry.toJSON({
206
+ filter: (r) => r.name === 'Echo',
207
+ transform: (envelope) => ({ ...envelope, filteredCount: envelope.routes.length }),
208
+ });
209
+ expect(out.filteredCount).toBe(1);
210
+ expect(out.routes).toHaveLength(1);
211
+ });
212
+ });
213
+ // --------------------------------------------------------------------------
214
+ // defaultErrors()
215
+ // --------------------------------------------------------------------------
216
+ describe('defaultErrors()', () => {
217
+ test('returns 4 entries', () => {
218
+ const errors = DocRegistry.defaultErrors();
219
+ expect(errors).toHaveLength(4);
220
+ });
221
+ test('has correct error names', () => {
222
+ const names = DocRegistry.defaultErrors().map((e) => e.name);
223
+ // Runtime errors come from the taxonomy (topologically sorted —
224
+ // subclasses first), followed by the doc-only registration error.
225
+ expect(names).toEqual([
226
+ 'ProcedureValidationError',
227
+ 'ProcedureYieldValidationError',
228
+ 'ProcedureError',
229
+ 'ProcedureRegistrationError',
230
+ ]);
231
+ });
232
+ test('has correct status codes', () => {
233
+ const byName = Object.fromEntries(DocRegistry.defaultErrors().map((e) => [e.name, e.statusCode]));
234
+ expect(byName.ProcedureError).toBe(500);
235
+ expect(byName.ProcedureValidationError).toBe(400);
236
+ expect(byName.ProcedureYieldValidationError).toBe(500);
237
+ expect(byName.ProcedureRegistrationError).toBe(500);
238
+ });
239
+ test('each entry has schema', () => {
240
+ for (const err of DocRegistry.defaultErrors()) {
241
+ expect(err.schema).toBeDefined();
242
+ expect(err.schema.type).toBe('object');
243
+ }
244
+ });
245
+ test('returns a new array each call', () => {
246
+ const a = DocRegistry.defaultErrors();
247
+ const b = DocRegistry.defaultErrors();
248
+ expect(a).not.toBe(b);
249
+ expect(a).toEqual(b);
250
+ });
251
+ });
252
+ // --------------------------------------------------------------------------
253
+ // taxonomy input + auto-defaults + dedupe (v6.0.1 simplification)
254
+ // --------------------------------------------------------------------------
255
+ describe('errors: ErrorTaxonomy (polymorphic constructor input)', () => {
256
+ test('accepts an ErrorTaxonomy directly and converts to ErrorDoc[]', () => {
257
+ const taxonomy = defineErrorTaxonomy({
258
+ AuthError: {
259
+ class: class AuthError extends Error {
260
+ },
261
+ statusCode: 401,
262
+ description: 'unauthenticated',
263
+ },
264
+ });
265
+ const envelope = new DocRegistry({ errors: taxonomy }).toJSON();
266
+ const auth = envelope.errors.find((e) => e.name === 'AuthError');
267
+ expect(auth).toBeDefined();
268
+ expect(auth?.statusCode).toBe(401);
269
+ expect(auth?.description).toBe('unauthenticated');
270
+ });
271
+ test('auto-includes framework defaults when errors is a taxonomy', () => {
272
+ const taxonomy = defineErrorTaxonomy({
273
+ AuthError: { class: class AuthError extends Error {
274
+ }, statusCode: 401 },
275
+ });
276
+ const names = new DocRegistry({ errors: taxonomy }).toJSON().errors.map((e) => e.name);
277
+ expect(names).toContain('AuthError');
278
+ expect(names).toContain('ProcedureValidationError');
279
+ expect(names).toContain('ProcedureYieldValidationError');
280
+ expect(names).toContain('ProcedureError');
281
+ expect(names).toContain('ProcedureRegistrationError');
282
+ });
283
+ test('auto-includes framework defaults when errors is ErrorDoc[]', () => {
284
+ const custom = { name: 'CustomThing', statusCode: 418, description: 'teapot' };
285
+ const names = new DocRegistry({ errors: [custom] }).toJSON().errors.map((e) => e.name);
286
+ expect(names).toContain('CustomThing');
287
+ expect(names).toContain('ProcedureValidationError');
288
+ });
289
+ test('includeDefaults: false omits framework defaults', () => {
290
+ const taxonomy = defineErrorTaxonomy({
291
+ AuthError: { class: class AuthError extends Error {
292
+ }, statusCode: 401 },
293
+ });
294
+ const names = new DocRegistry({ errors: taxonomy, includeDefaults: false })
295
+ .toJSON()
296
+ .errors.map((e) => e.name);
297
+ expect(names).toEqual(['AuthError']);
298
+ });
299
+ test('user taxonomy entry with same name as default overrides default (dedupe, user wins)', () => {
300
+ const taxonomy = defineErrorTaxonomy({
301
+ ProcedureError: {
302
+ class: Error,
303
+ statusCode: 418,
304
+ description: 'custom override',
305
+ },
306
+ });
307
+ const envelope = new DocRegistry({ errors: taxonomy }).toJSON();
308
+ const proc = envelope.errors.filter((e) => e.name === 'ProcedureError');
309
+ expect(proc).toHaveLength(1);
310
+ expect(proc[0].statusCode).toBe(418);
311
+ expect(proc[0].description).toBe('custom override');
312
+ });
313
+ test('user ErrorDoc with same name as default overrides default (dedupe, user wins)', () => {
314
+ const custom = {
315
+ name: 'ProcedureError',
316
+ statusCode: 418,
317
+ description: 'custom override',
318
+ };
319
+ const envelope = new DocRegistry({ errors: [custom] }).toJSON();
320
+ const proc = envelope.errors.filter((e) => e.name === 'ProcedureError');
321
+ expect(proc).toHaveLength(1);
322
+ expect(proc[0].statusCode).toBe(418);
323
+ expect(proc[0].description).toBe('custom override');
324
+ });
325
+ test('empty errors config still returns framework defaults', () => {
326
+ const names = new DocRegistry().toJSON().errors.map((e) => e.name);
327
+ expect(names).toContain('ProcedureError');
328
+ expect(names).toContain('ProcedureValidationError');
329
+ expect(names).toContain('ProcedureYieldValidationError');
330
+ expect(names).toContain('ProcedureRegistrationError');
331
+ });
332
+ test('includeDefaults: false with no errors produces empty error list', () => {
333
+ const envelope = new DocRegistry({ includeDefaults: false }).toJSON();
334
+ expect(envelope.errors).toEqual([]);
335
+ });
336
+ });
337
+ // --------------------------------------------------------------------------
338
+ // .documentError() fluent extension
339
+ // --------------------------------------------------------------------------
340
+ describe('.documentError()', () => {
341
+ test('adds a single ErrorDoc to the envelope', () => {
342
+ const registry = new DocRegistry({ includeDefaults: false }).documentError({
343
+ name: 'RateLimitExceeded',
344
+ statusCode: 429,
345
+ description: 'too many requests',
346
+ });
347
+ const names = registry.toJSON().errors.map((e) => e.name);
348
+ expect(names).toEqual(['RateLimitExceeded']);
349
+ });
350
+ test('accepts multiple docs via variadic args', () => {
351
+ const registry = new DocRegistry({ includeDefaults: false }).documentError({ name: 'A', statusCode: 400, description: 'a' }, { name: 'B', statusCode: 500, description: 'b' });
352
+ const names = registry.toJSON().errors.map((e) => e.name);
353
+ expect(names).toEqual(['A', 'B']);
354
+ });
355
+ test('returns this for chaining', () => {
356
+ const registry = new DocRegistry();
357
+ const returned = registry.documentError({
358
+ name: 'Foo',
359
+ statusCode: 500,
360
+ description: 'x',
361
+ });
362
+ expect(returned).toBe(registry);
363
+ });
364
+ test('composes with taxonomy input — extends docs without replacing', () => {
365
+ const taxonomy = defineErrorTaxonomy({
366
+ AuthError: { class: class AuthError extends Error {
367
+ }, statusCode: 401 },
368
+ });
369
+ const envelope = new DocRegistry({ errors: taxonomy })
370
+ .documentError({ name: 'RateLimitExceeded', statusCode: 429, description: 'x' })
371
+ .toJSON();
372
+ const names = envelope.errors.map((e) => e.name);
373
+ expect(names).toContain('AuthError');
374
+ expect(names).toContain('RateLimitExceeded');
375
+ expect(names).toContain('ProcedureValidationError');
376
+ });
377
+ test('dedupes against existing errors (last write wins)', () => {
378
+ const registry = new DocRegistry({ includeDefaults: false })
379
+ .documentError({ name: 'Foo', statusCode: 400, description: 'first' })
380
+ .documentError({ name: 'Foo', statusCode: 500, description: 'second' });
381
+ const foo = registry.toJSON().errors.filter((e) => e.name === 'Foo');
382
+ expect(foo).toHaveLength(1);
383
+ expect(foo[0].statusCode).toBe(500);
384
+ expect(foo[0].description).toBe('second');
385
+ });
386
+ });
387
+ // --------------------------------------------------------------------------
388
+ // kind discriminant
389
+ // --------------------------------------------------------------------------
390
+ describe('kind discriminant', () => {
391
+ it('rpcDoc fixture requires kind field', () => {
392
+ const doc = rpcDoc;
393
+ expect(doc.kind).toBe('rpc');
394
+ });
395
+ it('apiDoc fixture requires kind field', () => {
396
+ const doc = apiDoc;
397
+ expect(doc.kind).toBe('api');
398
+ });
399
+ it('streamDoc fixture requires kind field', () => {
400
+ const doc = streamDoc;
401
+ expect(doc.kind).toBe('stream');
402
+ });
403
+ it('narrows AnyHttpRouteDoc union via kind', () => {
404
+ const routes = [rpcDoc, apiDoc, streamDoc];
405
+ for (const route of routes) {
406
+ expect(route.kind).toBeDefined();
407
+ }
408
+ });
409
+ });
410
+ // --------------------------------------------------------------------------
411
+ // Integration
412
+ // --------------------------------------------------------------------------
413
+ describe('integration', () => {
414
+ test('multiple builder types composed', () => {
415
+ const registry = new DocRegistry({
416
+ basePath: '/api',
417
+ headers: [{ name: 'Authorization', description: 'Bearer token', required: false }],
418
+ })
419
+ .from(makeSource([rpcDoc]))
420
+ .from(makeSource([apiDoc]))
421
+ .from(makeSource([streamDoc]));
422
+ const out = registry.toJSON();
423
+ expect(out.basePath).toBe('/api');
424
+ expect(out.headers).toHaveLength(1);
425
+ expect(out.errors).toHaveLength(4);
426
+ expect(out.routes).toHaveLength(3);
427
+ });
428
+ test('filter + transform combined', () => {
429
+ const registry = new DocRegistry({ basePath: '/api' })
430
+ .from(makeSource([rpcDoc]))
431
+ .from(makeSource([apiDoc]))
432
+ .from(makeSource([streamDoc]));
433
+ const out = registry.toJSON({
434
+ filter: (r) => r.name !== 'Feed',
435
+ transform: (envelope) => ({
436
+ ...envelope,
437
+ generatedAt: '2026-01-01',
438
+ }),
439
+ });
440
+ expect(out.routes).toHaveLength(2);
441
+ expect(out.routes.map((r) => r.name)).toEqual(['Echo', 'GetUser']);
442
+ expect(out.generatedAt).toBe('2026-01-01');
443
+ });
444
+ test('output is JSON.stringify-safe', () => {
445
+ const registry = new DocRegistry({
446
+ basePath: '/api',
447
+ headers: [{ name: 'X-Request-Id', example: 'abc-123' }],
448
+ })
449
+ .from(makeSource([rpcDoc]))
450
+ .from(makeSource([apiDoc]));
451
+ const out = registry.toJSON();
452
+ const str = JSON.stringify(out);
453
+ expect(() => JSON.parse(str)).not.toThrow();
454
+ expect(JSON.parse(str)).toEqual(out);
455
+ });
456
+ test('JSON.stringify(registry) produces default envelope', () => {
457
+ const registry = new DocRegistry({ basePath: '/api' })
458
+ .from(makeSource([rpcDoc]));
459
+ const str = JSON.stringify(registry);
460
+ const parsed = JSON.parse(str);
461
+ expect(parsed.basePath).toBe('/api');
462
+ expect(parsed.routes).toHaveLength(1);
463
+ expect(parsed.routes[0].name).toBe('Echo');
464
+ });
465
+ describe('coverage warnings via skippedProcedures', () => {
466
+ afterEach(() => {
467
+ vi.restoreAllMocks();
468
+ delete process.env.TS_PROCEDURES_SUPPRESS_COVERAGE_WARNINGS;
469
+ });
470
+ test('warns when sources expose skippedProcedures entries', () => {
471
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
472
+ const sourceWithSkipped = {
473
+ docs: [rpcDoc],
474
+ skippedProcedures: [
475
+ { name: 'StreamProc', reason: 'wrong builder' },
476
+ ],
477
+ };
478
+ new DocRegistry().from(sourceWithSkipped).toJSON();
479
+ expect(warnSpy).toHaveBeenCalledOnce();
480
+ const message = warnSpy.mock.calls[0][0];
481
+ expect(message).toContain('1 procedure(s)');
482
+ expect(message).toContain('StreamProc');
483
+ expect(message).toContain('wrong builder');
484
+ });
485
+ test('does not warn when sources have no skippedProcedures', () => {
486
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
487
+ new DocRegistry().from(makeSource([rpcDoc])).toJSON();
488
+ expect(warnSpy).not.toHaveBeenCalled();
489
+ });
490
+ test('TS_PROCEDURES_SUPPRESS_COVERAGE_WARNINGS=1 silences the warning', () => {
491
+ process.env.TS_PROCEDURES_SUPPRESS_COVERAGE_WARNINGS = '1';
492
+ const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
493
+ const source = {
494
+ docs: [rpcDoc],
495
+ skippedProcedures: [{ name: 'X', reason: 'y' }],
496
+ };
497
+ new DocRegistry().from(source).toJSON();
498
+ expect(warnSpy).not.toHaveBeenCalled();
499
+ });
500
+ });
501
+ test('accepts a real HonoRPCAppBuilder as source', () => {
502
+ const RPC = Procedures();
503
+ RPC.Create('Ping', { scope: 'ping', version: 1, schema: { params: v.object({ msg: v.string() }) } }, async (_ctx, params) => params);
504
+ const builder = new HonoRPCAppBuilder();
505
+ builder.register(RPC, () => ({ userId: '123' }));
506
+ builder.build();
507
+ const registry = new DocRegistry({ basePath: '/rpc' }).from(builder);
508
+ const out = registry.toJSON();
509
+ expect(out.basePath).toBe('/rpc');
510
+ expect(out.routes).toHaveLength(1);
511
+ expect(out.routes[0].name).toBe('Ping');
512
+ expect(out.routes[0]).toHaveProperty('jsonSchema');
513
+ });
514
+ });
515
+ });
516
+ //# sourceMappingURL=doc-registry.test.js.map