@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
@@ -10,8 +10,8 @@ import {
10
10
  type StandardErrorResponseBody,
11
11
  type StandardSchema,
12
12
  type StandardSchemaV1,
13
- } from "../contracts";
14
- import { isErrorResponseBody, SchemaValidationError } from "../errors";
13
+ } from "../contracts/index.js";
14
+ import { isErrorResponseBody, SchemaValidationError } from "../errors/index.js";
15
15
  import type {
16
16
  CallArgs,
17
17
  ClientConfig,
@@ -21,7 +21,7 @@ import type {
21
21
  InferEndpointErrorResponseByStatus,
22
22
  InferEndpointErrorStatus,
23
23
  InferSuccessResponse,
24
- } from "./types";
24
+ } from "./types.js";
25
25
 
26
26
  /**
27
27
  * Source category for a `ContractError`.
@@ -583,11 +583,10 @@ export class Endpoint<
583
583
 
584
584
  try {
585
585
  if (args.body !== undefined && args.rawBody !== undefined) {
586
- throw this.createError(
587
- undefined,
588
- "INVALID_REQUEST_BODY",
589
- "Pass either body or rawBody, not both.",
590
- );
586
+ throw this.createError({
587
+ code: "INVALID_REQUEST_BODY",
588
+ message: "Pass either body or rawBody, not both.",
589
+ });
591
590
  }
592
591
 
593
592
  const methodSupportsBody = methodSupportsRequestBody(
@@ -597,11 +596,10 @@ export class Endpoint<
597
596
  (args.body !== undefined || args.rawBody !== undefined) &&
598
597
  !methodSupportsBody
599
598
  ) {
600
- throw this.createError(
601
- undefined,
602
- "INVALID_REQUEST_BODY",
603
- `Request bodies are not supported for ${this.contract.method} contracts. Use POST, PUT, or PATCH for contract request bodies.`,
604
- );
599
+ throw this.createError({
600
+ code: "INVALID_REQUEST_BODY",
601
+ message: `Request bodies are not supported for ${this.contract.method} contracts. Use POST, PUT, or PATCH for contract request bodies.`,
602
+ });
605
603
  }
606
604
 
607
605
  let requestBody: BodyInit | undefined;
@@ -613,7 +611,7 @@ export class Endpoint<
613
611
 
614
612
  if (args.body !== undefined && methodSupportsBody) {
615
613
  let bodyToSend = args.body;
616
- if (this.config.validate && this.contract.body) {
614
+ if (this.config.validateInput && this.contract.body) {
617
615
  try {
618
616
  bodyToSend = (await validateSchema(
619
617
  this.contract.body,
@@ -621,12 +619,11 @@ export class Endpoint<
621
619
  )) as typeof bodyToSend;
622
620
  } catch (err) {
623
621
  if (err instanceof SchemaValidationError) {
624
- throw this.createError(
625
- 422,
626
- "VALIDATION_ERROR",
627
- "Body validation failed",
628
- err.issues,
629
- );
622
+ throw this.createError({
623
+ code: "INPUT_VALIDATION_ERROR",
624
+ message: "Body validation failed",
625
+ details: err.issues,
626
+ });
630
627
  }
631
628
  throw err;
632
629
  }
@@ -643,19 +640,28 @@ export class Endpoint<
643
640
  args.headers as Record<string, string> | undefined,
644
641
  requestBodyType === "json",
645
642
  );
646
- if (this.config.validate) {
643
+ const idempotencyMeta = this.contract.metadata?.idempotency;
644
+ if (idempotencyMeta) {
645
+ const idempotencyHeader = (
646
+ idempotencyMeta.header ?? "idempotency-key"
647
+ ).toLowerCase();
648
+ if (!headers[idempotencyHeader]) {
649
+ headers[idempotencyHeader] =
650
+ args.idempotencyKey ?? crypto.randomUUID();
651
+ }
652
+ }
653
+ if (this.config.validateInput) {
647
654
  const headerSchemas = getContractHeaderSchemas(this.contract.headers);
648
655
  if (headerSchemas.length > 0) {
649
656
  try {
650
657
  headers = await validateHeaderSchemas(headerSchemas, headers);
651
658
  } catch (err) {
652
659
  if (err instanceof SchemaValidationError) {
653
- throw this.createError(
654
- 422,
655
- "VALIDATION_ERROR",
656
- "Headers validation failed",
657
- err.issues,
658
- );
660
+ throw this.createError({
661
+ code: "INPUT_VALIDATION_ERROR",
662
+ message: "Headers validation failed",
663
+ details: err.issues,
664
+ });
659
665
  }
660
666
  throw err;
661
667
  }
@@ -674,6 +680,7 @@ export class Endpoint<
674
680
  }
675
681
 
676
682
  const response = await fetchFn(url, options);
683
+ const shouldValidateResponses = this.config.validateResponses !== false;
677
684
 
678
685
  // Handle non-2xx responses
679
686
  if (!response.ok) {
@@ -682,35 +689,34 @@ export class Endpoint<
682
689
  errorBody = await parseResponseBody(response);
683
690
  } catch (parseErr) {
684
691
  // JSON parse failed — still report the HTTP error
685
- throw this.createError(
686
- response.status,
687
- "INVALID_JSON",
688
- createInvalidJsonMessage("error", response.status),
689
- undefined,
690
- parseErr,
691
- undefined,
692
+ throw this.createError({
693
+ status: response.status,
694
+ code: "INVALID_JSON",
695
+ message: createInvalidJsonMessage("error", response.status),
696
+ cause: parseErr,
692
697
  response,
693
- "contract",
694
- );
698
+ source: "contract",
699
+ });
695
700
  }
696
701
 
697
- const validatedError = await validateErrorBodyOrStandardEnvelope(
698
- this.contract,
699
- response,
700
- errorBody,
701
- );
702
+ const validatedError = shouldValidateResponses
703
+ ? await validateErrorBodyOrStandardEnvelope(
704
+ this.contract,
705
+ response,
706
+ errorBody,
707
+ )
708
+ : errorBody;
702
709
 
703
710
  const errorPayload = getErrorPayload(validatedError);
704
- throw this.createError(
705
- response.status,
706
- errorPayload.code || "HTTP_ERROR",
707
- errorPayload.message || response.statusText,
708
- errorPayload.details,
709
- undefined,
710
- validatedError,
711
+ throw this.createError({
712
+ status: response.status,
713
+ code: errorPayload.code || "HTTP_ERROR",
714
+ message: errorPayload.message || response.statusText,
715
+ details: errorPayload.details,
716
+ body: validatedError,
711
717
  response,
712
- "http",
713
- );
718
+ source: "http",
719
+ });
714
720
  }
715
721
 
716
722
  // Parse response
@@ -718,71 +724,68 @@ export class Endpoint<
718
724
  try {
719
725
  data = await parseResponseBody(response);
720
726
  } catch (parseErr) {
721
- throw this.createError(
722
- response.status,
723
- "INVALID_JSON",
724
- createInvalidJsonMessage("success", response.status),
725
- undefined,
726
- parseErr,
727
- undefined,
727
+ throw this.createError({
728
+ status: response.status,
729
+ code: "INVALID_JSON",
730
+ message: createInvalidJsonMessage("success", response.status),
731
+ cause: parseErr,
728
732
  response,
729
- "contract",
730
- );
733
+ source: "contract",
734
+ });
731
735
  }
732
736
 
733
737
  // Validate response if schema exists for this status
734
- const statusKey = String(response.status);
735
- const hasSchema = statusKey in this.contract.responses;
736
- const hasDeclaredResponses =
737
- Object.keys(this.contract.responses).length > 0;
738
- const responseSchema = this.contract.responses[response.status];
739
- if (hasSchema && responseSchema === null) {
740
- if (data !== undefined && data !== null) {
741
- throw this.createError(
742
- response.status,
743
- "RESPONSE_VALIDATION_ERROR",
744
- `Response validation failed for ${this.contract.method} ${this.contract.path} (status ${response.status}, contract: ${this.contract.name})`,
745
- [
746
- {
747
- message:
748
- "Response body must be empty for a null response schema.",
749
- },
750
- ],
751
- undefined,
752
- data,
753
- response,
754
- "contract",
755
- );
756
- }
757
- } else if (hasSchema && responseSchema) {
758
- try {
759
- data = await validateSchema(responseSchema, data);
760
- } catch (err) {
761
- if (err instanceof SchemaValidationError) {
762
- throw this.createError(
763
- response.status,
764
- "RESPONSE_VALIDATION_ERROR",
765
- `Response validation failed for ${this.contract.method} ${this.contract.path} (status ${response.status}, contract: ${this.contract.name})`,
766
- err.issues,
767
- err,
768
- data,
738
+ if (shouldValidateResponses) {
739
+ const statusKey = String(response.status);
740
+ const hasSchema = statusKey in this.contract.responses;
741
+ const hasDeclaredResponses =
742
+ Object.keys(this.contract.responses).length > 0;
743
+ const responseSchema = this.contract.responses[response.status];
744
+ if (hasSchema && responseSchema === null) {
745
+ if (data !== undefined && data !== null) {
746
+ throw this.createError({
747
+ status: response.status,
748
+ code: "RESPONSE_VALIDATION_ERROR",
749
+ message: `Response validation failed for ${this.contract.method} ${this.contract.path} (status ${response.status}, contract: ${this.contract.name})`,
750
+ details: [
751
+ {
752
+ message:
753
+ "Response body must be empty for a null response schema.",
754
+ },
755
+ ],
756
+ body: data,
769
757
  response,
770
- "contract",
771
- );
758
+ source: "contract",
759
+ });
772
760
  }
773
- throw err;
761
+ } else if (hasSchema && responseSchema) {
762
+ try {
763
+ data = await validateSchema(responseSchema, data);
764
+ } catch (err) {
765
+ if (err instanceof SchemaValidationError) {
766
+ throw this.createError({
767
+ status: response.status,
768
+ code: "RESPONSE_VALIDATION_ERROR",
769
+ message: `Response validation failed for ${this.contract.method} ${this.contract.path} (status ${response.status}, contract: ${this.contract.name})`,
770
+ details: err.issues,
771
+ cause: err,
772
+ body: data,
773
+ response,
774
+ source: "contract",
775
+ });
776
+ }
777
+ throw err;
778
+ }
779
+ } else if (!hasSchema && hasDeclaredResponses) {
780
+ throw this.createError({
781
+ status: response.status,
782
+ code: "UNDECLARED_RESPONSE_STATUS",
783
+ message: `Server returned undeclared status ${response.status} for ${this.contract.method} ${this.contract.path} (contract: ${this.contract.name})`,
784
+ body: data,
785
+ response,
786
+ source: "contract",
787
+ });
774
788
  }
775
- } else if (!hasSchema && hasDeclaredResponses) {
776
- throw this.createError(
777
- response.status,
778
- "UNDECLARED_RESPONSE_STATUS",
779
- `Server returned undeclared status ${response.status} for ${this.contract.method} ${this.contract.path} (contract: ${this.contract.name})`,
780
- undefined,
781
- undefined,
782
- data,
783
- response,
784
- "contract",
785
- );
786
789
  }
787
790
 
788
791
  return {
@@ -801,16 +804,12 @@ export class Endpoint<
801
804
  response: error.response,
802
805
  };
803
806
  }
804
- const error = this.createError(
805
- undefined,
806
- "NETWORK_ERROR",
807
- err instanceof Error ? err.message : "Network request failed",
808
- undefined,
809
- err,
810
- undefined,
811
- undefined,
812
- "network",
813
- ) as InferEndpointContractError<TContract>;
807
+ const error = this.createError({
808
+ code: "NETWORK_ERROR",
809
+ message: err instanceof Error ? err.message : "Network request failed",
810
+ cause: err,
811
+ source: "network",
812
+ }) as InferEndpointContractError<TContract>;
814
813
  return {
815
814
  ok: false,
816
815
  status: error.status,
@@ -843,7 +842,7 @@ export class Endpoint<
843
842
  // Replace path parameters
844
843
  if (path && parsedPath.keys.length > 0) {
845
844
  // Validate path params if schema exists and validation is enabled
846
- if (this.config.validate && this.contract.pathParams) {
845
+ if (this.config.validateInput && this.contract.pathParams) {
847
846
  try {
848
847
  pathToSerialize = (await validateSchema(
849
848
  this.contract.pathParams,
@@ -851,12 +850,11 @@ export class Endpoint<
851
850
  )) as PathParams;
852
851
  } catch (err) {
853
852
  if (err instanceof SchemaValidationError) {
854
- throw this.createError(
855
- 422,
856
- "VALIDATION_ERROR",
857
- "Path params validation failed",
858
- err.issues,
859
- );
853
+ throw this.createError({
854
+ code: "INPUT_VALIDATION_ERROR",
855
+ message: "Path params validation failed",
856
+ details: err.issues,
857
+ });
860
858
  }
861
859
  throw err;
862
860
  }
@@ -894,7 +892,7 @@ export class Endpoint<
894
892
  let queryToSerialize = query;
895
893
  if (query) {
896
894
  // Validate query params if schema exists and validation is enabled
897
- if (this.config.validate && this.contract.query) {
895
+ if (this.config.validateInput && this.contract.query) {
898
896
  try {
899
897
  queryToSerialize = (await validateSchema(
900
898
  this.contract.query,
@@ -902,12 +900,11 @@ export class Endpoint<
902
900
  )) as QueryParams;
903
901
  } catch (err) {
904
902
  if (err instanceof SchemaValidationError) {
905
- throw this.createError(
906
- 422,
907
- "VALIDATION_ERROR",
908
- "Query params validation failed",
909
- err.issues,
910
- );
903
+ throw this.createError({
904
+ code: "INPUT_VALIDATION_ERROR",
905
+ message: "Query params validation failed",
906
+ details: err.issues,
907
+ });
911
908
  }
912
909
  throw err;
913
910
  }
@@ -987,26 +984,27 @@ export class Endpoint<
987
984
  /**
988
985
  * Create a contract error
989
986
  */
