@beignet/core 0.0.3 → 0.0.5

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 +159 -0
  2. package/README.md +792 -50
  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 +12 -14
  100. package/dist/jobs/index.d.ts.map +1 -1
  101. package/dist/jobs/index.js +13 -13
  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 +15 -6
  116. package/dist/outbox/index.d.ts.map +1 -1
  117. package/dist/outbox/index.js +60 -16
  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 +45 -4
  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 +49 -10
  184. package/dist/server/hooks/auth.d.ts.map +1 -1
  185. package/dist/server/hooks/auth.js +77 -37
  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 +61 -35
  214. package/dist/server/http.d.ts.map +1 -1
  215. package/dist/server/http.js +1 -20
  216. package/dist/server/http.js.map +1 -1
  217. package/dist/server/index.d.ts +36 -12
  218. package/dist/server/index.d.ts.map +1 -1
  219. package/dist/server/index.js +24 -8
  220. package/dist/server/index.js.map +1 -1
  221. package/dist/server/instrumentation.d.ts +108 -0
  222. package/dist/server/instrumentation.d.ts.map +1 -0
  223. package/dist/server/instrumentation.js +297 -0
  224. package/dist/server/instrumentation.js.map +1 -0
  225. package/dist/server/openapi.d.ts +3 -3
  226. package/dist/server/openapi.d.ts.map +1 -1
  227. package/dist/server/openapi.js +1 -1
  228. package/dist/server/openapi.js.map +1 -1
  229. package/dist/server/providers/index.d.ts +3 -3
  230. package/dist/server/providers/index.d.ts.map +1 -1
  231. package/dist/server/providers/index.js +3 -3
  232. package/dist/server/providers/index.js.map +1 -1
  233. package/dist/server/providers/loadProviderConfig.d.ts +2 -2
  234. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
  235. package/dist/server/providers/loadProviderConfig.js +2 -2
  236. package/dist/server/providers/loadProviderConfig.js.map +1 -1
  237. package/dist/server/request-context.d.ts +67 -0
  238. package/dist/server/request-context.d.ts.map +1 -0
  239. package/dist/server/request-context.js +79 -0
  240. package/dist/server/request-context.js.map +1 -0
  241. package/dist/server/server-context.d.ts +38 -0
  242. package/dist/server/server-context.d.ts.map +1 -0
  243. package/dist/server/server-context.js +38 -0
  244. package/dist/server/server-context.js.map +1 -0
  245. package/dist/server/server.d.ts +105 -33
  246. package/dist/server/server.d.ts.map +1 -1
  247. package/dist/server/server.js +434 -118
  248. package/dist/server/server.js.map +1 -1
  249. package/dist/server/types.d.ts +2 -2
  250. package/dist/server/types.d.ts.map +1 -1
  251. package/dist/server/types.js +2 -2
  252. package/dist/server/types.js.map +1 -1
  253. package/dist/server/use-case-route.d.ts +263 -0
  254. package/dist/server/use-case-route.d.ts.map +1 -0
  255. package/dist/server/use-case-route.js +77 -0
  256. package/dist/server/use-case-route.js.map +1 -0
  257. package/dist/server-only.d.ts +8 -0
  258. package/dist/server-only.d.ts.map +1 -0
  259. package/dist/server-only.js +8 -0
  260. package/dist/server-only.js.map +1 -0
  261. package/dist/tasks/index.d.ts +139 -0
  262. package/dist/tasks/index.d.ts.map +1 -0
  263. package/dist/tasks/index.js +98 -0
  264. package/dist/tasks/index.js.map +1 -0
  265. package/dist/testing/index.d.ts +607 -5
  266. package/dist/testing/index.d.ts.map +1 -1
  267. package/dist/testing/index.js +426 -4
  268. package/dist/testing/index.js.map +1 -1
  269. package/dist/tracing/index.d.ts +89 -0
  270. package/dist/tracing/index.d.ts.map +1 -0
  271. package/dist/tracing/index.js +101 -0
  272. package/dist/tracing/index.js.map +1 -0
  273. package/dist/uploads/client.d.ts +1 -1
  274. package/dist/uploads/client.d.ts.map +1 -1
  275. package/dist/uploads/index.d.ts +2 -2
  276. package/dist/uploads/index.d.ts.map +1 -1
  277. package/dist/uploads/index.js +1 -1
  278. package/dist/uploads/index.js.map +1 -1
  279. package/package.json +24 -2
  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 +14 -24
  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 +84 -19
  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 +86 -7
  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 +141 -51
  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 +14 -7
  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 +78 -51
  337. package/src/server/index.ts +62 -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 +886 -238
  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 +1142 -6
  350. package/src/tracing/index.ts +176 -0
  351. package/src/uploads/client.ts +1 -1
  352. package/src/uploads/index.ts +7 -3
  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,7 +9,7 @@ import {
9
9
  type EventPayloadDef,
10
10
  type InferEventPayload,
11
11
  parseEventPayload,
12
- } from "../events";
12
+ } from "../events/index.js";
13
13
  import {
14
14
  getJobRetryDelayMs,
15
15
  getJobRetryMaxAttempts,
@@ -17,16 +17,17 @@ import {
17
17
  type JobDef,
18
18
  parseJobPayload,
19
19
  shouldRetryJob,
20
- } from "../jobs";
21
- import type { JobDispatcherPort } from "../ports/events";
20
+ } from "../jobs/index.js";
21
+ import type { JobDispatcherPort } from "../ports/events.js";
22
22
  import type {
23
23
  BufferedDomainEventRecorder,
24
24
  DomainEventRecorderPort,
25
- } from "../ports/unit-of-work";
25
+ } from "../ports/unit-of-work.js";
26
26
  import {
27
+ type BaseProviderInstrumentationEvent,
27
28
  createProviderInstrumentation,
28
29
  type ProviderInstrumentationTarget,
29
- } from "../providers";
30
+ } from "../providers/index.js";
30
31
 
