@beignet/core 0.0.2 → 0.0.4

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 (360) hide show
  1. package/CHANGELOG.md +173 -0
  2. package/README.md +821 -30
  3. package/dist/application/index.d.ts +28 -2
  4. package/dist/application/index.d.ts.map +1 -1
  5. package/dist/application/index.js +140 -12
  6. package/dist/application/index.js.map +1 -1
  7. package/dist/client/client.d.ts +2 -2
  8. package/dist/client/client.d.ts.map +1 -1
  9. package/dist/client/client.js +136 -48
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/error-messages.d.ts +14 -0
  12. package/dist/client/error-messages.d.ts.map +1 -0
  13. package/dist/client/error-messages.js +23 -0
  14. package/dist/client/error-messages.js.map +1 -0
  15. package/dist/client/index.d.ts +8 -4
  16. package/dist/client/index.d.ts.map +1 -1
  17. package/dist/client/index.js +6 -2
  18. package/dist/client/index.js.map +1 -1
  19. package/dist/client/types.d.ts +35 -5
  20. package/dist/client/types.d.ts.map +1 -1
  21. package/dist/client-only.d.ts +8 -0
  22. package/dist/client-only.d.ts.map +1 -0
  23. package/dist/client-only.js +8 -0
  24. package/dist/client-only.js.map +1 -0
  25. package/dist/config/index.d.ts +5 -5
  26. package/dist/config/index.d.ts.map +1 -1
  27. package/dist/config/index.js +2 -2
  28. package/dist/config/index.js.map +1 -1
  29. package/dist/contracts/catalog-errors.d.ts +27 -0
  30. package/dist/contracts/catalog-errors.d.ts.map +1 -0
  31. package/dist/contracts/catalog-errors.js +69 -0
  32. package/dist/contracts/catalog-errors.js.map +1 -0
  33. package/dist/contracts/contract-builder.d.ts +15 -12
  34. package/dist/contracts/contract-builder.d.ts.map +1 -1
  35. package/dist/contracts/contract-builder.js +15 -41
  36. package/dist/contracts/contract-builder.js.map +1 -1
  37. package/dist/contracts/contract-group.d.ts +11 -8
  38. package/dist/contracts/contract-group.d.ts.map +1 -1
  39. package/dist/contracts/contract-group.js +13 -40
  40. package/dist/contracts/contract-group.js.map +1 -1
  41. package/dist/contracts/contract-like.d.ts +1 -1
  42. package/dist/contracts/contract-like.d.ts.map +1 -1
  43. package/dist/contracts/index.d.ts +13 -9
  44. package/dist/contracts/index.d.ts.map +1 -1
  45. package/dist/contracts/index.js +9 -5
  46. package/dist/contracts/index.js.map +1 -1
  47. package/dist/contracts/openapi-meta.d.ts +48 -0
  48. package/dist/contracts/openapi-meta.d.ts.map +1 -1
  49. package/dist/contracts/openapi-meta.js +3 -0
  50. package/dist/contracts/openapi-meta.js.map +1 -1
  51. package/dist/contracts/path-template.d.ts +1 -1
  52. package/dist/contracts/path-template.js +2 -2
  53. package/dist/contracts/path-template.js.map +1 -1
  54. package/dist/contracts/schema-shape.d.ts +37 -0
  55. package/dist/contracts/schema-shape.d.ts.map +1 -0
  56. package/dist/contracts/schema-shape.js +61 -0
  57. package/dist/contracts/schema-shape.js.map +1 -0
  58. package/dist/contracts/success-status.d.ts +32 -0
  59. package/dist/contracts/success-status.d.ts.map +1 -0
  60. package/dist/contracts/success-status.js +18 -0
  61. package/dist/contracts/success-status.js.map +1 -0
  62. package/dist/contracts/types.d.ts +25 -5
  63. package/dist/contracts/types.d.ts.map +1 -1
  64. package/dist/contracts/types.js.map +1 -1
  65. package/dist/contracts/utils.d.ts +1 -1
  66. package/dist/contracts/utils.d.ts.map +1 -1
  67. package/dist/contracts/utils.js +1 -1
  68. package/dist/contracts/utils.js.map +1 -1
  69. package/dist/domain/events.d.ts +1 -1
  70. package/dist/domain/events.d.ts.map +1 -1
  71. package/dist/domain/events.js +1 -1
  72. package/dist/domain/events.js.map +1 -1
  73. package/dist/domain/index.d.ts +3 -3
  74. package/dist/domain/index.d.ts.map +1 -1
  75. package/dist/domain/index.js +3 -3
  76. package/dist/domain/index.js.map +1 -1
  77. package/dist/errors/catalog.d.ts +9 -1
  78. package/dist/errors/catalog.d.ts.map +1 -1
  79. package/dist/errors/catalog.js +7 -1
  80. package/dist/errors/catalog.js.map +1 -1
  81. package/dist/errors/http.d.ts +10 -0
  82. package/dist/errors/http.d.ts.map +1 -1
  83. package/dist/errors/http.js +11 -1
  84. package/dist/errors/http.js.map +1 -1
  85. package/dist/errors/index.d.ts +4 -4
  86. package/dist/errors/index.d.ts.map +1 -1
  87. package/dist/errors/index.js +4 -4
  88. package/dist/errors/index.js.map +1 -1
  89. package/dist/errors/response.d.ts +4 -1
  90. package/dist/errors/response.d.ts.map +1 -1
  91. package/dist/errors/response.js.map +1 -1
  92. package/dist/events/index.d.ts +10 -12
  93. package/dist/events/index.d.ts.map +1 -1
  94. package/dist/events/index.js +10 -10
  95. package/dist/events/index.js.map +1 -1
  96. package/dist/idempotency/index.d.ts +5 -3
  97. package/dist/idempotency/index.d.ts.map +1 -1
  98. package/dist/idempotency/index.js.map +1 -1
  99. package/dist/jobs/index.d.ts +148 -16
  100. package/dist/jobs/index.d.ts.map +1 -1
  101. package/dist/jobs/index.js +174 -14
  102. package/dist/jobs/index.js.map +1 -1
  103. package/dist/notifications/index.d.ts +14 -16
  104. package/dist/notifications/index.d.ts.map +1 -1
  105. package/dist/notifications/index.js +14 -14
  106. package/dist/notifications/index.js.map +1 -1
  107. package/dist/openapi/index.d.ts +8 -3
  108. package/dist/openapi/index.d.ts.map +1 -1
  109. package/dist/openapi/index.js +41 -29
  110. package/dist/openapi/index.js.map +1 -1
  111. package/dist/openapi/schema-introspector.d.ts +37 -0
  112. package/dist/openapi/schema-introspector.d.ts.map +1 -1
  113. package/dist/openapi/schema-introspector.js +23 -17
  114. package/dist/openapi/schema-introspector.js.map +1 -1
  115. package/dist/outbox/index.d.ts +18 -4
  116. package/dist/outbox/index.d.ts.map +1 -1
  117. package/dist/outbox/index.js +104 -4
  118. package/dist/outbox/index.js.map +1 -1
  119. package/dist/ports/audit.d.ts +56 -10
  120. package/dist/ports/audit.d.ts.map +1 -1
  121. package/dist/ports/audit.js +71 -3
  122. package/dist/ports/audit.js.map +1 -1
  123. package/dist/ports/auth.d.ts +92 -0
  124. package/dist/ports/auth.d.ts.map +1 -1
  125. package/dist/ports/auth.js +92 -0
  126. package/dist/ports/auth.js.map +1 -1
  127. package/dist/ports/events.d.ts +2 -2
  128. package/dist/ports/events.d.ts.map +1 -1
  129. package/dist/ports/index.d.ts +62 -33
  130. package/dist/ports/index.d.ts.map +1 -1
  131. package/dist/ports/index.js +28 -34
  132. package/dist/ports/index.js.map +1 -1
  133. package/dist/ports/policy.d.ts +32 -3
  134. package/dist/ports/policy.d.ts.map +1 -1
  135. package/dist/ports/policy.js +13 -2
  136. package/dist/ports/policy.js.map +1 -1
  137. package/dist/ports/testing.d.ts +1030 -2
  138. package/dist/ports/testing.d.ts.map +1 -1
  139. package/dist/ports/testing.js +1031 -1
  140. package/dist/ports/testing.js.map +1 -1
  141. package/dist/ports/unbound.d.ts +21 -0
  142. package/dist/ports/unbound.d.ts.map +1 -0
  143. package/dist/ports/unbound.js +57 -0
  144. package/dist/ports/unbound.js.map +1 -0
  145. package/dist/ports/unit-of-work.d.ts +1 -1
  146. package/dist/ports/unit-of-work.d.ts.map +1 -1
  147. package/dist/ports/unit-of-work.js +1 -1
  148. package/dist/ports/unit-of-work.js.map +1 -1
  149. package/dist/providers/index.d.ts +3 -2
  150. package/dist/providers/index.d.ts.map +1 -1
  151. package/dist/providers/index.js +3 -2
  152. package/dist/providers/index.js.map +1 -1
  153. package/dist/providers/instrumentation.d.ts +46 -5
  154. package/dist/providers/instrumentation.d.ts.map +1 -1
  155. package/dist/providers/instrumentation.js +25 -6
  156. package/dist/providers/instrumentation.js.map +1 -1
  157. package/dist/providers/metadata.d.ts +39 -0
  158. package/dist/providers/metadata.d.ts.map +1 -0
  159. package/dist/providers/metadata.js +169 -0
  160. package/dist/providers/metadata.js.map +1 -0
  161. package/dist/providers/provider.d.ts +114 -9
  162. package/dist/providers/provider.d.ts.map +1 -1
  163. package/dist/providers/provider.js +3 -20
  164. package/dist/providers/provider.js.map +1 -1
  165. package/dist/schedules/index.d.ts +94 -13
  166. package/dist/schedules/index.d.ts.map +1 -1
  167. package/dist/schedules/index.js +66 -12
  168. package/dist/schedules/index.js.map +1 -1
  169. package/dist/server/audit-context.d.ts +29 -0
  170. package/dist/server/audit-context.d.ts.map +1 -0
  171. package/dist/server/audit-context.js +44 -0
  172. package/dist/server/audit-context.js.map +1 -0
  173. package/dist/server/context.d.ts +141 -0
  174. package/dist/server/context.d.ts.map +1 -0
  175. package/dist/server/context.js +39 -0
  176. package/dist/server/context.js.map +1 -0
  177. package/dist/server/contract-like.d.ts +1 -1
  178. package/dist/server/contract-like.d.ts.map +1 -1
  179. package/dist/server/contract-like.js +1 -1
  180. package/dist/server/contract-like.js.map +1 -1
  181. package/dist/server/health.d.ts +2 -2
  182. package/dist/server/health.d.ts.map +1 -1
  183. package/dist/server/hooks/auth.d.ts +89 -65
  184. package/dist/server/hooks/auth.d.ts.map +1 -1
  185. package/dist/server/hooks/auth.js +84 -55
  186. package/dist/server/hooks/auth.js.map +1 -1
  187. package/dist/server/hooks/cors.d.ts +1 -1
  188. package/dist/server/hooks/cors.d.ts.map +1 -1
  189. package/dist/server/hooks/errors.d.ts +2 -2
  190. package/dist/server/hooks/errors.d.ts.map +1 -1
  191. package/dist/server/hooks/errors.js +2 -2
  192. package/dist/server/hooks/errors.js.map +1 -1
  193. package/dist/server/hooks/idempotency.d.ts +78 -0
  194. package/dist/server/hooks/idempotency.d.ts.map +1 -0
  195. package/dist/server/hooks/idempotency.js +154 -0
  196. package/dist/server/hooks/idempotency.js.map +1 -0
  197. package/dist/server/hooks/index.d.ts +8 -7
  198. package/dist/server/hooks/index.d.ts.map +1 -1
  199. package/dist/server/hooks/index.js +6 -5
  200. package/dist/server/hooks/index.js.map +1 -1
  201. package/dist/server/hooks/logging.d.ts +2 -2
  202. package/dist/server/hooks/logging.d.ts.map +1 -1
  203. package/dist/server/hooks/logging.js +1 -1
  204. package/dist/server/hooks/logging.js.map +1 -1
  205. package/dist/server/hooks/rate-limit.d.ts +25 -7
  206. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  207. package/dist/server/hooks/rate-limit.js +47 -12
  208. package/dist/server/hooks/rate-limit.js.map +1 -1
  209. package/dist/server/hooks.d.ts +1 -1
  210. package/dist/server/hooks.d.ts.map +1 -1
  211. package/dist/server/hooks.js +1 -1
  212. package/dist/server/hooks.js.map +1 -1
  213. package/dist/server/http.d.ts +84 -6
  214. package/dist/server/http.d.ts.map +1 -1
  215. package/dist/server/index.d.ts +36 -12
  216. package/dist/server/index.d.ts.map +1 -1
  217. package/dist/server/index.js +24 -8
  218. package/dist/server/index.js.map +1 -1
  219. package/dist/server/instrumentation.d.ts +108 -0
  220. package/dist/server/instrumentation.d.ts.map +1 -0
  221. package/dist/server/instrumentation.js +297 -0
  222. package/dist/server/instrumentation.js.map +1 -0
  223. package/dist/server/openapi.d.ts +3 -3
  224. package/dist/server/openapi.d.ts.map +1 -1
  225. package/dist/server/openapi.js +1 -1
  226. package/dist/server/openapi.js.map +1 -1
  227. package/dist/server/providers/index.d.ts +3 -3
  228. package/dist/server/providers/index.d.ts.map +1 -1
  229. package/dist/server/providers/index.js +3 -3
  230. package/dist/server/providers/index.js.map +1 -1
  231. package/dist/server/providers/loadProviderConfig.d.ts +2 -2
  232. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
  233. package/dist/server/providers/loadProviderConfig.js +2 -2
  234. package/dist/server/providers/loadProviderConfig.js.map +1 -1
  235. package/dist/server/request-context.d.ts +67 -0
  236. package/dist/server/request-context.d.ts.map +1 -0
  237. package/dist/server/request-context.js +79 -0
  238. package/dist/server/request-context.js.map +1 -0
  239. package/dist/server/server-context.d.ts +38 -0
  240. package/dist/server/server-context.d.ts.map +1 -0
  241. package/dist/server/server-context.js +38 -0
  242. package/dist/server/server-context.js.map +1 -0
  243. package/dist/server/server.d.ts +148 -35
  244. package/dist/server/server.d.ts.map +1 -1
  245. package/dist/server/server.js +482 -145
  246. package/dist/server/server.js.map +1 -1
  247. package/dist/server/types.d.ts +2 -2
  248. package/dist/server/types.d.ts.map +1 -1
  249. package/dist/server/types.js +2 -2
  250. package/dist/server/types.js.map +1 -1
  251. package/dist/server/use-case-route.d.ts +263 -0
  252. package/dist/server/use-case-route.d.ts.map +1 -0
  253. package/dist/server/use-case-route.js +77 -0
  254. package/dist/server/use-case-route.js.map +1 -0
  255. package/dist/server-only.d.ts +8 -0
  256. package/dist/server-only.d.ts.map +1 -0
  257. package/dist/server-only.js +8 -0
  258. package/dist/server-only.js.map +1 -0
  259. package/dist/tasks/index.d.ts +139 -0
  260. package/dist/tasks/index.d.ts.map +1 -0
  261. package/dist/tasks/index.js +98 -0
  262. package/dist/tasks/index.js.map +1 -0
  263. package/dist/testing/index.d.ts +611 -5
  264. package/dist/testing/index.d.ts.map +1 -1
  265. package/dist/testing/index.js +434 -4
  266. package/dist/testing/index.js.map +1 -1
  267. package/dist/tracing/index.d.ts +89 -0
  268. package/dist/tracing/index.d.ts.map +1 -0
  269. package/dist/tracing/index.js +101 -0
  270. package/dist/tracing/index.js.map +1 -0
  271. package/dist/uploads/client.d.ts +278 -0
  272. package/dist/uploads/client.d.ts.map +1 -0
  273. package/dist/uploads/client.js +428 -0
  274. package/dist/uploads/client.js.map +1 -0
  275. package/dist/uploads/index.d.ts +361 -0
  276. package/dist/uploads/index.d.ts.map +1 -0
  277. package/dist/uploads/index.js +543 -0
  278. package/dist/uploads/index.js.map +1 -0
  279. package/package.json +34 -3
  280. package/src/application/index.ts +193 -10
  281. package/src/client/client.ts +148 -150
  282. package/src/client/error-messages.ts +35 -0
  283. package/src/client/index.ts +12 -4
  284. package/src/client/types.ts +44 -5
  285. package/src/client-only.ts +7 -0
  286. package/src/config/index.ts +6 -6
  287. package/src/contracts/catalog-errors.ts +115 -0
  288. package/src/contracts/contract-builder.ts +39 -76
  289. package/src/contracts/contract-group.ts +33 -68
  290. package/src/contracts/contract-like.ts +1 -1
  291. package/src/contracts/index.ts +24 -11
  292. package/src/contracts/openapi-meta.ts +55 -0
  293. package/src/contracts/path-template.ts +2 -2
  294. package/src/contracts/schema-shape.ts +75 -0
  295. package/src/contracts/success-status.ts +68 -0
  296. package/src/contracts/types.ts +32 -5
  297. package/src/contracts/utils.ts +5 -2
  298. package/src/domain/events.ts +6 -2
  299. package/src/domain/index.ts +3 -3
  300. package/src/errors/catalog.ts +9 -1
  301. package/src/errors/http.ts +11 -1
  302. package/src/errors/index.ts +4 -4
  303. package/src/errors/response.ts +4 -1
  304. package/src/events/index.ts +12 -26
  305. package/src/idempotency/index.ts +5 -3
  306. package/src/jobs/index.ts +340 -29
  307. package/src/notifications/index.ts +17 -27
  308. package/src/openapi/index.ts +73 -38
  309. package/src/openapi/schema-introspector.ts +68 -17
  310. package/src/outbox/index.ts +151 -6
  311. package/src/ports/audit.ts +120 -11
  312. package/src/ports/auth.ts +132 -0
  313. package/src/ports/events.ts +2 -2
  314. package/src/ports/index.ts +104 -35
  315. package/src/ports/policy.ts +50 -3
  316. package/src/ports/testing.ts +2220 -33
  317. package/src/ports/unbound.ts +64 -0
  318. package/src/ports/unit-of-work.ts +6 -2
  319. package/src/providers/index.ts +16 -3
  320. package/src/providers/instrumentation.ts +93 -8
  321. package/src/providers/metadata.ts +234 -0
  322. package/src/providers/provider.ts +168 -9
  323. package/src/schedules/index.ts +173 -23
  324. package/src/server/audit-context.ts +45 -0
  325. package/src/server/context.ts +224 -0
  326. package/src/server/contract-like.ts +1 -1
  327. package/src/server/health.ts +2 -2
  328. package/src/server/hooks/auth.ts +175 -158
  329. package/src/server/hooks/cors.ts +1 -1
  330. package/src/server/hooks/errors.ts +7 -4
  331. package/src/server/hooks/idempotency.ts +263 -0
  332. package/src/server/hooks/index.ts +15 -12
  333. package/src/server/hooks/logging.ts +3 -3
  334. package/src/server/hooks/rate-limit.ts +85 -17
  335. package/src/server/hooks.ts +1 -1
  336. package/src/server/http.ts +112 -6
  337. package/src/server/index.ts +63 -12
  338. package/src/server/instrumentation.ts +470 -0
  339. package/src/server/openapi.ts +4 -4
  340. package/src/server/providers/index.ts +6 -3
  341. package/src/server/providers/loadProviderConfig.ts +4 -4
  342. package/src/server/request-context.ts +116 -0
  343. package/src/server/server-context.ts +44 -0
  344. package/src/server/server.ts +1045 -229
  345. package/src/server/types.ts +2 -2
  346. package/src/server/use-case-route.ts +430 -0
  347. package/src/server-only.ts +7 -0
  348. package/src/tasks/index.ts +275 -0
  349. package/src/testing/index.ts +1153 -6
  350. package/src/tracing/index.ts +176 -0
  351. package/src/uploads/client.ts +861 -0
  352. package/src/uploads/index.ts +1071 -0
  353. package/dist/ports/mailer.d.ts +0 -6
  354. package/dist/ports/mailer.d.ts.map +0 -1
  355. package/dist/ports/mailer.js +0 -2
  356. package/dist/ports/mailer.js.map +0 -1
  357. package/dist/ports/schedules.d.ts +0 -9
  358. package/dist/ports/schedules.d.ts.map +0 -1
  359. package/dist/ports/schedules.js +0 -2
  360. package/dist/ports/schedules.js.map +0 -1
