ts-procedures 5.10.0 → 5.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (252) hide show
  1. package/agent_config/bin/postinstall.mjs +3 -3
  2. package/agent_config/bin/setup.mjs +22 -11
  3. package/agent_config/claude-code/agents/ts-procedures-architect.md +2 -2
  4. package/agent_config/claude-code/skills/{guide → ts-procedures}/SKILL.md +1 -1
  5. package/agent_config/claude-code/skills/{guide → ts-procedures}/api-reference.md +11 -8
  6. package/agent_config/claude-code/skills/{guide → ts-procedures}/patterns.md +8 -2
  7. package/agent_config/claude-code/skills/{review → ts-procedures-review}/SKILL.md +3 -3
  8. package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/SKILL.md +2 -2
  9. package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/client.md +4 -4
  10. package/agent_config/copilot/copilot-instructions.md +6 -5
  11. package/agent_config/cursor/cursorrules +6 -5
  12. package/agent_config/lib/install-claude.mjs +35 -87
  13. package/build/codegen/e2e.test.js +21 -14
  14. package/build/codegen/e2e.test.js.map +1 -1
  15. package/build/codegen/emit-index.d.ts +7 -3
  16. package/build/codegen/emit-index.js +33 -20
  17. package/build/codegen/emit-index.js.map +1 -1
  18. package/build/codegen/emit-index.test.js +69 -45
  19. package/build/codegen/emit-index.test.js.map +1 -1
  20. package/build/codegen/pipeline.js +4 -5
  21. package/build/codegen/pipeline.js.map +1 -1
  22. package/build/codegen/pipeline.test.js +4 -4
  23. package/build/codegen/pipeline.test.js.map +1 -1
  24. package/build/src/client/call.d.ts +14 -0
  25. package/build/src/client/call.js +47 -0
  26. package/build/src/client/call.js.map +1 -0
  27. package/build/src/client/call.test.d.ts +1 -0
  28. package/build/src/client/call.test.js +124 -0
  29. package/build/src/client/call.test.js.map +1 -0
  30. package/build/src/client/errors.d.ts +25 -0
  31. package/build/src/client/errors.js +33 -0
  32. package/build/src/client/errors.js.map +1 -0
  33. package/build/src/client/errors.test.d.ts +1 -0
  34. package/build/src/client/errors.test.js +41 -0
  35. package/build/src/client/errors.test.js.map +1 -0
  36. package/build/src/client/fetch-adapter.d.ts +12 -0
  37. package/build/src/client/fetch-adapter.js +156 -0
  38. package/build/src/client/fetch-adapter.js.map +1 -0
  39. package/build/src/client/fetch-adapter.test.d.ts +1 -0
  40. package/build/src/client/fetch-adapter.test.js +271 -0
  41. package/build/src/client/fetch-adapter.test.js.map +1 -0
  42. package/build/src/client/hooks.d.ts +17 -0
  43. package/build/src/client/hooks.js +40 -0
  44. package/build/src/client/hooks.js.map +1 -0
  45. package/build/src/client/hooks.test.d.ts +1 -0
  46. package/build/src/client/hooks.test.js +163 -0
  47. package/build/src/client/hooks.test.js.map +1 -0
  48. package/build/src/client/index.d.ts +22 -0
  49. package/build/src/client/index.js +67 -0
  50. package/build/src/client/index.js.map +1 -0
  51. package/build/src/client/index.test.d.ts +1 -0
  52. package/build/src/client/index.test.js +231 -0
  53. package/build/src/client/index.test.js.map +1 -0
  54. package/build/src/client/request-builder.d.ts +13 -0
  55. package/build/src/client/request-builder.js +53 -0
  56. package/build/src/client/request-builder.js.map +1 -0
  57. package/build/src/client/request-builder.test.d.ts +1 -0
  58. package/build/src/client/request-builder.test.js +160 -0
  59. package/build/src/client/request-builder.test.js.map +1 -0
  60. package/build/src/client/stream.d.ts +27 -0
  61. package/build/src/client/stream.js +118 -0
  62. package/build/src/client/stream.js.map +1 -0
  63. package/build/src/client/stream.test.d.ts +1 -0
  64. package/build/src/client/stream.test.js +228 -0
  65. package/build/src/client/stream.test.js.map +1 -0
  66. package/build/src/client/types.d.ts +78 -0
  67. package/build/src/client/types.js +3 -0
  68. package/build/src/client/types.js.map +1 -0
  69. package/build/src/codegen/bin/cli.d.ts +45 -0
  70. package/build/src/codegen/bin/cli.js +246 -0
  71. package/build/src/codegen/bin/cli.js.map +1 -0
  72. package/build/src/codegen/bin/cli.test.d.ts +1 -0
  73. package/build/src/codegen/bin/cli.test.js +220 -0
  74. package/build/src/codegen/bin/cli.test.js.map +1 -0
  75. package/build/src/codegen/constants.d.ts +1 -0
  76. package/build/src/codegen/constants.js +2 -0
  77. package/build/src/codegen/constants.js.map +1 -0
  78. package/build/src/codegen/e2e.test.d.ts +1 -0
  79. package/build/src/codegen/e2e.test.js +464 -0
  80. package/build/src/codegen/e2e.test.js.map +1 -0
  81. package/build/src/codegen/emit-client-runtime.d.ts +9 -0
  82. package/build/src/codegen/emit-client-runtime.js +99 -0
  83. package/build/src/codegen/emit-client-runtime.js.map +1 -0
  84. package/build/src/codegen/emit-client-runtime.test.d.ts +1 -0
  85. package/build/src/codegen/emit-client-runtime.test.js +78 -0
  86. package/build/src/codegen/emit-client-runtime.test.js.map +1 -0
  87. package/build/src/codegen/emit-client-types.d.ts +8 -0
  88. package/build/src/codegen/emit-client-types.js +25 -0
  89. package/build/src/codegen/emit-client-types.js.map +1 -0
  90. package/build/src/codegen/emit-client-types.test.d.ts +1 -0
  91. package/build/src/codegen/emit-client-types.test.js +33 -0
  92. package/build/src/codegen/emit-client-types.test.js.map +1 -0
  93. package/build/src/codegen/emit-errors.d.ts +19 -0
  94. package/build/src/codegen/emit-errors.js +59 -0
  95. package/build/src/codegen/emit-errors.js.map +1 -0
  96. package/build/src/codegen/emit-errors.test.d.ts +1 -0
  97. package/build/src/codegen/emit-errors.test.js +175 -0
  98. package/build/src/codegen/emit-errors.test.js.map +1 -0
  99. package/build/src/codegen/emit-index.d.ts +12 -0
  100. package/build/src/codegen/emit-index.js +41 -0
  101. package/build/src/codegen/emit-index.js.map +1 -0
  102. package/build/src/codegen/emit-index.test.d.ts +1 -0
  103. package/build/src/codegen/emit-index.test.js +106 -0
  104. package/build/src/codegen/emit-index.test.js.map +1 -0
  105. package/build/src/codegen/emit-scope.d.ts +15 -0
  106. package/build/src/codegen/emit-scope.js +299 -0
  107. package/build/src/codegen/emit-scope.js.map +1 -0
  108. package/build/src/codegen/emit-scope.test.d.ts +1 -0
  109. package/build/src/codegen/emit-scope.test.js +559 -0
  110. package/build/src/codegen/emit-scope.test.js.map +1 -0
  111. package/build/src/codegen/emit-types.d.ts +43 -0
  112. package/build/src/codegen/emit-types.js +111 -0
  113. package/build/src/codegen/emit-types.js.map +1 -0
  114. package/build/src/codegen/emit-types.test.d.ts +1 -0
  115. package/build/src/codegen/emit-types.test.js +184 -0
  116. package/build/src/codegen/emit-types.test.js.map +1 -0
  117. package/build/src/codegen/group-routes.d.ts +23 -0
  118. package/build/src/codegen/group-routes.js +46 -0
  119. package/build/src/codegen/group-routes.js.map +1 -0
  120. package/build/src/codegen/group-routes.test.d.ts +1 -0
  121. package/build/src/codegen/group-routes.test.js +131 -0
  122. package/build/src/codegen/group-routes.test.js.map +1 -0
  123. package/build/src/codegen/index.d.ts +15 -0
  124. package/build/src/codegen/index.js +16 -0
  125. package/build/src/codegen/index.js.map +1 -0
  126. package/build/src/codegen/naming.d.ts +7 -0
  127. package/build/src/codegen/naming.js +21 -0
  128. package/build/src/codegen/naming.js.map +1 -0
  129. package/build/src/codegen/naming.test.d.ts +1 -0
  130. package/build/src/codegen/naming.test.js +40 -0
  131. package/build/src/codegen/naming.test.js.map +1 -0
  132. package/build/src/codegen/pipeline.d.ts +17 -0
  133. package/build/src/codegen/pipeline.js +78 -0
  134. package/build/src/codegen/pipeline.js.map +1 -0
  135. package/build/src/codegen/pipeline.test.d.ts +1 -0
  136. package/build/src/codegen/pipeline.test.js +269 -0
  137. package/build/src/codegen/pipeline.test.js.map +1 -0
  138. package/build/src/codegen/resolve-envelope.d.ts +7 -0
  139. package/build/src/codegen/resolve-envelope.js +46 -0
  140. package/build/src/codegen/resolve-envelope.js.map +1 -0
  141. package/build/src/codegen/resolve-envelope.test.d.ts +1 -0
  142. package/build/src/codegen/resolve-envelope.test.js +69 -0
  143. package/build/src/codegen/resolve-envelope.test.js.map +1 -0
  144. package/build/src/errors.d.ts +33 -0
  145. package/build/src/errors.js +91 -0
  146. package/build/src/errors.js.map +1 -0
  147. package/build/src/errors.test.d.ts +1 -0
  148. package/build/src/errors.test.js +122 -0
  149. package/build/src/errors.test.js.map +1 -0
  150. package/build/src/exports.d.ts +7 -0
  151. package/build/src/exports.js +8 -0
  152. package/build/src/exports.js.map +1 -0
  153. package/build/src/implementations/http/doc-registry.d.ts +12 -0
  154. package/build/src/implementations/http/doc-registry.js +114 -0
  155. package/build/src/implementations/http/doc-registry.js.map +1 -0
  156. package/build/src/implementations/http/doc-registry.test.d.ts +1 -0
  157. package/build/src/implementations/http/doc-registry.test.js +347 -0
  158. package/build/src/implementations/http/doc-registry.test.js.map +1 -0
  159. package/build/src/implementations/http/express-rpc/index.d.ts +94 -0
  160. package/build/src/implementations/http/express-rpc/index.js +185 -0
  161. package/build/src/implementations/http/express-rpc/index.js.map +1 -0
  162. package/build/src/implementations/http/express-rpc/index.test.d.ts +1 -0
  163. package/build/src/implementations/http/express-rpc/index.test.js +684 -0
  164. package/build/src/implementations/http/express-rpc/index.test.js.map +1 -0
  165. package/build/src/implementations/http/express-rpc/types.d.ts +11 -0
  166. package/build/src/implementations/http/express-rpc/types.js +2 -0
  167. package/build/src/implementations/http/express-rpc/types.js.map +1 -0
  168. package/build/src/implementations/http/hono-api/index.d.ts +102 -0
  169. package/build/src/implementations/http/hono-api/index.js +341 -0
  170. package/build/src/implementations/http/hono-api/index.js.map +1 -0
  171. package/build/src/implementations/http/hono-api/index.test.d.ts +1 -0
  172. package/build/src/implementations/http/hono-api/index.test.js +992 -0
  173. package/build/src/implementations/http/hono-api/index.test.js.map +1 -0
  174. package/build/src/implementations/http/hono-api/types.d.ts +13 -0
  175. package/build/src/implementations/http/hono-api/types.js +2 -0
  176. package/build/src/implementations/http/hono-api/types.js.map +1 -0
  177. package/build/src/implementations/http/hono-rpc/index.d.ts +92 -0
  178. package/build/src/implementations/http/hono-rpc/index.js +161 -0
  179. package/build/src/implementations/http/hono-rpc/index.js.map +1 -0
  180. package/build/src/implementations/http/hono-rpc/index.test.d.ts +1 -0
  181. package/build/src/implementations/http/hono-rpc/index.test.js +803 -0
  182. package/build/src/implementations/http/hono-rpc/index.test.js.map +1 -0
  183. package/build/src/implementations/http/hono-rpc/types.d.ts +11 -0
  184. package/build/src/implementations/http/hono-rpc/types.js +2 -0
  185. package/build/src/implementations/http/hono-rpc/types.js.map +1 -0
  186. package/build/src/implementations/http/hono-stream/index.d.ts +120 -0
  187. package/build/src/implementations/http/hono-stream/index.js +309 -0
  188. package/build/src/implementations/http/hono-stream/index.js.map +1 -0
  189. package/build/src/implementations/http/hono-stream/index.test.d.ts +1 -0
  190. package/build/src/implementations/http/hono-stream/index.test.js +1356 -0
  191. package/build/src/implementations/http/hono-stream/index.test.js.map +1 -0
  192. package/build/src/implementations/http/hono-stream/types.d.ts +15 -0
  193. package/build/src/implementations/http/hono-stream/types.js +2 -0
  194. package/build/src/implementations/http/hono-stream/types.js.map +1 -0
  195. package/build/src/implementations/types.d.ts +142 -0
  196. package/build/src/implementations/types.js +2 -0
  197. package/build/src/implementations/types.js.map +1 -0
  198. package/build/src/index.d.ts +165 -0
  199. package/build/src/index.js +253 -0
  200. package/build/src/index.js.map +1 -0
  201. package/build/src/index.test.d.ts +1 -0
  202. package/build/src/index.test.js +890 -0
  203. package/build/src/index.test.js.map +1 -0
  204. package/build/src/schema/compute-schema.d.ts +35 -0
  205. package/build/src/schema/compute-schema.js +41 -0
  206. package/build/src/schema/compute-schema.js.map +1 -0
  207. package/build/src/schema/compute-schema.test.d.ts +1 -0
  208. package/build/src/schema/compute-schema.test.js +107 -0
  209. package/build/src/schema/compute-schema.test.js.map +1 -0
  210. package/build/src/schema/extract-json-schema.d.ts +2 -0
  211. package/build/src/schema/extract-json-schema.js +12 -0
  212. package/build/src/schema/extract-json-schema.js.map +1 -0
  213. package/build/src/schema/extract-json-schema.test.d.ts +1 -0
  214. package/build/src/schema/extract-json-schema.test.js +23 -0
  215. package/build/src/schema/extract-json-schema.test.js.map +1 -0
  216. package/build/src/schema/parser.d.ts +28 -0
  217. package/build/src/schema/parser.js +170 -0
  218. package/build/src/schema/parser.js.map +1 -0
  219. package/build/src/schema/parser.test.d.ts +1 -0
  220. package/build/src/schema/parser.test.js +120 -0
  221. package/build/src/schema/parser.test.js.map +1 -0
  222. package/build/src/schema/resolve-schema-lib.d.ts +12 -0
  223. package/build/src/schema/resolve-schema-lib.js +11 -0
  224. package/build/src/schema/resolve-schema-lib.js.map +1 -0
  225. package/build/src/schema/resolve-schema-lib.test.d.ts +1 -0
  226. package/build/src/schema/resolve-schema-lib.test.js +17 -0
  227. package/build/src/schema/resolve-schema-lib.test.js.map +1 -0
  228. package/build/src/schema/types.d.ts +8 -0
  229. package/build/src/schema/types.js +2 -0
  230. package/build/src/schema/types.js.map +1 -0
  231. package/build/src/stack-utils.d.ts +25 -0
  232. package/build/src/stack-utils.js +95 -0
  233. package/build/src/stack-utils.js.map +1 -0
  234. package/build/src/stack-utils.test.d.ts +1 -0
  235. package/build/src/stack-utils.test.js +80 -0
  236. package/build/src/stack-utils.test.js.map +1 -0
  237. package/docs/ai-agent-setup.md +7 -6
  238. package/docs/client-and-codegen.md +9 -6
  239. package/package.json +1 -1
  240. package/src/codegen/e2e.test.ts +23 -14
  241. package/src/codegen/emit-index.test.ts +72 -45
  242. package/src/codegen/emit-index.ts +43 -20
  243. package/src/codegen/pipeline.test.ts +4 -4
  244. package/src/codegen/pipeline.ts +4 -5
  245. /package/agent_config/claude-code/skills/{guide → ts-procedures}/anti-patterns.md +0 -0
  246. /package/agent_config/claude-code/skills/{review → ts-procedures-review}/checklist.md +0 -0
  247. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/express-rpc.md +0 -0
  248. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/hono-api.md +0 -0
  249. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/hono-rpc.md +0 -0
  250. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/hono-stream.md +0 -0
  251. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/procedure.md +0 -0
  252. /package/agent_config/claude-code/skills/{scaffold → ts-procedures-scaffold}/templates/stream-procedure.md +0 -0