990
- private createError(
991
- status: number | undefined,
992
- code: string,
993
- message: string,
994
- details?: unknown,
995
- cause?: unknown,
996
- body?: unknown,
997
- response?: Response,
998
- source: ContractErrorSource = "client",
999
- ): AnyContractError {
987
+ private createError(options: {
988
+ code: string;
989
+ message: string;
990
+ status?: number;
991
+ details?: unknown;
992
+ cause?: unknown;
993
+ body?: unknown;
994
+ response?: Response;
995
+ source?: ContractErrorSource;
996
+ }): AnyContractError {
997
+ const source = options.source ?? "client";
998
+ const isLocalError = source === "client" || source === "network";
1000
999
  return new ContractError({
1001
1000
  source,
1002
- status: source === "client" || source === "network" ? undefined : status,
1003
- code,
1004
- message,
1005
- body: source === "client" || source === "network" ? undefined : body,
1006
- details,
1007
- response:
1008
- source === "client" || source === "network" ? undefined : response,
1009
- cause,
1001
+ status: isLocalError ? undefined : options.status,
1002
+ code: options.code,
1003
+ message: options.message,
1004
+ body: isLocalError ? undefined : options.body,
1005
+ details: options.details,
1006
+ response: isLocalError ? undefined : options.response,
1007
+ cause: options.cause,
1010
1008
  }) as AnyContractError;
1011
1009
  }
1012
1010
  }