@@ -4,7 +4,6 @@
4
4
  * OpenAPI 3.1 generation from Beignet contracts
5
5
  */
6
6
 
7
- import { type ZodTypeAny, z } from "zod";
8
7
  import {
9
8
  type AnyContract,
10
9
  type ContractLike,
@@ -13,17 +12,26 @@ import {
13
12
  parsePathTemplate,
14
13
  resolveContract,
15
14
  STANDARD_ERROR_RESPONSE_SCHEMA,
16
- } from "../contracts";
15
+ } from "../contracts/index.js";
16
+ import {
17
+ comparePathParamsToTemplate,
18
+ formatPathParamsMismatch,
19
+ } from "../contracts/schema-shape.js";
17
20
  import {
18
21
  createZodIntrospector,
22
+ createZodSchemaConverter,
23
+ type SchemaConverter,
19
24
  type SchemaIntrospector,
20
- } from "./schema-introspector";
25
+ type SchemaIO,
26
+ } from "./schema-introspector.js";
21
27
 
22
28
  // Re-export the introspector types for consumers who want custom implementations
23
29
  export {
24
30
  createZodIntrospector,
31
+ createZodSchemaConverter,
32
+ type SchemaConverter,
25
33
  type SchemaIntrospector,
26
- } from "./schema-introspector";
34
+ } from "./schema-introspector.js";
27
35
 
