ts-procedures 10.1.1 → 10.2.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 (244) hide show
  1. package/CHANGELOG.md +26 -65
  2. package/README.md +2 -0
  3. package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +8 -0
  4. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +25 -22
  5. package/agent_config/claude-code/skills/ts-procedures/patterns.md +24 -13
  6. package/agent_config/copilot/copilot-instructions.md +1 -1
  7. package/agent_config/cursor/cursorrules +1 -1
  8. package/build/adapters/hono/envelope-parity.test.js +6 -17
  9. package/build/adapters/hono/envelope-parity.test.js.map +1 -1
  10. package/build/adapters/hono/handlers/http-stream.d.ts +2 -1
  11. package/build/adapters/hono/handlers/http-stream.js +9 -2
  12. package/build/adapters/hono/handlers/http-stream.js.map +1 -1
  13. package/build/adapters/hono/handlers/http.d.ts +2 -1
  14. package/build/adapters/hono/handlers/http.js +9 -2
  15. package/build/adapters/hono/handlers/http.js.map +1 -1
  16. package/build/adapters/hono/handlers/rpc.d.ts +2 -1
  17. package/build/adapters/hono/handlers/rpc.js +9 -2
  18. package/build/adapters/hono/handlers/rpc.js.map +1 -1
  19. package/build/adapters/hono/handlers/stream.d.ts +2 -1
  20. package/build/adapters/hono/handlers/stream.js +9 -2
  21. package/build/adapters/hono/handlers/stream.js.map +1 -1
  22. package/build/adapters/hono/index.d.ts +14 -7
  23. package/build/adapters/hono/index.js +45 -5
  24. package/build/adapters/hono/index.js.map +1 -1
  25. package/build/adapters/hono/index.test.js +55 -0
  26. package/build/adapters/hono/index.test.js.map +1 -1
  27. package/build/adapters/hono/request.d.ts +7 -0
  28. package/build/adapters/hono/request.js +15 -0
  29. package/build/adapters/hono/request.js.map +1 -1
  30. package/build/adapters/hono/types.d.ts +31 -0
  31. package/build/adapters/hono/types.js.map +1 -1
  32. package/build/codegen/bin/cli.js +1 -1
  33. package/build/codegen/bin/cli.js.map +1 -1
  34. package/build/codegen/constants.js +2 -2
  35. package/build/codegen/constants.js.map +1 -1
  36. package/build/codegen/emit/api-route.js +1 -1
  37. package/build/codegen/emit/api-route.js.map +1 -1
  38. package/build/codegen/emit/scope-file.js +1 -1
  39. package/build/codegen/emit/scope-file.js.map +1 -1
  40. package/build/codegen/emit-errors.integration.test.js +2 -2
  41. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  42. package/build/codegen/resolve-envelope.js +1 -1
  43. package/build/codegen/resolve-envelope.js.map +1 -1
  44. package/build/codegen/test-helpers/run-tsc.js +1 -1
  45. package/build/codegen/test-helpers/run-tsc.js.map +1 -1
  46. package/build/core/create-http-stream.d.ts +3 -14
  47. package/build/core/create-http-stream.js +4 -2
  48. package/build/core/create-http-stream.js.map +1 -1
  49. package/build/core/create-http.d.ts +2 -9
  50. package/build/core/create-http.js +3 -2
  51. package/build/core/create-http.js.map +1 -1
  52. package/build/core/create-http.test-d.d.ts +1 -0
  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/extract-json-schema.d.ts +2 -0
  153. package/build/schema/extract-json-schema.js +12 -0
  154. package/build/schema/extract-json-schema.js.map +1 -0
  155. package/build/schema/extract-json-schema.test.d.ts +1 -0
  156. package/build/schema/extract-json-schema.test.js +23 -0
  157. package/build/schema/extract-json-schema.test.js.map +1 -0
  158. package/build/schema/parser.d.ts +28 -0
  159. package/build/schema/parser.js +170 -0
  160. package/build/schema/parser.js.map +1 -0
  161. package/build/schema/parser.test.d.ts +1 -0
  162. package/build/schema/parser.test.js +120 -0
  163. package/build/schema/parser.test.js.map +1 -0
  164. package/build/schema/resolve-schema-lib.d.ts +12 -0
  165. package/build/schema/resolve-schema-lib.js +11 -0
  166. package/build/schema/resolve-schema-lib.js.map +1 -0
  167. package/build/schema/resolve-schema-lib.test.d.ts +1 -0
  168. package/build/schema/resolve-schema-lib.test.js +17 -0
  169. package/build/schema/resolve-schema-lib.test.js.map +1 -0
  170. package/build/schema/typebox.d.ts +1 -1
  171. package/build/schema/typebox.js +2 -20
  172. package/build/schema/typebox.js.map +1 -1
  173. package/build/schema/typebox.test.js +0 -10
  174. package/build/schema/typebox.test.js.map +1 -1
  175. package/build/schema/types.d.ts +8 -0
  176. package/build/schema/types.js +2 -0
  177. package/build/schema/types.js.map +1 -0
  178. package/build/server/context.d.ts +12 -2
  179. package/build/server/context.js +49 -4
  180. package/build/server/context.js.map +1 -1
  181. package/build/server/context.test.js +32 -1
  182. package/build/server/context.test.js.map +1 -1
  183. package/build/server/doc-registry.js +0 -3
  184. package/build/server/doc-registry.js.map +1 -1
  185. package/build/server/doc-registry.test.js +0 -31
  186. package/build/server/doc-registry.test.js.map +1 -1
  187. package/build/server/index.d.ts +0 -1
  188. package/build/server/index.js +0 -1
  189. package/build/server/index.js.map +1 -1
  190. package/build/server/types.d.ts +6 -27
  191. package/build/stack-utils.d.ts +25 -0
  192. package/build/stack-utils.js +95 -0
  193. package/build/stack-utils.js.map +1 -0
  194. package/build/stack-utils.test.d.ts +1 -0
  195. package/build/stack-utils.test.js +80 -0
  196. package/build/stack-utils.test.js.map +1 -0
  197. package/docs/core.md +8 -0
  198. package/docs/http-integrations.md +41 -27
  199. package/package.json +8 -8
  200. package/src/adapters/astro/README.md +2 -0
  201. package/src/adapters/hono/envelope-parity.test.ts +6 -18
  202. package/src/adapters/hono/handlers/http-stream.ts +12 -3
  203. package/src/adapters/hono/handlers/http.ts +12 -3
  204. package/src/adapters/hono/handlers/rpc.ts +12 -3
  205. package/src/adapters/hono/handlers/stream.ts +12 -3
  206. package/src/adapters/hono/index.test.ts +70 -0
  207. package/src/adapters/hono/index.ts +53 -11
  208. package/src/adapters/hono/request.ts +18 -0
  209. package/src/adapters/hono/types.ts +37 -0
  210. package/src/codegen/bin/cli.ts +1 -1
  211. package/src/codegen/constants.ts +2 -2
  212. package/src/codegen/emit/api-route.ts +1 -1
  213. package/src/codegen/emit/scope-file.ts +1 -2
  214. package/src/codegen/emit-errors.integration.test.ts +2 -2
  215. package/src/codegen/resolve-envelope.ts +1 -1
  216. package/src/codegen/test-helpers/run-tsc.ts +0 -1
  217. package/src/core/create-http-stream.ts +20 -13
  218. package/src/core/create-http.test-d.ts +154 -0
  219. package/src/core/create-http.ts +19 -8
  220. package/src/core/create-stream.ts +12 -4
  221. package/src/core/create.test.ts +74 -0
  222. package/src/core/create.ts +10 -5
  223. package/src/core/internal.ts +35 -0
  224. package/src/core/procedures.ts +4 -0
  225. package/src/core/types.ts +99 -4
  226. package/src/exports.ts +6 -4
  227. package/src/schema/typebox.test.ts +0 -12
  228. package/src/schema/typebox.ts +2 -21
  229. package/src/server/context.test.ts +39 -1
  230. package/src/server/context.ts +54 -4
  231. package/src/server/doc-registry.test.ts +0 -34
  232. package/src/server/doc-registry.ts +0 -3
  233. package/src/server/index.ts +0 -1
  234. package/src/server/types.ts +8 -27
  235. package/build/server/spec-version.d.ts +0 -24
  236. package/build/server/spec-version.js +0 -26
  237. package/build/server/spec-version.js.map +0 -1
  238. package/build/version.d.ts +0 -9
  239. package/build/version.js +0 -11
  240. package/build/version.js.map +0 -1
  241. package/docs/doc-envelope-spec-v1.md +0 -296
  242. package/docs/doc-envelope-v1.schema.json +0 -253
  243. package/src/server/spec-version.ts +0 -27
  244. package/src/version.ts +0 -11