@@ -0,0 +1,35 @@
1
+ import { isContractError } from "./client.js";
2
+
3
+ /**
4
+ * User-facing copy overrides keyed by error code.
5
+ */
6
+ export type ErrorMessageOverrides = Partial<Record<string, string>>;
7
+
8
+ /**
9
+ * Map an unknown error to user-facing copy.
10
+ *
11
+ * Non-`ContractError` values return the fallback. Override copy per catalog
12
+ * code with `overrides`. Client-side input validation failures return a
13
+ * generic "check the highlighted fields" message; other contract errors
14
+ * return the error message.
15
+ */
16
+ export function contractErrorMessage(
17
+ error: unknown,
18
+ fallback: string,
19
+ overrides: ErrorMessageOverrides = {},
20
+ ): string {
21
+ if (!isContractError(error)) {
22
+ return fallback;
23
+ }
24
+
25
+ const override = error.code ? overrides[error.code] : undefined;
26
+ if (override) {
27
+ return override;
28
+ }
29
+
30
+ if (error.hasSource("client") && error.hasCode("INPUT_VALIDATION_ERROR")) {
31
+ return "Check the highlighted fields and try again.";
32
+ }
33
+
34
+ return error.message;
35
+ }
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Schema validation error re-exported for client users.
9
9
  */