28
36
  /**
29
37
  * OpenAPI 3.1 info object.
@@ -338,6 +346,11 @@ export interface OpenAPIGeneratorOptions {
338
346
  * to support other schema libraries.
339
347
  */
340
348
  schemaIntrospector?: SchemaIntrospector;
349
+ /**
350
+ * Schema converters used to turn contract schemas into OpenAPI schemas.
351
+ * Custom converters run before Beignet's default Zod converter.
352
+ */
353
+ schemaConverters?: readonly SchemaConverter[];
341
354
  }
342
355
 
343
356
  /**
@@ -347,10 +360,9 @@ type GeneratorState = {
347
360
  components: ComponentsObject;
348
361
  jsonMediaType: string;
349
362
  introspector: SchemaIntrospector;
363
+ schemaConverters: readonly SchemaConverter[];
350
364
  };
351
365
 
352
- type SchemaIO = "input" | "output";
353
-
354
366
  /**
355
367
  * Contract input accepted by the OpenAPI generator.
356
368
  */
@@ -392,6 +404,10 @@ export function contractsToOpenAPI(
392
404
  components,
393
405
  jsonMediaType: options.jsonMediaType ?? "application/json",
394
406
  introspector: options.schemaIntrospector ?? createZodIntrospector(),
407
+ schemaConverters: [
408
+ ...(options.schemaConverters ?? []),
409
+ createZodSchemaConverter(),
410
+ ],
395
411
  };
