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,95 @@
1
+ /**
2
+ * Internal ts-procedures files that should be skipped when finding user code.
3
+ * Only skip the core library files, not test files or user code.
4
+ */
5
+ const INTERNAL_FILES = [
6
+ '/index.ts',
7
+ '/index.js',
8
+ '/errors.ts',
9
+ '/errors.js',
10
+ '/stack-utils.ts',
11
+ '/stack-utils.js',
12
+ '/compute-schema.ts',
13
+ '/compute-schema.js',
14
+ '/parser.ts',
15
+ '/parser.js',
16
+ ];
17
+ /**
18
+ * Captures the stack trace at the call site and extracts the definition location.
19
+ * Finds the first stack frame outside of ts-procedures internal files.
20
+ */
21
+ export function captureDefinitionInfo() {
22
+ const err = new Error();
23
+ const stack = err.stack;
24
+ if (!stack) {
25
+ return {};
26
+ }
27
+ const lines = stack.split('\n');
28
+ // Find the first frame that's not from ts-procedures internals
29
+ // Skip the first line (Error message) and frames from this module
30
+ let userFrame;
31
+ for (let i = 1; i < lines.length; i++) {
32
+ const rawLine = lines[i];
33
+ if (!rawLine)
34
+ continue;
35
+ const line = rawLine.trim();
36
+ // Skip empty or invalid frames
37
+ if (!line.startsWith('at ')) {
38
+ continue;
39
+ }
40
+ // Skip frames from ts-procedures internal source files
41
+ const isInternalFile = INTERNAL_FILES.some(file => line.includes(file));
42
+ if (isInternalFile) {
43
+ continue;
44
+ }
45
+ // Skip frames from ts-procedures in node_modules (when used as a dependency)
46
+ if (line.includes('/node_modules/ts-procedures/') || line.includes('\\node_modules\\ts-procedures\\')) {
47
+ continue;
48
+ }
49
+ // Skip internal node frames
50
+ if (line.includes('node:') || line.startsWith('at Module.') || line.startsWith('at Object.<anonymous> (node:')) {
51
+ continue;
52
+ }
53
+ userFrame = line;
54
+ break;
55
+ }
56
+ if (!userFrame) {
57
+ return { definitionStack: stack };
58
+ }
59
+ const definedAt = parseStackFrame(userFrame);
60
+ return {
61
+ definedAt,
62
+ definitionStack: stack,
63
+ };
64
+ }
65
+ /**
66
+ * Parses a V8 stack frame line to extract file, line, and column info.
67
+ * Handles formats like:
68
+ * - "at Object.<anonymous> (/path/to/file.ts:10:5)"
69
+ * - "at functionName (/path/to/file.ts:10:5)"
70
+ * - "at /path/to/file.ts:10:5"
71
+ */
72
+ function parseStackFrame(frame) {
73
+ // Match patterns like "(path:line:column)" or just "path:line:column"
74
+ const match = frame.match(/\(([^)]+):(\d+):(\d+)\)$/) || frame.match(/at ([^:]+):(\d+):(\d+)$/);
75
+ if (match && match[1] && match[2] && match[3]) {
76
+ return {
77
+ file: match[1],
78
+ line: parseInt(match[2], 10),
79
+ column: parseInt(match[3], 10),
80
+ raw: frame,
81
+ };
82
+ }
83
+ return undefined;
84
+ }
85
+ /**
86
+ * Formats definition info for appending to error stacks.
87
+ */
88
+ export function formatDefinitionInfo(info, procedureName) {
89
+ if (!info.definedAt) {
90
+ return undefined;
91
+ }
92
+ const { file, line, column } = info.definedAt;
93
+ return `\n--- Procedure "${procedureName}" defined at ---\n at ${file}:${line}:${column}`;
94
+ }
95
+ //# sourceMappingURL=stack-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-utils.js","sourceRoot":"","sources":["../src/stack-utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import { captureDefinitionInfo, formatDefinitionInfo } from './stack-utils.js';
3
+ describe('Stack Utils', () => {
4
+ describe('captureDefinitionInfo', () => {
5
+ test('returns definition info with definedAt', () => {
6
+ const info = captureDefinitionInfo();
7
+ // Should capture the call site in this test file
8
+ expect(info).toBeDefined();
9
+ expect(info.definitionStack).toBeDefined();
10
+ expect(typeof info.definitionStack).toBe('string');
11
+ });
12
+ test('definedAt contains file, line, column when available', () => {
13
+ const info = captureDefinitionInfo();
14
+ // The definedAt should be present since we're calling from user code (test file)
15
+ if (info.definedAt) {
16
+ expect(info.definedAt.file).toBeDefined();
17
+ expect(typeof info.definedAt.file).toBe('string');
18
+ expect(info.definedAt.line).toBeDefined();
19
+ expect(typeof info.definedAt.line).toBe('number');
20
+ expect(info.definedAt.line).toBeGreaterThan(0);
21
+ expect(info.definedAt.column).toBeDefined();
22
+ expect(typeof info.definedAt.column).toBe('number');
23
+ expect(info.definedAt.column).toBeGreaterThan(0);
24
+ expect(info.definedAt.raw).toBeDefined();
25
+ expect(typeof info.definedAt.raw).toBe('string');
26
+ }
27
+ });
28
+ test('definitionStack contains Error stack trace', () => {
29
+ const info = captureDefinitionInfo();
30
+ expect(info.definitionStack).toContain('Error');
31
+ expect(info.definitionStack).toContain('at ');
32
+ });
33
+ });
34
+ describe('formatDefinitionInfo', () => {
35
+ test('returns undefined when definedAt is not present', () => {
36
+ const info = {};
37
+ const result = formatDefinitionInfo(info, 'TestProcedure');
38
+ expect(result).toBeUndefined();
39
+ });
40
+ test('returns formatted string when definedAt is present', () => {
41
+ const info = {
42
+ definedAt: {
43
+ file: '/app/procedures/test.ts',
44
+ line: 42,
45
+ column: 5,
46
+ raw: 'at Object.<anonymous> (/app/procedures/test.ts:42:5)',
47
+ },
48
+ };
49
+ const result = formatDefinitionInfo(info, 'TestProcedure');
50
+ expect(result).toBeDefined();
51
+ expect(result).toContain('--- Procedure "TestProcedure" defined at ---');
52
+ expect(result).toContain('/app/procedures/test.ts:42:5');
53
+ });
54
+ test('includes procedure name in formatted output', () => {
55
+ const info = {
56
+ definedAt: {
57
+ file: '/path/to/file.ts',
58
+ line: 10,
59
+ column: 3,
60
+ raw: 'at /path/to/file.ts:10:3',
61
+ },
62
+ };
63
+ const result = formatDefinitionInfo(info, 'MyCustomProcedure');
64
+ expect(result).toContain('"MyCustomProcedure"');
65
+ });
66
+ });
67
+ describe('integration with procedure creation', () => {
68
+ test('captures location from calling code', () => {
69
+ // Helper to simulate what happens in Create()
70
+ function simulateCreate() {
71
+ return captureDefinitionInfo();
72
+ }
73
+ const info = simulateCreate();
74
+ // Should have captured the location of the simulateCreate() call
75
+ expect(info).toBeDefined();
76
+ expect(info.definitionStack).toBeDefined();
77
+ });
78
+ });
79
+ });
80
+ //# sourceMappingURL=stack-utils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stack-utils.test.js","sourceRoot":"","sources":["../src/stack-utils.test.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
 