10
- export { SchemaValidationError } from "../errors";
10
+ export { SchemaValidationError } from "../errors/index.js";
11
11
  /**
12
12
  * Contract client error types.
13
13
  */
@@ -23,7 +23,7 @@ export type {
23
23
  InferEndpointErrorCode,
24
24
  NetworkContractError,
25
25
  ResponseContractError,
26
- } from "./client";
26
+ } from "./client.js";
27
27
  /**
28
28
  * Contract client runtime helpers.
29
29
  */
@@ -33,11 +33,19 @@ export {
33
33
  createClient,
34
34
  Endpoint,
35
35
  isContractError,
36
- } from "./client";
36
+ } from "./client.js";
37
+ /**
38
+ * Client error message helpers.
39
+ */
40
+ export {
41
+ contractErrorMessage,
42
+ type ErrorMessageOverrides,
43
+ } from "./error-messages.js";
37
44
  /**
38
45
  * Contract client inference and call types.
39
46
  */
40
47
  export type {
48
+ AutoProvidedHeaders,
41
49
  CallArgs,
42
50
  ClientConfig,
43
51
  EndpointCallArgs,
@@ -54,4 +62,4 @@ export type {
54
62
  InferPathParams,
55
63
  InferQuery,
56
64
  InferSuccessResponse,
57
- } from "./types";
65
+ } from "./types.js";
@@ -6,8 +6,8 @@ import type {
6
6
  InferOutput,
7
7
  StandardSchema,
8
8
  StandardSchemaV1,
9
- } from "../contracts";
10
- import type { ErrorResponseBody } from "../errors";
9
+ } from "../contracts/index.js";
10
+ import type { ErrorResponseBody } from "../errors/index.js";
11
11
 