396
412
 
397
413
  for (const contract of contracts) {
@@ -456,6 +472,7 @@ function addContractToPaths(
456
472
  addHeaderParams(contract, operation, state);
457
473
  addRequestBody(contract, operation, state);
458
474
  addResponses(contract, operation, state);
475
+ applyOpenAPIOverrides(operation, meta);
459
476
 
460
477
  // Clean up empty parameters array
461
478
  if (operation.parameters?.length === 0) {
@@ -474,6 +491,25 @@ function addContractToPaths(
474
491
  pathItem[methodKey] = operation;
475
492
  }
476
493
 
494
+ function applyOpenAPIOverrides(
495
+ operation: OperationObject,
496
+ meta: AnyContract["metadata"]["openapi"] | undefined,
497
+ ): void {
498
+ if (!meta) return;
499
+
500
+ for (const parameter of meta.parameters ?? []) {
501
+ addParameter(operation, parameter);
502
+ }
503
+
504
+ if (meta.requestBody) {
505
+ operation.requestBody = meta.requestBody;
506
+ }
507
+
508
+ for (const [status, response] of Object.entries(meta.responses ?? {})) {
509
+ operation.responses[status] = response;
510
+ }
511
+ }
512
+
477
513
  function addParameter(
478
514
  operation: OperationObject,
479
515
  parameter: ParameterObject,
@@ -525,20 +561,12 @@ function addPathParams(
525
561
  return;
526
562
  }
527
563
 
528
- const shapeKeys = Object.keys(shape);
529
- const missingKeys = pathKeys.filter((key) => !shapeKeys.includes(key));
530
- const extraKeys = shapeKeys.filter((key) => !pathKeys.includes(key));
564
+ const { missingKeys, extraKeys } = comparePathParamsToTemplate({
565
+ pathKeys,
566
+ shapeKeys: Object.keys(shape),
567
+ });
531
568
  if (missingKeys.length > 0 || extraKeys.length > 0) {
532
- const details = [
533
- missingKeys.length > 0
534
- ? `missing pathParams keys: ${missingKeys.join(", ")}`
535
- : undefined,
536
- extraKeys.length > 0
537
- ? `extra pathParams keys: ${extraKeys.join(", ")}`
538
- : undefined,
539
- ]
540
- .filter(Boolean)
541
- .join("; ");
569
+ const details = formatPathParamsMismatch({ missingKeys, extraKeys });
542
570
  throw new Error(
543
571
  `Path parameters for contract "${contract.name}" must match "${contract.path}" (${details}).`,
544
572
  );
@@ -546,8 +574,8 @@ function addPathParams(
546
574
 
547
575
  for (const key of pathKeys) {
548
576
  const field = shape[key];
549
- const paramSchemaRef = zodToSchemaRef(
550
- field as ZodTypeAny,
577
+ const paramSchemaRef = schemaToConvertedSchemaRef(
578
+ field,
551
579
  `${contract.name}_path_${key}`,
552
580
  state,
553
581
  "input",
@@ -593,8 +621,8 @@ function addQueryParams(
593
621
  description = state.introspector.getDescription(field);
594
622
  }
595
623
 
596
- const paramSchemaRef = zodToSchemaRef(
597
- field as ZodTypeAny,
624
+ const paramSchemaRef = schemaToConvertedSchemaRef(
625
+ field,
598
626
  `${contract.name}_query_${key}`,
599
627
  state,
600
628
  "input",
@@ -630,8 +658,8 @@ function addHeaderParams(
630
658
  const optional = state.introspector.isOptional(originalField);
631
659
  const description = state.introspector.getDescription(originalField);
632
660
 
633
- const paramSchemaRef = zodToSchemaRef(
634
- originalField as ZodTypeAny,
661
+ const paramSchemaRef = schemaToConvertedSchemaRef(
662
+ originalField,
635
663
  `${contract.name}_header_${key}`,
636
664
  state,
637
665
  "input",
@@ -663,8 +691,8 @@ function addRequestBody(
663
691
  );
664
692
  }
665
693
 
666
- const schemaRef = zodToSchemaRef(
667
- contract.body as ZodTypeAny,
694
+ const schemaRef = schemaToConvertedSchemaRef(
695
+ contract.body,
668
696
  `${contract.name}_body`,
669
697
  state,
670
698
  "input",
@@ -889,10 +917,10 @@ function normalizeExampleKey(key: string): string {
889
917
  }
890
918
 
891
919
  /**
892
- * Convert a Zod schema to a JSON Schema reference, registering it in components
920
+ * Convert a validation schema to a JSON Schema reference, registering it in components.
893
921
  */
894
- function zodToSchemaRef(
895
- schema: ZodTypeAny,
922
+ function schemaToConvertedSchemaRef(
923
+ schema: unknown,
896
924
  nameHint: string,
897
925
  state: GeneratorState,
898
926
  io: SchemaIO,
@@ -905,14 +933,21 @@ function zodToSchemaRef(
905
933
  const schemaName = normalizeSchemaName(nameHint, state);
906
934
 
907
935
  if (!state.components.schemas[schemaName]) {
908
- const jsonSchema = z.toJSONSchema(schema, {
909
- target: "draft-2020-12",
910
- unrepresentable: "any",
911
- io,
912
- }) as SchemaObject;
936
+ const converter = state.schemaConverters.find((candidate) =>
937
+ candidate.canConvert(schema),
938
+ );
939
+
940
+ if (!converter) {
941
+ throw new Error(
942
+ `Unable to convert schema "${nameHint}" to OpenAPI. ` +
943
+ "Pass a schemaConverters entry to contractsToOpenAPI for this schema library.",
944
+ );
945
+ }
913
946
 
914
- // Remove $schema as it's not needed in OpenAPI
915
- delete jsonSchema.$schema;
947
+ const jsonSchema = converter.toJSONSchema(schema, {
948
+ nameHint,
949
+ io,
950
+ });
916
951
 
917
952
  state.components.schemas[schemaName] = jsonSchema;
918
953
  }
@@ -930,7 +965,7 @@ function schemaToSchemaRef(
930
965
  return standardErrorResponseSchemaRef(nameHint, state);
931
966
  }
932
967
 
933
- return zodToSchemaRef(schema as ZodTypeAny, nameHint, state, io);
968
+ return schemaToConvertedSchemaRef(schema, nameHint, state, io);
934
969
  }
935
970
 
936
971
  function standardErrorResponseSchemaRef(
@@ -1,3 +1,45 @@
1
+ import { type ZodTypeAny, z } from "zod";
2
+ import { getObjectSchemaShape } from "../contracts/schema-shape.js";
3
+
4
+ export type SchemaIO = "input" | "output";
5
+
6
+ type ConvertedSchemaObject = Record<string, unknown>;
7
+
8
+ /**
9
+ * Context passed to an OpenAPI schema converter.
10
+ */
11
+ export type SchemaConverterContext = {
12
+ /**
13
+ * Whether the schema is documenting request input or response output.
14
+ */
15
+ io: SchemaIO;
16
+ /**
17
+ * Component name hint Beignet will use when registering the converted schema.
18
+ */
19
+ nameHint: string;
20
+ };
21
+
22
+ /**
23
+ * Converts a validation schema into an OpenAPI-compatible JSON Schema object.
24
+ */
25
+ export interface SchemaConverter {
26
+ /**
27
+ * Human-readable converter name used in diagnostics.
28
+ */
29
+ name: string;
30
+ /**
31
+ * Return true when this converter owns the schema value.
32
+ */
33
+ canConvert(schema: unknown): boolean;
34
+ /**
35
+ * Convert the schema into an OpenAPI-compatible JSON Schema object.
36
+ */
37
+ toJSONSchema(
38
+ schema: unknown,
39
+ context: SchemaConverterContext,
40
+ ): ConvertedSchemaObject;
41
+ }
42
+
1
43
  /**
2
44
  * Schema introspection adapter.
3
45
  *
@@ -31,6 +73,31 @@ export interface SchemaIntrospector {
31
73
  unwrapOptional(schema: unknown): unknown;
32
74
  }
33
75
 
76
+ /**
77
+ * Create the default schema converter for Zod schemas.
78
+ */
79
+ export function createZodSchemaConverter(): SchemaConverter {
80
+ return {
81
+ name: "zod",
82
+ canConvert(schema: unknown): boolean {
83
+ return schema instanceof z.ZodType;
84
+ },
85
+ toJSONSchema(
86
+ schema: unknown,
87
+ context: SchemaConverterContext,
88
+ ): ConvertedSchemaObject {
89
+ const jsonSchema = z.toJSONSchema(schema as ZodTypeAny, {
90
+ target: "draft-2020-12",
91
+ unrepresentable: "any",
92
+ io: context.io,
93
+ }) as ConvertedSchemaObject;
94
+
95
+ delete jsonSchema.$schema;
96
+ return jsonSchema;
97
+ },
98
+ };
99
+ }
100
+
34
101
  /**
35
102
  * Create a schema introspector for Zod schemas.
36
103
  *
@@ -41,23 +108,7 @@ export interface SchemaIntrospector {
41
108
  export function createZodIntrospector(): SchemaIntrospector {
42
109
  return {
43
110
  getShape(schema: unknown): Record<string, unknown> | undefined {
44
- if (!schema || typeof schema !== "object") return undefined;
45
-
46
- const schemaDef = (schema as Record<string, unknown>)?._def;
47
- if (!schemaDef || typeof schemaDef !== "object") return undefined;
48
-
49
- if (!("shape" in schemaDef)) return undefined;
50
-
51
- let shape: unknown;
52
- if (typeof schemaDef.shape === "function") {
53
- shape = schemaDef.shape();
54
- } else if (typeof schemaDef.shape === "object") {
55
- shape = schemaDef.shape;
56
- }
57
-
58
- if (!shape || typeof shape !== "object") return undefined;
59
-
60
- return shape as Record<string, unknown>;
111
+ return getObjectSchemaShape(schema);
61
112
  },
62
113
 
63
114
  getDescription(schema: unknown): string | undefined {
@@ -9,13 +9,25 @@ import {
9
9
  type EventPayloadDef,
10
10
  type InferEventPayload,
11
11
  parseEventPayload,
12
- } from "../events";
13
- import { type InferJobPayload, type JobDef, parseJobPayload } from "../jobs";
14
- import type { JobDispatcherPort } from "../ports/events";
12
+ } from "../events/index.js";
13
+ import {
14
+ getJobRetryDelayMs,
15
+ getJobRetryMaxAttempts,
16
+ type InferJobPayload,
17
+ type JobDef,
18
+ parseJobPayload,
19
+ shouldRetryJob,
20
+ } from "../jobs/index.js";
21
+ import type { JobDispatcherPort } from "../ports/events.js";
15
22
  import type {
16
23
  BufferedDomainEventRecorder,
17
24
  DomainEventRecorderPort,
18
- } from "../ports/unit-of-work";
25
+ } from "../ports/unit-of-work.js";
26
+ import {
27
+ type BaseProviderInstrumentationEvent,
28
+ createProviderInstrumentation,
29
+ type ProviderInstrumentationTarget,
30
+ } from "../providers/index.js";
19
31
 
20
32
  /**
21
33
  * Value or promise of that value.
@@ -354,6 +366,14 @@ export interface DefineOutboxRegistryInput {
354
366
  jobs?: readonly JobDef[];
355
367
  }
356
368
 
369
+ /**
370
+ * Correlation fields attached to outbox instrumentation events.
371
+ */
372
+ export type OutboxInstrumentationContext = Pick<
373
+ BaseProviderInstrumentationEvent,
374
+ "requestId" | "traceId" | "spanId" | "parentSpanId" | "traceparent"
375
+ >;
376
+
357
377
  /**
358
378
  * Options for draining one outbox batch.
359
379
  */
@@ -401,6 +421,15 @@ export interface DrainOutboxOptions {
401
421
  error: unknown;
402
422
  now: Date;
403
423
  }) => number);
424
+ /**
425
+ * Optional instrumentation target for delivery, retry, and dead-letter
426
+ * visibility.
427
+ */
428
+ instrumentation?: ProviderInstrumentationTarget;
429
+ /**
430
+ * Optional correlation fields attached to outbox instrumentation events.
431
+ */
432
+ instrumentationContext?: OutboxInstrumentationContext;
404
433
  /**
405
434
  * Observer called when delivery fails. Observer failures are ignored so the
406
435
  * original delivery failure still controls retry/dead-letter behavior.
@@ -852,7 +881,7 @@ export async function enqueueJob<J extends JobDef>(
852
881
  name: job.name,
853
882
  payload: toOutboxJsonValue(parsed),
854
883
  availableAt: options.availableAt,
855
- maxAttempts: options.maxAttempts,
884
+ maxAttempts: options.maxAttempts ?? getJobRetryMaxAttempts(job.retry),
856
885
  });
857
886
  }
858
887
 
@@ -906,9 +935,52 @@ function resolveRetryDelayMs(
906
935
  return options.retryDelayMs;
907
936
  }
908
937
 
938
+ if (message.kind === "job") {
939
+ const job = options.registry.jobs.get(message.name);
940
+ if (job?.retry) {
941
+ return getJobRetryDelayMs(job.retry, {
942
+ attempt: message.attempts,
943
+ error,
944
+ jobName: message.name,
945
+ });
946
+ }
947
+ }
948
+
909
949
  return Math.min(60_000, 1000 * 2 ** Math.max(0, message.attempts - 1));
910
950
  }
911
951
 
952
+ function shouldRetryOutboxMessage(
953
+ options: DrainOutboxOptions,
954
+ message: ClaimedOutboxMessage,
955
+ error: unknown,
956
+ ): boolean {
957
+ if (message.kind !== "job") {
958
+ return message.attempts < message.maxAttempts;
959
+ }
960
+
961
+ const job = options.registry.jobs.get(message.name);
962
+ return shouldRetryJob(job?.retry, {
963
+ attempt: message.attempts,
964
+ error,
965
+ jobName: message.name,
966
+ maxAttempts: message.maxAttempts,
967
+ });
968
+ }
969
+
970
+ function outboxInstrumentationDetails(
971
+ message: ClaimedOutboxMessage,
972
+ details?: Record<string, unknown>,
973
+ ): Record<string, unknown> {
974
+ return {
975
+ attempt: message.attempts,
976
+ maxAttempts: message.maxAttempts,
977
+ messageId: message.id,
978
+ messageKind: message.kind,
979
+ messageName: message.name,
980
+ ...details,
981
+ };
982
+ }
983
+
912
984
  async function deliverOutboxMessage(
913
985
  options: DrainOutboxOptions,
914
986
  message: ClaimedOutboxMessage,
@@ -962,6 +1034,20 @@ export async function drainOutbox(
962
1034
  ): Promise<DrainOutboxResult> {
963
1035
  const batchSize = options.batchSize ?? 100;
964
1036
  assertPositiveInteger("batchSize", batchSize);
1037
+ const instrumentation = createProviderInstrumentation(
1038
+ options.instrumentation,
1039
+ {
1040
+ providerName: "outbox",
1041
+ watcher: "outbox",
1042
+ },
1043
+ );
1044
+ const jobInstrumentation = createProviderInstrumentation(
1045
+ options.instrumentation,
1046
+ {
1047
+ providerName: "outbox",
1048
+ watcher: "jobs",
1049
+ },
1050
+ );
965
1051
 
966
1052
  const now = options.now ?? new Date();
967
1053
  const messages = await options.outbox.claimBatch({
@@ -984,6 +1070,15 @@ export async function drainOutbox(
984
1070
  claimToken: message.claimToken,
985
1071
  now,
986
1072
  });
1073
+ instrumentation.record({
1074
+ type: "outbox",
1075
+ ...options.instrumentationContext,
1076
+ messageId: message.id,
1077
+ messageKind: message.kind,
1078
+ messageName: message.name,
1079
+ status: "delivered",
1080
+ details: outboxInstrumentationDetails(message),
1081
+ });
987
1082
  result.delivered += 1;
988
1083
  } catch (error) {
989
1084
  try {
@@ -992,7 +1087,8 @@ export async function drainOutbox(
992
1087
  // Preserve the delivery failure path so the message is retried or
993
1088
  // dead-lettered even if the observer fails.
994
1089
  }
995
- const deadLetter = message.attempts >= message.maxAttempts;
1090
+ const shouldRetry = shouldRetryOutboxMessage(options, message, error);
1091
+ const deadLetter = !shouldRetry;
996
1092
  const retryDelayMs = deadLetter
997
1093
  ? 0
998
1094
  : resolveRetryDelayMs(options, message, error, now);
@@ -1008,8 +1104,57 @@ export async function drainOutbox(
1008
1104
  });
1009
1105
 
1010
1106
  if (deadLetter) {
1107
+ instrumentation.record({
1108
+ type: "outbox",
1109
+ ...options.instrumentationContext,
1110
+ messageId: message.id,
1111
+ messageKind: message.kind,
1112
+ messageName: message.name,
1113
+ status: "deadLettered",
1114
+ details: outboxInstrumentationDetails(message, {
1115
+ error: serializeOutboxError(error),
1116
+ }),
1117
+ });
1118
+ if (message.kind === "job") {
1119
+ jobInstrumentation.record({
1120
+ type: "job",
1121
+ ...options.instrumentationContext,
1122
+ jobName: message.name,
1123
+ status: "deadLettered",
1124
+ details: outboxInstrumentationDetails(message, {
1125
+ error: serializeOutboxError(error),
1126
+ }),
1127
+ });
1128
+ }
1011
1129
  result.deadLettered += 1;
1012
1130
  } else {
1131
+ const retryAt = new Date(now.getTime() + retryDelayMs).toISOString();
1132
+ instrumentation.record({
1133
+ type: "outbox",
1134
+ ...options.instrumentationContext,
1135
+ messageId: message.id,
1136
+ messageKind: message.kind,
1137
+ messageName: message.name,
1138
+ status: "retryScheduled",
1139
+ details: outboxInstrumentationDetails(message, {
1140
+ retryDelayMs,
1141
+ retryAt,
1142
+ error: serializeOutboxError(error),
1143
+ }),
1144
+ });
1145
+ if (message.kind === "job") {
1146
+ jobInstrumentation.record({
1147
+ type: "job",
1148
+ ...options.instrumentationContext,
1149
+ jobName: message.name,
1150
+ status: "retryScheduled",
1151
+ details: outboxInstrumentationDetails(message, {
1152
+ retryDelayMs,
1153
+ retryAt,
1154
+ error: serializeOutboxError(error),
1155
+ }),
1156
+ });
1157
+ }
1013
1158
  result.retried += 1;
1014
1159
  }
1015
1160
  }