3
3
  # AI Agent Setup
4
4
 
5
- ts-procedures ships with built-in AI assistant configuration for **Claude Code**, **Cursor**, and **GitHub Copilot**. This gives AI tools framework-aware context when writing ts-procedures code in your project.
5
+ ts-procedures ships a single, spec-compliant **Agent Skill** that gives AI coding tools framework-aware context when writing ts-procedures code in your project. It follows the open [Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) / `SKILL.md` standard, so a wide range of tools — Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, and others — read the same files.
6
6
 
7
7
  ## Quick Setup
8
8
 
@@ -10,52 +10,44 @@ ts-procedures ships with built-in AI assistant configuration for **Claude Code**
10
10
  npx ts-procedures-setup
11
11
  ```
12
12
 
13
- This installs rules for all supported AI tools. You can also target specific tools:
14
-
15
- ```bash
16
- npx ts-procedures-setup claude # Claude Code only
17
- npx ts-procedures-setup cursor # Cursor only
18
- npx ts-procedures-setup copilot # GitHub Copilot only
19
- ```
13
+ This installs the skill into the standard agent directories. There are no per-tool targets — one skill works everywhere.
20
14
 
21
15
  ## What Gets Installed
22
16
 
23
- | Tool | Files | Auto-updates? |
24
- |------|-------|---------------|
25
- | **Claude Code** | `.claude/skills/ts-procedures/` (single skill — reference + `review`/`scaffold` modes + Kotlin/Swift codegen), `.claude/agents/ts-procedures-architect.md` | Yes |
26
- | **Cursor** | `.cursorrules` (marker-based section) | Yes |
27
- | **GitHub Copilot** | `.github/copilot-instructions.md` (marker-based section) | Yes |
17
+ The same skill is copied into two standard locations:
18
+
19
+ | Path | Read by |
20
+ |------|---------|
21
+ | `.agents/skills/ts-procedures/` | The cross-tool standard — 30+ agents (Codex, Cursor, GitHub Copilot, Gemini CLI, …) scan `.agents/skills` from the working directory up to the repo root |
22
+ | `.claude/skills/ts-procedures/` | Claude Code's native project skills location |
23
+
24
+ The skill bundles the framework reference plus `review` / `scaffold` modes (checklist + templates) and Kotlin/Swift client-codegen reference, using progressive disclosure so tools load detail only as needed.
28
25
 
29
26
  ## Auto-Updates
30
27
 
31
- After initial setup, rules are automatically refreshed on every `npm install` or `npm update`. When ts-procedures publishes a new version, your AI tools get the latest framework guidance without any manual steps.
28
+ After initial setup, the skill is automatically refreshed on every `npm install` or `npm update` via the package's postinstall hook. When ts-procedures publishes a new version, your AI tools get the latest framework guidance with no manual steps. Auto-update is opt-in: it only runs once the skill has been installed at least once.
32
29
 
33
- ## Claude Code Features
30
+ ## Skill Modes
34
31
 
35
- Once installed, Claude Code gets:
32
+ Once installed, the `ts-procedures` skill exposes three modes selected by the first word of its arguments:
36
33
 
37
- - **Unified `ts-procedures` skill**one skill with three modes selected by the first word of its arguments:
38
- - *Reference* (no argument) core API, schema system, error handling, decision framework, plus Kotlin/Swift client-codegen reference (auto-discovered by Claude Code)
39
- - *Scaffold* — `/ts-procedures scaffold <type> <Name>` generates procedures, streams, and HTTP setups with correct patterns
40
- - *Review* — `/ts-procedures review <path>` checks code against a 60+ item checklist
41
- - **Architecture agent** — `ts-procedures-architect` helps plan procedure structure, schema design, and HTTP implementation choices
34
+ - *Reference* (no argument)core API, schema system, error handling, decision framework, plus Kotlin/Swift client-codegen reference (auto-discovered by the agent)
35
+ - *Scaffold* — `scaffold <type> <Name>` generates procedures, streams, and HTTP setups with correct patterns
36
+ - *Review* — `review <path>` checks code against a 60+ item checklist
42
37
 
43
38
  ## CLI Options
44
39
 
45
40
  ```bash