12
12
  /**
13
13
  * Configuration for a Beignet contract client.
@@ -29,8 +29,20 @@ export type ClientConfig<TProvidedHeaders extends string = never> = {
29
29
  * Fetch implementation. Defaults to global `fetch`.
30
30
  */
31
31
  fetch?: typeof fetch;
32
- /** Enable client-side input validation for path, query, and body. Default: false. */
33
- validate?: boolean;
32
+ /**
33
+ * Validate path params, query params, request bodies, and declared request
34
+ * headers against contract schemas before sending. Failures throw a
35
+ * client-source `ContractError` with code `INPUT_VALIDATION_ERROR`.
36
+ * Default: false.
37
+ */
38
+ validateInput?: boolean;
39
+ /**
40
+ * Validate response bodies (success and declared error responses) against
41
+ * contract schemas. Disabling this returns success bodies as-is and skips
42
+ * contract-drift checks such as undeclared response statuses.
43
+ * Default: true.
44
+ */
45
+ validateResponses?: boolean;
34
46
  };
35
47
 
36
48
  type PathParamPrimitive = string | number | boolean;
@@ -300,6 +312,25 @@ type ProvidedHeaderKeys<THeaders, TProvidedHeaders extends string> = Extract<
300
312
  TProvidedHeaders
301
313
  >;
302
314
 