31
32
  /**
32
33
  * Value or promise of that value.
@@ -365,6 +366,14 @@ export interface DefineOutboxRegistryInput {
365
366
  jobs?: readonly JobDef[];
366
367
  }
367
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
+
368
377
  /**
369
378
  * Options for draining one outbox batch.
370
379
  */
@@ -413,9 +422,14 @@ export interface DrainOutboxOptions {
413
422
  now: Date;
414
423
  }) => number);
415
424
  /**
416
- * Optional instrumentation target for retry and dead-letter visibility.
425
+ * Optional instrumentation target for delivery, retry, and dead-letter
426
+ * visibility.
417
427
  */
418
428
  instrumentation?: ProviderInstrumentationTarget;
429
+ /**
430
+ * Optional correlation fields attached to outbox instrumentation events.
431
+ */
432
+ instrumentationContext?: OutboxInstrumentationContext;
419
433
  /**
420
434
  * Observer called when delivery fails. Observer failures are ignored so the
421
435
  * original delivery failure still controls retry/dead-letter behavior.
@@ -953,6 +967,20 @@ function shouldRetryOutboxMessage(
953
967
  });
954
968
  }
955
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
+
956
984
  async function deliverOutboxMessage(
957
985
  options: DrainOutboxOptions,
958
986
  message: ClaimedOutboxMessage,
@@ -1007,6 +1035,13 @@ export async function drainOutbox(
1007
1035
  const batchSize = options.batchSize ?? 100;
1008
1036
  assertPositiveInteger("batchSize", batchSize);
1009
1037
  const instrumentation = createProviderInstrumentation(
1038
+ options.instrumentation,
1039
+ {
1040
+ providerName: "outbox",
1041
+ watcher: "outbox",
1042
+ },
1043
+ );
1044
+ const jobInstrumentation = createProviderInstrumentation(
1010
1045
  options.instrumentation,
1011
1046
  {
1012
1047
  providerName: "outbox",
@@ -1035,6 +1070,15 @@ export async function drainOutbox(
1035
1070
  claimToken: message.claimToken,
1036
1071
  now,
1037
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
+ });
1038
1082
  result.delivered += 1;
1039
1083
  } catch (error) {
1040
1084
  try {
@@ -1060,34 +1104,55 @@ export async function drainOutbox(
1060
1104
  });
1061
1105
 
1062
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
+ });
1063
1118
  if (message.kind === "job") {
1064
- instrumentation.record({
1119
+ jobInstrumentation.record({
1065
1120
  type: "job",
1121
+ ...options.instrumentationContext,
1066
1122
  jobName: message.name,
1067
1123
  status: "deadLettered",
1068
- details: {
1069
- attempt: message.attempts,
1070
- maxAttempts: message.maxAttempts,
1071
- messageId: message.id,
1124
+ details: outboxInstrumentationDetails(message, {
1072
1125
  error: serializeOutboxError(error),
1073
- },
1126
+ }),
1074
1127
  });
1075
1128
  }
1076
1129
  result.deadLettered += 1;
1077
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
+ });
1078
1145
  if (message.kind === "job") {
1079
- instrumentation.record({
1146
+ jobInstrumentation.record({
1080
1147
  type: "job",
1148
+ ...options.instrumentationContext,
1081
1149
  jobName: message.name,
1082
1150
  status: "retryScheduled",
1083
- details: {
1084
- attempt: message.attempts,
1085
- maxAttempts: message.maxAttempts,
1086
- messageId: message.id,
1151
+ details: outboxInstrumentationDetails(message, {
1087
1152
  retryDelayMs,
1088
- retryAt: new Date(now.getTime() + retryDelayMs).toISOString(),
1153
+ retryAt,
1089
1154
  error: serializeOutboxError(error),
1090
- },
1155
+ }),
1091
1156
  });
1092
1157
  }
1093
1158
  result.retried += 1;
@@ -1,4 +1,8 @@
1
- import { redactValue } from "./redaction";
1
+ import {
2
+ type ProviderInstrumentationTarget,
3
+ resolveProviderInstrumentationPort,
4
+ } from "../providers/instrumentation.js";
5
+ import { redactValue } from "./redaction.js";
2
6
 
3
7
  /**
4
8
  * The normalized category of actor that caused application activity.
@@ -111,10 +115,11 @@ export interface ActivityResource {
111
115
  /**
112
116
  * A normalized audit/activity log entry.
113
117
  *
114
- * Application code usually creates these through an app-owned helper such as
115
- * `auditEntry(ctx, { action, resource })` so actor, tenant, request ID, and
116
- * trace ID are copied from context consistently. Durability depends on the
117
- * `AuditLogPort` implementation.
118
+ * Application code usually records entries through an audit port wrapped with
119
+ * `createAmbientAuditLog(...)` from `@beignet/core/server`, which fills
120
+ * missing actor, tenant, request ID, and trace ID fields from the ambient
121
+ * request context at record time. Durability depends on the `AuditLogPort`
122
+ * implementation.
118
123
  */