46
- npx ts-procedures-setup --force # Overwrite without prompting
47
41
  npx ts-procedures-setup --dry-run # Preview what would be created/updated
48
42
  npx ts-procedures-setup --check # Exit with code 1 if files are outdated (for CI)
49
43
  ```
50
44
 
51
45
  ## Gitignore
52
46
 
53
- The `.claude/` files are auto-generated and regenerated on `npm install`. You can add them to `.gitignore`:
47
+ The installed skill is generated and regenerated on `npm install`. You can add the install roots to `.gitignore`:
54
48
 
55
49
  ```gitignore
56
- # Auto-generated AI agent rules (regenerated on npm install)
50
+ # Auto-generated AI agent skill (regenerated on npm install)
51
+ .agents/skills/ts-procedures/
57
52
  .claude/skills/ts-procedures/
58
- .claude/agents/ts-procedures-*.md
59
53
  ```
60
-
61
- Cursor and Copilot files use marker-based sections that coexist with your own rules, so they should typically be committed.
@@ -414,7 +414,7 @@ A framework-level "skip `onError` for `.safe()`" toggle was deliberately deferre
414
414
 
415
415
  - Codegen setup, per-call options, hooks, and typed-error wiring: [`docs/client-and-codegen.md`](./client-and-codegen.md)
416
416
  - Client types and interfaces: package source under `src/client/` (`types.ts`, `errors.ts`, `error-dispatch.ts`)
417
- - Agent config patterns and anti-patterns: [`agent_config/claude-code/skills/ts-procedures/patterns.md`](../agent_config/claude-code/skills/ts-procedures/patterns.md) and [`anti-patterns.md`](../agent_config/claude-code/skills/ts-procedures/anti-patterns.md)
417
+ - Agent config patterns and anti-patterns: [`agent_config/skills/ts-procedures/patterns.md`](../agent_config/skills/ts-procedures/patterns.md) and [`anti-patterns.md`](../agent_config/skills/ts-procedures/anti-patterns.md)
418
418
 
419
419
  ---
420
420
 
package/docs/core.md CHANGED
@@ -14,6 +14,7 @@ Procedures<TContext, TExtendedConfig>(options?: {
14
14
  schema?: { adapters?: SchemaAdapter[] }
15
15
  http?: { pathPrefix?: string; scope?: string }
16
16
  onCreate?: (procedure: AnyProcedureRegistration<TContext, TExtendedConfig>) => void
17
+ middleware?: FactoryMiddleware<TContext>[] // runs with final ctx + input for all procedures
17
18
  })
18
19
  ```