315
+ /**
316
+ * Header names the client fills in automatically from contract metadata.
317
+ *
318
+ * Contracts with idempotency metadata get their idempotency key header
319
+ * generated per call, so typed calls may omit it. When the metadata header
320
+ * name is widened to `string`, only the default `idempotency-key` header is
321
+ * treated as auto-provided so other required headers stay required.
322
+ */
323
+ export type AutoProvidedHeaders<TContract extends HttpContractConfig> =
324
+ TContract["metadata"] extends { idempotency: infer M }
325
+ ? M extends { header: infer H extends string }
326
+ ? string extends H
327
+ ? "idempotency-key"
328
+ : Lowercase<H>
329
+ : M extends object
330
+ ? "idempotency-key"
331
+ : never
332
+ : never;
333
+
303
334
  type ApplyProvidedHeaders<THeaders, TProvidedHeaders extends string> = Omit<
304
335
  THeaders,
305
336
  ProvidedHeaderKeys<THeaders, TProvidedHeaders>
@@ -312,7 +343,10 @@ type HeaderInput<
312
343
  > =
313
344
  InferHeaders<TContract> extends undefined
314
345
  ? Record<string, string>
315
- : ApplyProvidedHeaders<InferHeaders<TContract>, TProvidedHeaders>;
346
+ : ApplyProvidedHeaders<
347
+ InferHeaders<TContract>,
348
+ TProvidedHeaders | AutoProvidedHeaders<TContract>
349
+ >;
316
350
 
317
351
  type HeadersArgs<
318
352
  TContract extends HttpContractConfig,
@@ -341,6 +375,11 @@ export type EndpointCallArgs<
341
375
  * and is not validated or JSON-serialized.
342
376
  */
343
377
  rawBody?: TContract["method"] extends BodyHttpMethod ? BodyInit : never;
378
+ /**
379
+ * Idempotency key to send instead of an auto-generated one. Use for
380
+ * retry-with-same-key flows on contracts with idempotency metadata.
381
+ */
382
+ idempotencyKey?: string;
344
383
  /**
345
384
  * Abort signal passed to fetch.
346
385
  */
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Static Beignet lint marker for modules intended for client-side imports.
3
+ *
4
+ * Host frameworks may provide their own runtime-only enforcement. This marker
5
+ * is intentionally a no-op so Beignet can enforce intent through `beignet lint`.
6
+ */
7
+ export const beignetClientOnly = true;
@@ -96,9 +96,9 @@ export interface ReadEnvOptions {
96
96
  }
97
97
 
98
98
  /**
99
- * Options for defining a single validated env loader.
99
+ * Options for creating a single validated env loader.
100
100
  */
101
- export interface DefineEnvOptions<Schema extends StandardSchemaV1> {
101
+ export interface CreateEnvLoaderOptions<Schema extends StandardSchemaV1> {
102
102
  /**
103
103
  * Standard Schema for validating the full environment object.
104
104
  */
@@ -133,7 +133,7 @@ export interface DefineEnvOptions<Schema extends StandardSchemaV1> {
133
133
  }
134
134
 
135
135
  /**
136
- * Validated env loader returned by `defineEnv(...)`.
136
+ * Validated env loader returned by `createEnvLoader(...)`.
137
137
  */
138
138
  export interface EnvInstance<Out> {
139
139
  /**
@@ -396,13 +396,13 @@ function wrapEnvError(err: unknown, prefix?: string): Error {
396
396
  }
397
397
 
398
398
  /**
399
- * Define a reusable validated env loader.
399
+ * Create a reusable validated env loader.
400
400
  *
401
401
  * This is useful for provider config and app-level config objects that are
402
402
  * loaded from a prefixed subset of the environment.
403
403
  */
404
- export function defineEnv<Schema extends StandardSchemaV1>(
405
- options: DefineEnvOptions<Schema>,
404
+ export function createEnvLoader<Schema extends StandardSchemaV1>(
405
+ options: CreateEnvLoaderOptions<Schema>,
406
406
  ): EnvInstance<InferOutput<Schema>> {
407
407
  const {
408
408
  schema,