119
124
  export interface AuditLogEntry {
120
125
  /**
@@ -165,14 +170,18 @@ export interface AuditLogEntry {
165
170
  /**
166
171
  * Input accepted by `AuditLogPort.record(...)`.
167
172
  *
168
- * `occurredAt` and `outcome` are optional at call sites. Adapters that store
169
- * audit entries should call `normalizeAuditLogEntry(...)` before persistence or
170
- * otherwise apply equivalent defaults.
173
+ * `actor`, `occurredAt`, and `outcome` are optional at call sites. Wrappers
174
+ * such as `createAmbientAuditLog(...)` fill a missing actor from the ambient
175
+ * request context. Adapters that store audit entries should call
176
+ * `normalizeAuditLogEntry(...)` before persistence or otherwise apply
177
+ * equivalent defaults; entries without an actor normalize to an anonymous
178
+ * actor.
171
179
  */
172
180
  export type AuditLogEntryInput = Omit<
173
181
  AuditLogEntry,
174
- "occurredAt" | "outcome"
182
+ "actor" | "occurredAt" | "outcome"
175
183
  > & {
184
+ actor?: ActivityActor;
176
185
  occurredAt?: Date;
177
186
  outcome?: AuditOutcome;
178
187
  };
@@ -259,7 +268,7 @@ export function createServiceActor(
259
268
  /**
260
269
  * Create a system actor descriptor for framework or app-owned background work.
261
270
  *
262
- * Use this for scheduled tasks, scripts, maintenance jobs, and other work that
271
+ * Use this for schedules, scripts, maintenance jobs, and other work that
263
272
  * is not directly caused by a user or external service.
264
273
  *
265
274
  * @example
@@ -334,13 +343,15 @@ export function createTenant(
334
343
  * Fill default audit fields for an input entry.
335
344
  *
336
345
  * @param entry - Partial audit entry accepted by `AuditLogPort.record(...)`.
337
- * @returns A complete audit entry with `occurredAt` and `outcome` populated.
346
+ * @returns A complete audit entry with `actor`, `occurredAt`, and `outcome`
347
+ * populated. Entries without an actor default to an anonymous actor.
338
348
  */
339
349
  export function normalizeAuditLogEntry(
340
350
  entry: AuditLogEntryInput,
341
351
  ): AuditLogEntry {
342
352
  return {
343
353
  ...entry,
354
+ actor: entry.actor ?? createAnonymousActor(),
344
355
  occurredAt: entry.occurredAt ?? new Date(),
345
356
  outcome: entry.outcome ?? "success",
346
357
  };
@@ -409,6 +420,104 @@ export function createRedactedAuditLog(
409
420
  };
410
421
  }
411
422
 
423
+ /**
424
+ * Options for wrapping an audit log with instrumentation emission.
425
+ */
426
+ export interface InstrumentedAuditLogOptions {
427
+ /**
428
+ * Durable audit log to write first.
429
+ */
430
+ audit: AuditLogPort;
431
+ /**
432
+ * Instrumentation sink, port, or ports object. Pass the app ports object so
433
+ * the sink (`ports.instrumentation`, then `ports.devtools`) is resolved
434
+ * lazily on each write and observes provider startup order.
435
+ */
436
+ instrumentation?: ProviderInstrumentationTarget;
437
+ /**
438
+ * Whether to emit instrumentation events. Defaults to true.
439
+ */
440
+ emit?: boolean;
441
+ /**
442
+ * Optional app-owned redactor applied after Beignet's audit redaction.
443
+ */
444
+ redact?: (entry: AuditLogEntry) => AuditLogEntry;
445
+ }
446
+
447
+ function prepareInstrumentedEntry(
448
+ input: AuditLogEntryInput,
449
+ redact?: (entry: AuditLogEntry) => AuditLogEntry,
450
+ ): AuditLogEntry {
451
+ const redacted = redactAuditLogEntry(normalizeAuditLogEntry(input));
452
+ return redact ? redact(redacted) : redacted;
453
+ }
454
+
455
+ function auditSummary(entry: AuditLogEntry): string {
456
+ const resource = entry.resource?.id
457
+ ? `${entry.resource.type}:${entry.resource.id}`
458
+ : entry.resource?.type;
459
+ const outcome = entry.outcome === "failure" ? "failed" : "succeeded";
460
+ return resource
461
+ ? `${entry.action} ${outcome} for ${resource}`
462
+ : `${entry.action} ${outcome}`;
463
+ }
464
+
465
+ /**
466
+ * Wrap an audit log so durable audit writes also appear in instrumentation
467
+ * sinks such as devtools.
468
+ *
469
+ * Instrumentation failures are ignored so audit persistence remains the
470
+ * source of truth.
471
+ *
472
+ * @example
473
+ * ```ts
474
+ * const audit = createInstrumentedAuditLog({
475
+ * audit: createDrizzleAuditLog(db),
476
+ * instrumentation: ports,
477
+ * });
478
+ * ```
479
+ */
480
+ export function createInstrumentedAuditLog(
481
+ options: InstrumentedAuditLogOptions,
482
+ ): AuditLogPort {
483
+ return {
484
+ async record(input) {
485
+ const entry = prepareInstrumentedEntry(input, options.redact);
486
+
487
+ await options.audit.record(entry);
488
+
489
+ if (options.emit === false) return;
490
+
491
+ const port = resolveProviderInstrumentationPort(options.instrumentation);
492
+ if (!port) return;
493
+
494
+ try {
495
+ port.record({
496
+ type: "custom",
497
+ watcher: "audit",
498
+ name: entry.action,
499
+ label: "Audit",
500
+ summary: auditSummary(entry),
501
+ requestId: entry.requestId,
502
+ traceId: entry.traceId,
503
+ details: {
504
+ action: entry.action,
505
+ actor: entry.actor,
506
+ tenant: entry.tenant,
507
+ resource: entry.resource,
508
+ outcome: entry.outcome,
509
+ message: entry.message,
510
+ metadata: entry.metadata,
511
+ occurredAt: entry.occurredAt.toISOString(),
512
+ },
513
+ });
514
+ } catch {
515
+ // Instrumentation is an observer; durable audit writes must not depend on it.
516
+ }
517
+ },
518
+ };
519
+ }
520
+
412
521
  /**
413
522
  * Create an in-memory audit log for tests and local examples.
414
523
  *