19
20
 
@@ -25,9 +26,12 @@ Procedures<TContext, TExtendedConfig>(options?: {
25
26
  | `options.schema.adapters` | Plug in additional schema libraries via the `SchemaAdapter` interface. TypeBox is built in. See [Other Schema Libraries](#other-schema-libraries--schemaadapter). |
26
27
  | `options.http` | Factory-level defaults for `CreateHttp` / `CreateHttpStream`: `pathPrefix` is prepended to every route's `path` at registration time (part of the route's identity, unlike a server builder's mount prefix), and `scope` is the default codegen scope for routes that don't set one. Per-route config always wins. |
27
28
  | `options.onCreate` | Optional callback invoked when each procedure is registered (runtime) |
29
+ | `options.middleware` | Array of middleware functions that run for every procedure on this factory. They receive the final resolved `ctx` (your `TContext` + `error()` + `signal`) plus the validated input, and can inspect ("parse the incoming call"), augment via `next({ ctx?, input? })`, or short-circuit. See `FactoryMiddleware`. |
28
30
 
29
31
  **Returns:** `Create`, `CreateStream`, `CreateHttp`, `CreateHttpStream`, plus introspection helpers `getProcedures()`, `getProcedure(name)`, `removeProcedure(name)`, and `clear()`.
30
32
 
33
+ Factory `middleware` runs with the resolved context + input for every procedure defined through the factory (transport-independent, useful for authorization that needs `ctx` + call data). Builder-level middleware lives on `HonoAppBuilder` (see [HTTP Integrations](./http-integrations.md)).
34
+
31
35
  ## Create Function
32
36
 
33
37
  The `Create` function defines individual procedures:
@@ -74,6 +78,7 @@ const { UpdateUser } = CreateHttp(
74
78
  **Rules:**
75
79
  - `schema.req` and `schema.params` are **mutually exclusive** — `schema.params` belongs to RPC procedures (`Create`/`CreateStream`), `schema.req` to HTTP procedures. Using `schema.params` on `CreateHttp` throws `ProcedureRegistrationError`.
76
80
  - Valid channels are `pathParams`, `query`, `body`, and `headers`; each is validated independently with per-channel error messages.
81
+ - The channel names are provided by `THttpReqInput` (and its alias `APIInput` from `ts-procedures/http`) so that IDEs offer autocomplete when writing `schema: { req: { ... } }`. Use `satisfies APIInput` for additional typo protection on the keys.
77
82
  - `schema.res.body` documents the response body (drives codegen); omit `schema.res` entirely for 204-style no-content routes.
78
83
  - Factory-level `http.pathPrefix` / `http.scope` defaults apply; per-route config wins.
79
84
  - For HTTP streaming routes, use `CreateHttpStream` — same `schema.req` channels plus `schema.yield`.
@@ -500,6 +505,7 @@ Creates a procedure factory.
500
505
  - `options.schema.adapters` - Additional schema-library adapters (see [Other Schema Libraries](#other-schema-libraries--schemaadapter))
501
506
  - `options.http` - Factory-level `pathPrefix` / `scope` defaults for `CreateHttp` / `CreateHttpStream`
502
507
  - `options.onCreate` - Callback invoked when each procedure is registered
508
+ - `options.middleware` - Factory-level middleware with access to final `ctx` + input (for authz, enrichment, call parsing, etc.)
503
509
 
504
510
  **Returns:**
505
511
  - `Create` / `CreateStream` / `CreateHttp` / `CreateHttpStream` - Functions to define procedures
@@ -560,6 +566,8 @@ import type {
560
566
  TNoContextProvided,
561
567
  HttpMethod,
562
568
  HttpReturn,
569
+ FactoryMiddleware, // for Procedures({ middleware }) — receives final ctx + input
570
+
563
571
 
564
572
  // Errors
565
573
  ProcedureErrorKind, // 'procedure' | 'validation' | 'yield-validation' | 'registration'
@@ -178,7 +178,7 @@ new HonoAppBuilder({
178
178
  }).register(procs, /* ... */).build()
179
179
  ```
180
180
 
181
- (Need to route different error classes to different status codes? Use the taxonomy — that's exactly what it's designed for. Hand-writing `instanceof` ladders inside `onError` is [anti-pattern #20](../agent_config/claude-code/skills/ts-procedures/anti-patterns.md).)
181
+ (Need to route different error classes to different status codes? Use the taxonomy — that's exactly what it's designed for. Hand-writing `instanceof` ladders inside `onError` is [anti-pattern #20](../agent_config/skills/ts-procedures/anti-patterns.md).)
182
182
 
183
183
  `HonoAppBuilder`'s `onError` signature: `(procedure, c: Context, error) => Response | Promise<Response>` — for streaming procedures, pre-stream only.
184
184
 
@@ -219,6 +219,46 @@ new HonoAppBuilder({
219
219
 
220
220
  The observer is awaited before the response is sent, and any error it throws is swallowed (logged to the console) so a broken instrumentation hook can't corrupt the primary flow.
221
221
 
222
+ ### Middleware (auth, tenancy, guards)
223
+
224
+ Middleware can be declared at two levels that compose naturally.
225
+
226
+ **At the `HonoAppBuilder` (serving / transport level):**
227
+
228
+ - `new HonoAppBuilder({ middleware: [mw, ...] })`
229
+ - `builder.use(mw)` or `builder.use('/specific/*', mw)` (chainable)
230
+ - Per registration: `.register(factory, ctx, { middleware: [mw], context?: extraLayer })`
231
+
232
+ Signature: `(c: Context, next) => Response | Promise<Response|void> | void`.
233
+
234
+ **At the `Procedures()` factory (definition / business-logic level):**
235
+
236
+ ```ts
237
+ const Protected = Procedures<MyCtx>({
238
+ middleware: [({ ctx, input, next }) => { ... }]
239
+ })
240
+ ```
241
+
242
+ These run *after* context resolution but before your handler. They receive the final `ctx` (same one the handler sees) + the validated `input`, so you can "parse the incoming call", perform authz using `ctx.user` + `input`, or augment via `next({ ctx?, input? })`. Defined as `FactoryMiddleware<TContext>`.
243
+
244
+ See the `Procedures()` options in [Core](./core.md).
245
+
246
+ Use builder middleware for raw request concerns and factory middleware for ctx-aware logic. Errors from either flow through your taxonomy / `onError`.
247
+
248
+ The builder example below shows the two layers working together:
249
+
250
+ ```ts
251
+ const app = new HonoAppBuilder({ pathPrefix: '/api', middleware: [authenticate] })
252
+ .register(Protected, (c) => ({
253
+ tenant: c.get('tenant'),
254
+ user: getUser(c),
255
+ }))
256
+ .register(Public, (c) => ({ requestId: c.req.header('x-rid') }))
257
+ .build()
258
+ ```
259
+
260
+ `context` layers (via `register(..., { context })` or arrays) are resolved left-to-right via `composeContextInits`. Middleware declared at either level flows errors through `onRequestError` → taxonomy → `onError`.
261
+
222
262
  ### Dispatch order inside each builder's catch block
223
263
 
224
264
  1. **`onRequestError`** (observer) — awaited, can't alter dispatch or response
@@ -311,33 +351,7 @@ const docs = new DocRegistry({ errors: appErrors, basePath: '/api' })
311
351
 
312
352
  `http-stream` route docs include the route's `yield` and `res.headers` schemas. (Through v8, live envelopes silently dropped these for `http-stream` routes, so generated clients lost those types unless the envelope was hand-authored; v9 fixed this.)
313
353
 
314
- ### Envelope versioning
315
-
316
- Every envelope carries two top-level metadata fields so downstream consumers (docs viewers, codegen, dashboards) can reason about format compatibility:
317
-
318
- ```jsonc
319
- {
320
- "specVersion": 1, // wire-format version of the envelope itself
321
- "generatorVersion": "10.0.0", // ts-procedures package version that produced it
322
- "basePath": "/api",
323
- "headers": [],
324
- "errors": [ /* ... */ ],
325
- "routes": [ /* ... */ ]
326
- }
327
- ```
328
-
329
- - **`specVersion`** is a small monotonic integer, bumped **only** when the envelope shape changes in a way parsers must react to — never on ordinary package releases. Branch on it. An envelope with no `specVersion` predates the field and must be treated as `1`. The current value is exported as `DOC_ENVELOPE_SPEC_VERSION` from `ts-procedures` and `ts-procedures/server`.
330
- - **`generatorVersion`** is the producing package version (`DOC_ENVELOPE_GENERATOR_VERSION`, exported from `ts-procedures/server`). It is informational only — for telemetry and "which release served this?" — and must **not** drive parse-compatibility decisions, since it churns on every release regardless of the envelope format.
331
-
332
- If you'd rather not advertise the exact package version on a public docs endpoint, strip it with the `transform` hook:
333
-
334
- ```typescript
335
- app.get('/docs', (c) => c.json(
336
- builder.toDocEnvelope({ transform: ({ generatorVersion, ...env }) => env }),
337
- ))
338
- ```
339
-
340
- The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md). Building a third-party docs viewer or other tool that consumes the served envelope? The full wire contract — every field, per-kind shapes, and a validatable JSON Schema — is in [DocEnvelope Spec](./doc-envelope-spec-v1.md) (`doc-envelope-v1.schema.json`).
354
+ The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
341
355
 
342
356
  ## Type Exports
343
357
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-procedures",
3
- "version": "10.1.2",
3
+ "version": "10.2.1",
4
4
  "description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
5
5
  "main": "build/exports.js",
6
6
  "types": "build/exports.d.ts",
@@ -76,15 +76,15 @@
76
76
  "typebox": "^1.0.30"
77
77
  },
78
78
  "devDependencies": {
79
- "@eslint/js": "^10.0.1",
80
- "@typescript-eslint/eslint-plugin": "^8.53.0",
81
- "@typescript-eslint/parser": "^8.53.0",
82
- "astro": "^7.0.2",
83
- "eslint": "^10.5.0",
84
- "globals": "^17.7.0",
79
+ "@eslint/js": "^9.17.0",
80
+ "astro": "^6.3.1",
81
+ "eslint": "^9.17.0",
82
+ "globals": "^15.0.0",
85
83
  "hono": "^4.7.4",
86
- "typescript": "^6.0.3",
84
+ "typescript": "^5.7.0",
87
85
  "typescript-eslint": "^8.53.0",
86
+ "@typescript-eslint/eslint-plugin": "^8.53.0",
87
+ "@typescript-eslint/parser": "^8.53.0",
88
88
  "vitest": "^4.0.18"
89
89
  }
90
90
  }
@@ -86,3 +86,5 @@ Dispatch rules:
86
86
  ## What's NOT included
87
87
 
88
88
  - DocRegistry coupling — wire `DocRegistry` against the same builders separately for client codegen.
89
+
90
+ Middleware, guards, and context layering are configured on the `HonoAppBuilder` instances you pass in (`.use()`, `middleware`, per-`register` options). The Astro adapter just dispatches the pre-built apps.
@@ -3,10 +3,8 @@ import { dirname, resolve } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { describe, expect, it } from 'vitest'
5
5
  import { Type } from 'typebox'
6
- import pkg from '../../../package.json' with { type: 'json' }
7
6
  import { Procedures } from '../../core/procedures.js'
8
7
  import { HonoAppBuilder, defineErrorTaxonomy } from './index.js'
9
- import { DOC_ENVELOPE_SPEC_VERSION } from '../../server/spec-version.js'
10
8
  import type { RPCConfig } from '../../server/types.js'
11
9
 
12
10
  /**
@@ -18,15 +16,12 @@ import type { RPCConfig } from '../../server/types.js'
18
16
  * The envelope is codegen's input; if this test fails, generated client output
19
17
  * WILL drift.
20
18
  *
21
- * TWO documented deviations from the raw v8 capture:
22
- * 1. v8's CreateHttpStream dropped the `yield` schema (and `res.headers`) from
23
- * http-stream route docs — the registration stored `yieldType` while the
24
- * doc builder read `yield`. v9 fixes that, so the fixture's TailUser route
25
- * was patched to include the `yield` schema the builder now emits.
26
- * 2. v9 adds envelope-level `specVersion`/`generatorVersion` metadata that v8
27
- * never emitted. These are asserted explicitly below, then stripped before
28
- * the deep-equal so the rest stays byte-for-byte with the v8 capture.
29
- * Everything else is the untouched v8 capture.
19
+ * ONE documented deviation from the raw v8 capture: v8's CreateHttpStream
20
+ * dropped the `yield` schema (and `res.headers`) from http-stream route docs —
21
+ * the registration stored `yieldType` while the doc builder read `yield`. v9
22
+ * fixes that, so the fixture's TailUser route was patched to include the
23
+ * `yield` schema the builder now (correctly) emits. Everything else is the
24
+ * untouched v8 capture.
30
25
  */
31
26
  class TeapotError extends Error {}
32
27
 
@@ -126,13 +121,6 @@ describe('DocEnvelope parity with v8.6.0', () => {
126
121
  const expected = JSON.parse(await readFile(fixturePath, 'utf-8'))
127
122
  const actual = JSON.parse(JSON.stringify(builder.toDocEnvelope()))
128
123
 
129
- // Deviation #2: assert the new v9 envelope metadata, then strip it so the
130
- // remaining shape matches the untouched v8 capture.
131
- expect(actual.specVersion).toBe(DOC_ENVELOPE_SPEC_VERSION)
132
- expect(actual.generatorVersion).toBe(pkg.version)
133
- delete actual.specVersion
134
- delete actual.generatorVersion
135
-
136
124
  expect(actual).toEqual(expected)
137
125
  })
138
126
  })
@@ -7,9 +7,9 @@ import { resolveFactoryContext } from '../../../server/context.js'
7
7
  import { extractReqChannels } from '../../../server/request/params.js'
8
8
  import { parseQueryNative } from '../../../server/request/query.js'
9
9
  import { SseEventSequencer } from '../../../server/sse.js'
10
- import { honoRequestSource, respondToDispatch } from '../request.js'
10
+ import { honoRequestSource, respondToDispatch, runMiddlewareList } from '../request.js'
11
11
  import { midStreamErrorConfig, preStreamErrorConfig } from '../types.js'
12
- import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
12
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
13
13
 
14
14
  export function installHttpStreamRoute(params: {
15
15
  app: Hono
@@ -17,8 +17,9 @@ export function installHttpStreamRoute(params: {
17
17
  factoryItem: HonoFactoryItem
18
18
  cfg: HonoAppBuilderConfig
19
19
  docs: DocAccumulator
20
+ builderMiddleware?: HonoRequestMiddleware[]
20
21
  }): void {
21
- const { app, procedure, factoryItem, cfg, docs } = params
22
+ const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
22
23
  const queryParser = cfg.api?.queryParser ?? parseQueryNative
23
24
 
24
25
  const route = buildHttpStreamRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
@@ -26,8 +27,16 @@ export function installHttpStreamRoute(params: {
26
27
 
27
28
  const reqSchema = procedure.config.schema?.req
28
29
 
30
+ const combinedMw = [
31
+ ...(builderMiddleware ?? []),
32
+ ...(factoryItem.middleware ?? []),
33
+ ]
34
+
29
35
  app.on(procedure.config.method.toUpperCase(), route.fullPath, async (c: Context) => {
30
36
  try {
37
+ const early = await runMiddlewareList(c, combinedMw)
38
+ if (early) return early
39
+
31
40
  const context = await resolveFactoryContext(factoryItem.factoryContext, c)
32
41
 
33
42
  const reqParams = reqSchema
@@ -5,9 +5,9 @@ import { buildHttpRouteDoc } from '../../../server/docs/http-doc.js'
5
5
  import { resolveFactoryContext } from '../../../server/context.js'
6
6
  import { defaultSuccessStatus, extractReqChannels } from '../../../server/request/params.js'
7
7
  import { parseQueryNative } from '../../../server/request/query.js'
8
- import { honoRequestSource, respondToDispatch } from '../request.js'
8
+ import { honoRequestSource, respondToDispatch, runMiddlewareList } from '../request.js'
9
9
  import { preStreamErrorConfig } from '../types.js'
10
- import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
10
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
11
11
 
12
12
  export function installHttpRoute(params: {
13
13
  app: Hono
@@ -15,8 +15,9 @@ export function installHttpRoute(params: {
15
15
  factoryItem: HonoFactoryItem
16
16
  cfg: HonoAppBuilderConfig
17
17
  docs: DocAccumulator
18
+ builderMiddleware?: HonoRequestMiddleware[]
18
19
  }): void {
19
- const { app, procedure, factoryItem, cfg, docs } = params
20
+ const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
20
21
  const queryParser = cfg.api?.queryParser ?? parseQueryNative
21
22
 
22
23
  const route = buildHttpRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
@@ -25,8 +26,16 @@ export function installHttpRoute(params: {
25
26
  const successStatus = procedure.config.successStatus ?? defaultSuccessStatus(procedure.config.method)
26
27
  const reqSchema = procedure.config.schema?.req
27
28
 
29
+ const combinedMw = [
30
+ ...(builderMiddleware ?? []),
31
+ ...(factoryItem.middleware ?? []),
32
+ ]
33
+
28
34
  app.on(procedure.config.method.toUpperCase(), route.fullPath, async (c: Context) => {
29
35
  try {
36
+ const early = await runMiddlewareList(c, combinedMw)
37
+ if (early) return early
38
+
30
39
  const context = await resolveFactoryContext(factoryItem.factoryContext, c)
31
40
 
32
41
  const reqParams = reqSchema
@@ -4,9 +4,9 @@ import type { RPCConfig } from '../../../server/types.js'
4
4
  import { dispatchPreStreamError } from '../../../server/errors/dispatch.js'
5
5
  import { buildRpcRouteDoc } from '../../../server/docs/rpc-doc.js'
6
6
  import { resolveFactoryContext } from '../../../server/context.js'
7
- import { respondToDispatch } from '../request.js'
7
+ import { respondToDispatch, runMiddlewareList } from '../request.js'
8
8
  import { preStreamErrorConfig } from '../types.js'
9
- import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
9
+ import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
10
10
 
11
11
  export function installRpcRoute(params: {
12
12
  app: Hono
@@ -14,14 +14,23 @@ export function installRpcRoute(params: {
14
14
  factoryItem: HonoFactoryItem
15
15
  cfg: HonoAppBuilderConfig
16
16
  docs: DocAccumulator
17
+ builderMiddleware?: HonoRequestMiddleware[]
17
18
  }): void {
18
- const { app, procedure, factoryItem, cfg, docs } = params
19
+ const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
19
20
 
20
21
  const route = buildRpcRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
21
22
  docs.push(route)
22
23
 
24
+ const combinedMw = [
25
+ ...(builderMiddleware ?? []),
26
+ ...(factoryItem.middleware ?? []),
27
+ ]
28
+
23
29
  app.post(route.path, async (c: Context) => {
24
30
  try {
31
+ const early = await runMiddlewareList(c, combinedMw)
32
+ if (early) return early
33
+
25
34
  const context = await resolveFactoryContext(factoryItem.factoryContext, c)
26
35
  const body = await c.req.json().catch(() => ({}))
27
36
  const result = await procedure.handler({ ...context, signal: c.req.raw.signal }, body)