@@ -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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"}
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'
@@ -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.1",
3
+ "version": "10.2.0",
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)
@@ -7,9 +7,9 @@ import { dispatchMidStreamError, dispatchPreStreamError } from '../../../server/
7
7
  import { buildStreamRouteDoc } from '../../../server/docs/stream-doc.js'
8
8
  import { resolveFactoryContext } from '../../../server/context.js'
9
9
  import { SseEventSequencer } from '../../../server/sse.js'
10
- import { respondToDispatch } from '../request.js'
10
+ import { 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 installRpcStreamRoute(params: {
15
15
  app: Hono
@@ -18,8 +18,9 @@ export function installRpcStreamRoute(params: {
18
18
  cfg: HonoAppBuilderConfig
19
19
  docs: DocAccumulator
20
20
  streamMode: StreamMode
21
+ builderMiddleware?: HonoRequestMiddleware[]
21
22
  }): void {
22
- const { app, procedure, factoryItem, cfg, docs, streamMode } = params
23
+ const { app, procedure, factoryItem, cfg, docs, streamMode, builderMiddleware } = params
23
24
 
24
25
  const route = buildStreamRouteDoc(
25
26
  procedure,
@@ -29,8 +30,16 @@ export function installRpcStreamRoute(params: {
29
30
  )
30
31
  docs.push(route)
31
32
 
33
+ const combinedMw = [
34
+ ...(builderMiddleware ?? []),
35
+ ...(factoryItem.middleware ?? []),
36
+ ]
37
+
32
38
  const handler = async (c: Context) => {
33
39
  try {
40
+ const early = await runMiddlewareList(c, combinedMw)
41
+ if (early) return early
42
+
34
43
  const context = await resolveFactoryContext(factoryItem.factoryContext, c)
35
44
 
36
45
  const reqParams =
@@ -325,3 +325,73 @@ describe('HonoAppBuilder — per-factory streamMode override', () => {
325
325
  await res.text()
326
326
  })
327
327
  })
328
+
329
+ describe('HonoAppBuilder — middleware (new)', () => {
330
+ test('builder config.middleware and per-register middleware both run; register ones after builder ones', async () => {
331
+ const order: string[] = []
332
+ const builderMw = vi.fn(async (_c: any, next: any) => { order.push('builder'); await next() })
333
+ const regMw = vi.fn(async (_c: any, next: any) => { order.push('register'); await next() })
334
+
335
+ const P = Procedures<object, RPCConfig>()
336
+ P.Create('Ping', { scope: 'p', version: 1 }, async () => ({ ok: true }))
337
+
338
+ const app = new HonoAppBuilder({ middleware: [builderMw] })
339
+ .register(P, () => ({}), { middleware: [regMw] })
340
+ .build()
341
+
342
+ const res = await app.request('/p/ping/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
343
+ expect(res.status).toBe(200)
344
+ expect(order).toEqual(['builder', 'register'])
345
+ })
346
+
347
+ test('middleware can short-circuit by throwing (flows to dispatch + onRequestError)', async () => {
348
+ const onRequestError = vi.fn()
349
+ const P = Procedures<object, RPCConfig>()
350
+ P.Create('Secret', { scope: 's', version: 1 }, async () => ({ secret: true }))
351
+
352
+ const app = new HonoAppBuilder({ onRequestError })
353
+ .register(P, () => ({}), {
354
+ middleware: [async () => { throw Object.assign(new Error('nope'), { status: 401 }) }],
355
+ })
356
+ .build()
357
+
358
+ const res = await app.request('/s/secret/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
359
+ expect(res.status).toBe(500) // default path since no taxonomy matched the thrown Error
360
+ expect(onRequestError).toHaveBeenCalledTimes(1)
361
+ })
362
+
363
+ test('register options.context layers are composed with main factoryContext (later wins)', async () => {
364
+ const P = Procedures<{ tenant?: string; uid: string }, RPCConfig>()
365
+ P.Create('WhoAmI', { scope: 'w', version: 1 }, async (ctx) => ({ tenant: ctx.tenant, uid: ctx.uid }))
366
+
367
+ const base = (c: any) => ({ uid: c.req.header('x-uid') || 'u0' })
368
+ const tenantLayer = { tenant: 't-from-layer' }
369
+
370
+ const app = new HonoAppBuilder()
371
+ .register(P, base, { context: tenantLayer as any })
372
+ .build()
373
+
374
+ const res = await app.request('/w/who-am-i/1', {
375
+ method: 'POST',
376
+ headers: { 'Content-Type': 'application/json', 'x-uid': 'user-xyz' },
377
+ body: '{}',
378
+ })
379
+ expect(await res.json()).toEqual({ tenant: 't-from-layer', uid: 'user-xyz' })
380
+ })
381
+
382
+ test('chainable .use() installs and runs for procedures', async () => {
383
+ const calls: string[] = []
384
+ const mw = async (_c: any, next: any) => { calls.push('used'); await next() }
385
+
386
+ const P = Procedures<object, RPCConfig>()
387
+ P.Create('X', { scope: 'x', version: 1 }, async () => ({}))
388
+
389
+ const b = new HonoAppBuilder()
390
+ b.use(mw)
391
+ b.register(P, () => ({}))
392
+ const app = b.build()
393
+
394
+ await app.request('/x/x/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
395
+ expect(calls).toContain('used')
396
+ })
397
+ })