@@ -64,19 +64,19 @@ function updateMarkedFile(targetPath, sourceRelativePath) {
64
64
 
65
65
  // ─── Auto-update ──────────────────────────────────────────
66
66
 
67
- const rulesFile = join(projectRoot, '.claude', 'rules', 'ts-procedures.md');
67
+ const claudeSkillDir = join(projectRoot, '.claude', 'skills', 'ts-procedures');
68
68
  const cursorFile = join(projectRoot, '.cursorrules');
69
69
  const copilotFile = join(projectRoot, '.github', 'copilot-instructions.md');
70
70
 
71
71
  // Check if any target was previously set up
72
- const hasAnySetup = existsSync(rulesFile) || existsSync(cursorFile) || existsSync(copilotFile);
72
+ const hasAnySetup = existsSync(claudeSkillDir) || existsSync(cursorFile) || existsSync(copilotFile);
73
73
 
74
74
  if (hasAnySetup) {
75
75
  try {
76
76
  const updated = [];
77
77
 
78
78
  // Auto-update Claude Code files
79
- if (existsSync(rulesFile)) {
79
+ if (existsSync(claudeSkillDir)) {
80
80
  const { installClaude } = await import('../lib/install-claude.mjs');
81
81
  installClaude(projectRoot);
82
82
  updated.push('Claude Code');
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
3
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from 'node:fs';
4
4
  import { join, dirname, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createInterface } from 'node:readline';
@@ -23,7 +23,7 @@ function printUsage() {
23
23
  Usage: npx ts-procedures-setup [targets...] [options]
24
24
 
25
25
  Targets:
26
- claude Install Claude Code rules and commands into .claude/
26
+ claude Install Claude Code skills and architect agent into .claude/
27
27
  cursor Copy ts-procedures rules to .cursorrules
28
28
  copilot Copy ts-procedures instructions to .github/copilot-instructions.md
29
29
  all Set up all targets (default)
@@ -89,12 +89,24 @@ function isFileOutdated(targetPath, expectedContent) {
89
89
 
90
90
  function setupClaude({ dryRun, check } = {}) {
91
91
  if (dryRun) {
92
- const claudeFiles = [
93
- '.claude/rules/ts-procedures.md',
94
- '.claude/commands/ts-procedures-scaffold.md',
95
- '.claude/commands/ts-procedures-review.md',
96
- '.claude/agents/ts-procedures-architect.md',
97
- ];
92
+ // Mirror install-claude.mjs: copy every file under each source skill dir, plus the agent.
93
+ const claudeFiles = [];
94
+ const skillsSrc = join(AGENT_CONFIG_DIR, 'claude-code', 'skills');
95
+ for (const skill of ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold']) {
96
+ const walk = (dir, prefix) => {
97
+ for (const entry of readdirSync(dir)) {
98
+ const full = join(dir, entry);
99
+ if (statSync(full).isDirectory()) {
100
+ walk(full, `${prefix}/${entry}`);
101
+ } else {
102
+ claudeFiles.push(`${prefix}/${entry}`);
103
+ }
104
+ }
105
+ };
106
+ walk(join(skillsSrc, skill), `.claude/skills/${skill}`);
107
+ }
108
+ claudeFiles.push('.claude/agents/ts-procedures-architect.md');
109
+
98
110
  console.log('\n Claude Code (dry run)');
99
111
  console.log(' ─────────────────────');
100
112
  for (const f of claudeFiles) {
@@ -116,9 +128,8 @@ function setupClaude({ dryRun, check } = {}) {
116
128
  console.log(` ${f}`);
117
129
  }
118
130
  console.log('');
119
- console.log(' Commands: /project:ts-procedures-scaffold, /project:ts-procedures-review');
120
- console.log(' Agent: ts-procedures-architect (architecture planning)');
121
- console.log(' Reference: Auto-loaded via .claude/rules/ts-procedures.md\n');
131
+ console.log(' Skills: ts-procedures, ts-procedures-scaffold, ts-procedures-review');
132
+ console.log(' Agent: ts-procedures-architect (architecture planning)\n');
122
133
  return false;
123
134
  }
124
135
 
@@ -4,14 +4,14 @@ description: "Architecture planning agent for ts-procedures RPC applications. Us
4
4
  model: sonnet
5
5
  disallowedTools: Write, Edit
6
6
  skills:
7
- - guide
7
+ - ts-procedures
8
8
  color: blue
9
9
  effort: high
10
10
  ---
11
11
 
12
12
  You are an architecture planning agent for applications built with **ts-procedures**, a TypeScript RPC framework that creates type-safe, schema-validated procedure calls. You help developers plan APIs by deciding procedure structure, schema design, context shape, and HTTP integration strategy.
13
13
 
14
- The full ts-procedures framework reference is preloaded via the guide skill — use it for API details, schema rules, error classes, and HTTP builder specifics.
14
+ The full ts-procedures framework reference is preloaded via the `ts-procedures` skill — use it for API details, schema rules, error classes, and HTTP builder specifics.
15
15
 
16
16
  ## Your Process
17
17
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: guide
2
+ name: ts-procedures
3
3
  description: "TypeScript RPC framework reference for ts-procedures — schema validation, error handling, HTTP builders, streaming, and code generation. Use when writing, reviewing, or debugging procedures, configuring HTTP builders, or designing schemas."
4
4
  user-invocable: false
5
5
  ---
@@ -719,7 +719,7 @@ function createClient<TScopes>(config: {
719
719
 
720
720
  - `config.adapter` — Transport adapter implementing `ClientAdapter`. Use `createFetchAdapter()` for fetch-based transport.
721
721
  - `config.basePath` — Base URL prepended to all request paths (e.g., `'http://localhost:3000'`).
722
- - `config.scopes` — Factory function that receives a raw `ClientInstance` and returns the typed scope object. Typically the generated `createScopeBindings` export (or `create${ServiceName}Bindings` with `--service-name`).
722
+ - `config.scopes` — Factory function that receives a raw `ClientInstance` and returns the typed scope object. The generated `create${ServiceName}Bindings` export (defaults to `createApiBindings`; pass `--service-name <Name>` to rename).
723
723
  - `config.hooks` — Optional global hooks applied to every call. Can be overridden per call.
724
724
 
725
725
  ### Return Value
@@ -739,12 +739,12 @@ interface ClientHooks {
739
739
 
740
740
  ```typescript
741
741
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
742
- import { createScopeBindings } from './generated/api'
742
+ import { createApiBindings, Api } from './generated/api'
743
743
 
744
744
  const client = createClient({
745
745
  adapter: createFetchAdapter(),
746
746
  basePath: 'http://localhost:3000',
747
- scopes: createScopeBindings,
747
+ scopes: createApiBindings,
748
748
  hooks: {
749
749
  onBeforeRequest(ctx) {
750
750
  ctx.request.headers = { ...ctx.request.headers, Authorization: `Bearer ${getToken()}` }
@@ -754,6 +754,8 @@ const client = createClient({
754
754
  })
755
755
 
756
756
  const user = await client.users.GetUser({ pathParams: { id: '123' } })
757
+
758
+ // Reach types via the namespace: Api.Users.GetUser.Params, Api.Errors.ProcedureError
757
759
  ```
758
760
 
759
761
  ---
@@ -835,14 +837,15 @@ async function generateClient(options: {
835
837
  | `--depluralize` | Singularize array item type names (ignored with `--no-namespace-types`) |
836
838
  | `--array-item-naming <value>` | Postfix for array item type names (ignored with `--no-namespace-types`) |
837
839
  | `--uncountable-words <list>` | Comma-separated words to skip singularization (ignored with `--no-namespace-types`) |
838
- | `--service-name <name>` | Customize generated identifiers for multi-service apps (e.g., `Auth` → `createAuthBindings`) |
840
+ | `--service-name <name>` | Names the service namespace and binding factory (e.g., `Auth` → `export namespace Auth { ... }` + `createAuthBindings`). Also prefixes `${Name}Errors` and `${Name}ProcedureErrorUnion` in `_errors.ts`. **Default: `Api`.** |
839
841
 
840
842
  ### Generated Output
841
843
 
842
844
  - One `.ts` file per scope (e.g., `users.ts`, `events.ts`)
843
- - A root `index.ts` exporting `createScopeBindings` (or `create${ServiceName}Bindings` with `--service-name`)
845
+ - A root `index.ts` that imports each scope as a namespace (`import * as users from './users'`), exports a `create${ServiceName}Bindings` factory (defaults to `createApiBindings`), and — when namespace mode is on — wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block (defaults to `Api`). Errors are folded in as `${ServiceName}.Errors`.
846
+ - No `export *` re-exports — consumers reach types via `${ServiceName}.<Scope>.<Route>.Params` etc.
844
847
  - Each scope file exports fully-typed callable functions and TypeScript types
845
- - By default, types are wrapped in `export namespace Scope { export namespace Route { ... } }` (disable with `--no-namespace-types`)
848
+ - By default, types are wrapped in `export namespace Scope { export namespace Route { ... } }` (disable with `--no-namespace-types`; this also disables the outer service namespace in `index.ts`)
846
849
 
847
850
  ### Example
848
851
 
@@ -890,9 +893,9 @@ interface TypedStream<TYield, TReturn> extends AsyncIterable<TYield> {
890
893
 
891
894
  ```typescript
892
895
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
893
- import { createScopeBindings } from './generated/api'
896
+ import { createApiBindings } from './generated/api'
894
897
 
895
- const client = createClient({ adapter: createFetchAdapter(), basePath: '...', scopes: createScopeBindings })
898
+ const client = createClient({ adapter: createFetchAdapter(), basePath: '...', scopes: createApiBindings })
896
899
 
897
900
  const stream = client.events.WatchNotifications({ filter: 'all' })
898
901
 
@@ -791,12 +791,13 @@ app.get('/docs', (c) => c.json(docs.toJSON()))
791
791
 
792
792
  ```typescript
793
793
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
794
- import { createScopeBindings } from './generated/api'
794
+ import { createApiBindings, Api } from './generated/api'
795
+ // With --service-name <Name>: import { create<Name>Bindings, <Name> } from './generated/api'
795
796
 
796
797
  const client = createClient({
797
798
  adapter: createFetchAdapter(),
798
799
  basePath: 'http://localhost:3000',
799
- scopes: createScopeBindings,
800
+ scopes: createApiBindings,
800
801
  hooks: {
801
802
  onBeforeRequest(ctx) {
802
803
  ctx.request.headers = {
@@ -815,6 +816,11 @@ const client = createClient({
815
816
 
816
817
  // Fully typed — params and response inferred from server schemas
817
818
  const user = await client.users.GetUser({ pathParams: { id: '123' } })
819
+
820
+ // Reach types via the service namespace:
821
+ // Api.Users.GetUser.Params
822
+ // Api.Users.GetUser.Response
823
+ // Api.Errors.ProcedureError
818
824
  ```
819
825
 
820
826
  ---
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: review
2
+ name: ts-procedures-review
3
3
  description: "Review ts-procedures code for pattern adherence, schema correctness, error handling, and signal propagation."
4
4
  argument-hint: "<path>"
5
5
  allowed-tools: Read Grep Glob
@@ -16,7 +16,7 @@ Parse `$ARGUMENTS` as a file or directory path. If a directory, review all `.ts`
16
16
  1. Read the target file(s).
17
17
  2. Identify ts-procedures imports (`ts-procedures`, `ts-procedures/express-rpc`, `ts-procedures/hono-rpc`, `ts-procedures/hono-stream`, `ts-procedures/hono-api`, `ts-procedures/http`, `ts-procedures/http-docs`) to determine file types.
18
18
  3. Check each file against the categorized checklist in [checklist.md](checklist.md).
19
- 4. For detailed code examples of each violation pattern, reference [anti-patterns.md](../guide/anti-patterns.md) — it shows 20 common mistakes with before/after code fixes and severity ratings.
19
+ 4. For detailed code examples of each violation pattern, reference [anti-patterns.md](../ts-procedures/anti-patterns.md) — it shows 20 common mistakes with before/after code fixes and severity ratings.
20
20
  5. Output findings grouped by severity.
21
21
 
22
22
  ## Output Format
@@ -45,4 +45,4 @@ After individual findings, provide:
45
45
  ## Reference
46
46
 
47
47
  See [checklist.md](checklist.md) for the complete categorized checklist by file type.
48
- See [anti-patterns.md](../guide/anti-patterns.md) for detailed code examples of each violation.
48
+ See [anti-patterns.md](../ts-procedures/anti-patterns.md) for detailed code examples of each violation.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: scaffold
2
+ name: ts-procedures-scaffold
3
3
  description: "Generate ts-procedures implementations with correct patterns — procedures, streams, Express RPC, Hono RPC, Hono streaming, REST APIs, and client setup."
4
4
  argument-hint: "<type> <Name>"
5
5
  allowed-tools: Read Write
@@ -19,7 +19,7 @@ If either argument is missing, ask the user for `<type>` and `<Name>`.
19
19
  - `{{Name}}` — PascalCase as given (e.g., `UserProfile`)
20
20
  - `{{name}}` — camelCase (e.g., `userProfile`)
21
21
  - For URL scopes and file paths, use kebab-case (e.g., `user-profile`)
22
- 4. Read the template file from `${CLAUDE_SKILL_DIR}/templates/$0.md`.
22
+ 4. Read the template file from `templates/$0.md` (relative to this skill directory).
23
23
  5. Replace all `{{Name}}` and `{{name}}` placeholders with the appropriate variants.
24
24
  6. Generate the implementation file(s) following the template exactly.
25
25
  7. Also generate a colocated test file following ts-procedures test conventions.
@@ -4,15 +4,15 @@
4
4
 
5
5
  ```typescript
6
6
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
7
- import { createScopeBindings } from './generated/api'
8
- // With --service-name: import { create{{Name}}Bindings } from './generated/api'
7
+ import { createApiBindings, Api } from './generated/api'
8
+ // With --service-name {{Name}}: import { create{{Name}}Bindings, {{Name}} } from './generated/api'
9
9
 
10
10
  // Create the typed client
11
11
  export const {{name}}Client = createClient({
12
12
  adapter: createFetchAdapter(),
13
13
  basePath: 'http://localhost:3000', // TODO: configure base URL
14
- scopes: createScopeBindings,
15
- // With --service-name: scopes: create{{Name}}Bindings,
14
+ scopes: createApiBindings,
15
+ // With --service-name {{Name}}: scopes: create{{Name}}Bindings,
16
16
  hooks: {
17
17
  onBeforeRequest(ctx) {
18
18
  // TODO: add auth headers, request IDs, etc.
@@ -320,8 +320,8 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
320
320
  # --config ./codegen.config.json --service-name Auth
321
321
  ```
322
322
 
323
- Generates one `.ts` file per scope plus a root `index.ts` exporting `createScopeBindings` (or `create${ServiceName}Bindings` with `--service-name`).
324
- By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`).
323
+ Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings` factory (defaults to `createApiBindings`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.ProcedureError`, etc. The errors file (`_errors.ts`) emits `${ServiceName}Errors` and `${ServiceName}ProcedureErrorUnion` (defaults: `ApiErrors`, `ApiProcedureErrorUnion`).
324
+ By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
325
325
  Note: ajsc formatting options (`--enum-style enum`, `--depluralize`, etc.) only take effect in namespace mode (the default). They are ignored with `--no-namespace-types`.
326
326
  Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD) or `--config <path>`.
327
327
 
@@ -329,12 +329,13 @@ Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD)
329
329
 
330
330
  ```typescript
331
331
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
332
- import { createScopeBindings } from './generated/api'
332
+ import { createApiBindings, Api } from './generated/api'
333
+ // With --service-name <Name>: import { create<Name>Bindings, <Name> } from './generated/api'
333
334
 
334
335
  const client = createClient({
335
336
  adapter: createFetchAdapter(),
336
337
  basePath: 'http://localhost:3000',
337
- scopes: createScopeBindings,
338
+ scopes: createApiBindings,
338
339
  hooks: {
339
340
  onBeforeRequest(ctx) {
340
341
  ctx.request.headers = { ...ctx.request.headers, Authorization: `Bearer ${getToken()}` }
@@ -346,7 +347,7 @@ const client = createClient({
346
347
  },
347
348
  })
348
349
 
349
- // Fully typed — params and response inferred from server schemas
350
+ // Fully typed — params and response inferred from server schemas; type aliases live under Api.<Scope>.<Route>.
350
351
  const user = await client.users.GetUser({ pathParams: { id: '123' } })
351
352
 
352
353
  // Per-call hook override
@@ -320,8 +320,8 @@ npx ts-procedures-codegen --url http://localhost:3000/docs --out ./src/generated
320
320
  # --config ./codegen.config.json --service-name Auth
321
321
  ```
322
322
 
323
- Generates one `.ts` file per scope plus a root `index.ts` exporting `createScopeBindings` (or `create${ServiceName}Bindings` with `--service-name`).
324
- By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`).
323
+ Generates one `.ts` file per scope plus a root `index.ts` that imports each scope as a namespace and exports a `create${ServiceName}Bindings` factory (defaults to `createApiBindings`; pass `--service-name <Name>` to rename). When namespace mode is on (the default), `index.ts` also wraps every scope namespace in an outer `export namespace ${ServiceName} { ... }` block so types are reachable as `Api.Users.GetUser.Params`, `Api.Errors.ProcedureError`, etc. The errors file (`_errors.ts`) emits `${ServiceName}Errors` and `${ServiceName}ProcedureErrorUnion` (defaults: `ApiErrors`, `ApiProcedureErrorUnion`).
324
+ By default, types are wrapped in nested TS namespaces (`Scope.Route.Params`), JSDoc comments are emitted, and output is self-contained (no runtime dependency on `ts-procedures`). Use `--no-namespace-types` to revert to flat type names (`RouteParams`); this also disables the outer service namespace in `index.ts` and skips importing `_errors` from there.
325
325
  Note: ajsc formatting options (`--enum-style enum`, `--depluralize`, etc.) only take effect in namespace mode (the default). They are ignored with `--no-namespace-types`.
326
326
  Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD) or `--config <path>`.
327
327
 
@@ -329,12 +329,13 @@ Supports config file: `ts-procedures-codegen.config.json` (auto-loaded from CWD)
329
329
 
330
330
  ```typescript
331
331
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
332
- import { createScopeBindings } from './generated/api'
332
+ import { createApiBindings, Api } from './generated/api'
333
+ // With --service-name <Name>: import { create<Name>Bindings, <Name> } from './generated/api'
333
334
 
334
335
  const client = createClient({
335
336
  adapter: createFetchAdapter(),
336
337
  basePath: 'http://localhost:3000',
337
- scopes: createScopeBindings,
338
+ scopes: createApiBindings,
338
339
  hooks: {
339
340
  onBeforeRequest(ctx) {
340
341
  ctx.request.headers = { ...ctx.request.headers, Authorization: `Bearer ${getToken()}` }
@@ -346,7 +347,7 @@ const client = createClient({
346
347
  },
347
348
  })
348
349
 
349
- // Fully typed — params and response inferred from server schemas
350
+ // Fully typed — params and response inferred from server schemas; type aliases live under Api.<Scope>.<Route>.
350
351
  const user = await client.users.GetUser({ pathParams: { id: '123' } })
351
352
 
352
353
  // Per-call hook override
@@ -1,109 +1,57 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
1
+ import { cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
2
  import { join, dirname } from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
 
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = dirname(__filename);
7
- const SKILLS_DIR = join(__dirname, '..', 'claude-code', 'skills');
8
- const AGENTS_DIR = join(__dirname, '..', 'claude-code', 'agents');
9
-
10
- function getPackageVersion() {
11
- try {
12
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
13
- return pkg.version || 'unknown';
14
- } catch {
15
- return 'unknown';
16
- }
17
- }
18
-
19
- function makeAutoHeader() {
20
- const version = getPackageVersion();
21
- return `<!-- Auto-generated by ts-procedures@${version}. Updated on npm install/update. Do not edit. -->\n\n`;
22
- }
23
-
24
- function stripFrontmatter(content) {
25
- const match = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
26
- return match ? match[1].trim() : content.trim();
27
- }
28
-
29
- function ensureDir(dir) {
30
- if (!existsSync(dir)) {
31
- mkdirSync(dir, { recursive: true });
7
+ const SOURCE_DIR = join(__dirname, '..', 'claude-code');
8
+
9
+ const SKILL_NAMES = ['ts-procedures', 'ts-procedures-review', 'ts-procedures-scaffold'];
10
+ const AGENT_FILES = ['ts-procedures-architect.md'];
11
+
12
+ function listFilesRecursive(dir) {
13
+ const out = [];
14
+ for (const entry of readdirSync(dir)) {
15
+ const full = join(dir, entry);
16
+ if (statSync(full).isDirectory()) {
17
+ for (const nested of listFilesRecursive(full)) out.push(join(entry, nested));
18
+ } else {
19
+ out.push(entry);
20
+ }
32
21
  }
22
+ return out;
33
23
  }
34
24
 
35
25
  /**
36
26
  * Install Claude Code integration files into a project's .claude/ directory.
37
27
  *
38
28
  * Creates:
39
- * .claude/rules/ts-procedures.md — Framework reference (always loaded in context)
40
- * .claude/commands/ts-procedures-scaffold.md — Scaffold command (/project:ts-procedures-scaffold)
41
- * .claude/commands/ts-procedures-review.md — Review command (/project:ts-procedures-review)
42
- * .claude/agents/ts-procedures-architect.md — Architecture planning agent
29
+ * .claude/skills/ts-procedures/ — Framework reference skill
30
+ * .claude/skills/ts-procedures-scaffold/ — Scaffold skill (templates included)
31
+ * .claude/skills/ts-procedures-review/ — Review skill
32
+ * .claude/agents/ts-procedures-architect.md — Architecture planning agent
43
33
  *
44
34
  * @param {string} projectRoot — Absolute path to the consuming project's root
45
35
  * @returns {{ files: string[] }} — List of created/updated file paths (relative to projectRoot)
46
36
  */
47
37
  export function installClaude(projectRoot) {
48
38
  const claudeDir = join(projectRoot, '.claude');
49
- const rulesDir = join(claudeDir, 'rules');
50
- const commandsDir = join(claudeDir, 'commands');
51
- const agentsDir = join(claudeDir, 'agents');
39
+ const skillsDest = join(claudeDir, 'skills');
40
+ const agentsDest = join(claudeDir, 'agents');
41
+ mkdirSync(skillsDest, { recursive: true });
42
+ mkdirSync(agentsDest, { recursive: true });
52
43
 
53
- ensureDir(rulesDir);
54
- ensureDir(commandsDir);
55
- ensureDir(agentsDir);
56
-
57
- const autoHeader = makeAutoHeader();
58
44
  const files = [];
59
-
60
- // 1. Rules file — framework reference (always loaded in context)
61
- const guideSkill = readFileSync(join(SKILLS_DIR, 'guide', 'SKILL.md'), 'utf-8');
62
- const guideBasePath = 'node_modules/ts-procedures/agent_config/claude-code/skills/guide';
63
- const guideBody = stripFrontmatter(guideSkill)
64
- // Replace relative markdown links with node_modules paths
65
- .replace(/\[patterns\.md\]\(patterns\.md\)/g, `\`${guideBasePath}/patterns.md\``)
66
- .replace(/\[anti-patterns\.md\]\(anti-patterns\.md\)/g, `\`${guideBasePath}/anti-patterns.md\``)
67
- .replace(/\[api-reference\.md\]\(api-reference\.md\)/g, `\`${guideBasePath}/api-reference.md\``)
68
- // Replace the Workflow section — skill cross-references don't apply outside the plugin
69
- .replace(/\n## Workflow[\s\S]*$/, '');
70
-
71
- writeFileSync(join(rulesDir, 'ts-procedures.md'), autoHeader + guideBody, 'utf-8');
72
- files.push('.claude/rules/ts-procedures.md');
73
-
74
- // 2. Scaffold command
75
- const scaffoldSkill = readFileSync(join(SKILLS_DIR, 'scaffold', 'SKILL.md'), 'utf-8');
76
- const scaffoldBasePath = 'node_modules/ts-procedures/agent_config/claude-code/skills/scaffold';
77
- const scaffoldBody = stripFrontmatter(scaffoldSkill)
78
- // Replace ${CLAUDE_SKILL_DIR} template path with node_modules path
79
- .replace(
80
- '`${CLAUDE_SKILL_DIR}/templates/$0.md`',
81
- `\`${scaffoldBasePath}/templates/$0.md\``
82
- )
83
- // Replace Workflow section — skill cross-references don't apply outside the plugin
84
- .replace(/\n## Workflow[\s\S]*$/, '');
85
-
86
- writeFileSync(join(commandsDir, 'ts-procedures-scaffold.md'), autoHeader + scaffoldBody, 'utf-8');
87
- files.push('.claude/commands/ts-procedures-scaffold.md');
88
-
89
- // 3. Review command
90
- const reviewSkill = readFileSync(join(SKILLS_DIR, 'review', 'SKILL.md'), 'utf-8');
91
- const reviewBasePath = 'node_modules/ts-procedures/agent_config/claude-code/skills/review';
92
- const reviewBody = stripFrontmatter(reviewSkill)
93
- // Replace relative markdown links with node_modules paths
94
- .replace(/\[checklist\.md\]\(checklist\.md\)/g, `\`${reviewBasePath}/checklist.md\``)
95
- .replace(
96
- /\[anti-patterns\.md\]\(\.\.\/guide\/anti-patterns\.md\)/g,
97
- `\`${guideBasePath}/anti-patterns.md\``
98
- );
99
-
100
- writeFileSync(join(commandsDir, 'ts-procedures-review.md'), autoHeader + reviewBody, 'utf-8');
101
- files.push('.claude/commands/ts-procedures-review.md');
102
-
103
- // 4. Architect agent
104
- const architectAgent = readFileSync(join(AGENTS_DIR, 'ts-procedures-architect.md'), 'utf-8');
105
- writeFileSync(join(agentsDir, 'ts-procedures-architect.md'), autoHeader + architectAgent, 'utf-8');
106
- files.push('.claude/agents/ts-procedures-architect.md');
107
-
45
+ for (const skill of SKILL_NAMES) {
46
+ const src = join(SOURCE_DIR, 'skills', skill);
47
+ cpSync(src, join(skillsDest, skill), { recursive: true, force: true });
48
+ for (const rel of listFilesRecursive(src)) {
49
+ files.push(`.claude/skills/${skill}/${rel}`);
50
+ }
51
+ }
52
+ for (const agent of AGENT_FILES) {
53
+ cpSync(join(SOURCE_DIR, 'agents', agent), join(agentsDest, agent), { force: true });
54
+ files.push(`.claude/agents/${agent}`);
55
+ }
108
56
  return { files };
109
57
  }
@@ -239,35 +239,35 @@ describe('E2E: generateClient full pipeline', () => {
239
239
  expect(yieldTypeBody).not.toContain('retry');
240
240
  });
241
241
  // ── index.ts ───────────────────────────────────────────────────────────────
242
- it('index.ts contains createScopeBindings', async () => {
242
+ it('index.ts contains the default Api bindings factory', async () => {
243
243
  tmpDir = makeTmpDir();
244
244
  await generateClient({ envelope, outDir: tmpDir });
245
245
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
246
- expect(content).toContain('createScopeBindings');
246
+ expect(content).toContain('createApiBindings');
247
247
  });
248
- it('index.ts re-exports from users scope', async () => {
248
+ it('index.ts imports the users scope as a namespace', async () => {
249
249
  tmpDir = makeTmpDir();
250
250
  await generateClient({ envelope, outDir: tmpDir });
251
251
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
252
- expect(content).toContain("from './users'");
252
+ expect(content).toContain("import * as users from './users'");
253
253
  });
254
- it('index.ts re-exports from events scope', async () => {
254
+ it('index.ts imports the events scope as a namespace', async () => {
255
255
  tmpDir = makeTmpDir();
256
256
  await generateClient({ envelope, outDir: tmpDir });
257
257
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
258
- expect(content).toContain("from './events'");
258
+ expect(content).toContain("import * as events from './events'");
259
259
  });
260
- it('index.ts imports and uses bindUsersScope', async () => {
260
+ it('index.ts uses bindUsersScope through the namespace', async () => {
261
261
  tmpDir = makeTmpDir();
262
262
  await generateClient({ envelope, outDir: tmpDir });
263
263
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
264
- expect(content).toContain('bindUsersScope');
264
+ expect(content).toContain('users.bindUsersScope(client)');
265
265
  });
266
- it('index.ts imports and uses bindEventsScope', async () => {
266
+ it('index.ts uses bindEventsScope through the namespace', async () => {
267
267
  tmpDir = makeTmpDir();
268
268
  await generateClient({ envelope, outDir: tmpDir });
269
269
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
270
- expect(content).toContain('bindEventsScope');
270
+ expect(content).toContain('events.bindEventsScope(client)');
271
271
  });
272
272
  // ── _errors.ts ─────────────────────────────────────────────────────────────
273
273
  it('_errors.ts file exists when envelope has errors with schemas', async () => {
@@ -281,19 +281,26 @@ describe('E2E: generateClient full pipeline', () => {
281
281
  const content = readFileSync(join(tmpDir, '_errors.ts'), 'utf-8');
282
282
  expect(content).toContain('export type ProcedureError =');
283
283
  });
284
- it('_errors.ts contains ProcedureErrorUnion', async () => {
284
+ it('_errors.ts contains the service-prefixed ProcedureErrorUnion', async () => {
285
285
  tmpDir = makeTmpDir();
286
286
  await generateClient({ envelope, outDir: tmpDir });
287
287
  const content = readFileSync(join(tmpDir, '_errors.ts'), 'utf-8');
288
- expect(content).toContain('export type ProcedureErrorUnion =');
288
+ expect(content).toContain('export type ApiProcedureErrorUnion =');
289
289
  expect(content).toContain('ProcedureError');
290
290
  expect(content).toContain('ProcedureValidationError');
291
291
  });
292
- it('index.ts re-exports from _errors when errors are present', async () => {
292
+ it('index.ts does not import _errors when namespaceTypes is off', async () => {
293
293
  tmpDir = makeTmpDir();
294
294
  await generateClient({ envelope, outDir: tmpDir });
295
295
  const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
296
- expect(content).toContain("export * from './_errors'");
296
+ expect(content).not.toContain("from './_errors'");
297
+ });
298
+ it('index.ts folds errors into the service namespace when namespaceTypes is on', async () => {
299
+ tmpDir = makeTmpDir();
300
+ await generateClient({ envelope, outDir: tmpDir, namespaceTypes: true });
301
+ const content = readFileSync(join(tmpDir, 'index.ts'), 'utf-8');
302
+ expect(content).toContain("import * as _errorsModule from './_errors'");
303
+ expect(content).toContain('export import Errors = _errorsModule.ApiErrors');
297
304
  });
298
305
  it('_errors.ts is not generated when no errors have schemas', async () => {
299
306
  tmpDir = makeTmpDir();