@beignet/core 0.0.3 → 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 +157 -0
  2. package/README.md +785 -43
  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
@@ -1,2 +1,2 @@
1
- export * from "./contract-like";
2
- export * from "./http";
1
+ export * from "./contract-like.js";
2
+ export * from "./http.js";
@@ -0,0 +1,430 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+ import type {
3
+ HttpContractConfig,
4
+ InferOutput,
5
+ Success2xxKeys,
6
+ } from "../contracts/index.js";
7
+ import { inferSoleSuccessStatus } from "../contracts/index.js";
8
+ import type { ContractLike, ResolveContract } from "./contract-like.js";
9
+ import type {
10
+ AddedCtxFromHooks,
11
+ Handler,
12
+ InferBody,
13
+ InferHeaders,
14
+ InferPath,
15
+ InferQuery,
16
+ RouteHook,
17
+ } from "./http.js";
18
+
19
+ /**
20
+ * Structural shape of a finalized use case accepted by the route binder.
21
+ *
22
+ * This intentionally mirrors `UseCaseDef` from `@beignet/core/application`
23
+ * without importing it, so the server runtime stays decoupled from the
24
+ * application builder at runtime.
25
+ */
26
+ export type AnyUseCaseLike = {
27
+ /**
28
+ * Stable use-case name, used in binder diagnostics.
29
+ */
30
+ name: string;
31
+ /**
32
+ * Input schema declared with `.input(...)`.
33
+ */
34
+ inputSchema: StandardSchemaV1;
35
+ /**
36
+ * Output schema declared with `.output(...)`.
37
+ */
38
+ outputSchema: StandardSchemaV1;
39
+ /**
40
+ * Execute the use case with application context and typed input.
41
+ */
42
+ run: (args: never) => Promise<unknown>;
43
+ };
44
+
45
+ type UseCaseRouteCtx<UC> = UC extends {
46
+ run: (args: { ctx: infer Ctx; input: infer _Input }) => Promise<infer _Out>;
47
+ }
48
+ ? Ctx
49
+ : never;
50
+
51
+ /**
52
+ * Input type accepted by a bound use case's `run(...)`.
53
+ */
54
+ export type UseCaseRouteInput<UC> = UC extends {
55
+ run: (args: { ctx: infer _Ctx; input: infer Input }) => Promise<infer _Out>;
56
+ }
57
+ ? Input
58
+ : never;
59
+
60
+ type UseCaseRouteOutput<UC> = UC extends {
61
+ run: (args: { ctx: infer _Ctx; input: infer _Input }) => Promise<infer Out>;
62
+ }
63
+ ? Out
64
+ : never;
65
+
66
+ type ResponseBodyForSchema<S> = S extends null
67
+ ? // biome-ignore lint/suspicious/noConfusingVoidType: void accepts z.void() use case outputs for null response schemas
68
+ void | undefined
69
+ : S extends StandardSchemaV1
70
+ ? InferOutput<S>
71
+ : unknown;
72
+
73
+ type ResponseForStatus<
74
+ TResponses,
75
+ TStatus extends number,
76
+ > = TStatus extends keyof TResponses
77
+ ? TResponses[TStatus]
78
+ : `${TStatus}` extends keyof TResponses
79
+ ? TResponses[`${TStatus}`]
80
+ : never;
81
+
82
+ type SuccessBodyFromKeys<TResponses, K> = [K] extends [never]
83
+ ? unknown
84
+ : K extends number
85
+ ? ResponseBodyForSchema<ResponseForStatus<TResponses, K>>
86
+ : unknown;
87
+
88
+ type UnionToIntersection<T> = (
89
+ T extends unknown
90
+ ? (value: T) => void
91
+ : never
92
+ ) extends (value: infer I) => void
93
+ ? I
94
+ : never;
95
+
96
+ type BinderStatusFromKeys<K> = [K] extends [never]
97
+ ? {
98
+ /**
99
+ * Success status for the use case result. Required because the contract
100
+ * does not declare exactly one 2xx response.
101
+ */
102
+ status: number;
103
+ }
104
+ : [K] extends [UnionToIntersection<K>]
105
+ ? {
106
+ /**
107
+ * Success status for the use case result. Optional because the
108
+ * contract declares exactly one 2xx response.
109
+ */
110
+ status?: K;
111
+ }
112
+ : {
113
+ /**
114
+ * Success status for the use case result. Required because the
115
+ * contract declares multiple 2xx responses.
116
+ */
117
+ status: K;
118
+ };
119
+
120
+ /**
121
+ * `status` option for a binder route.
122
+ *
123
+ * Optional and typed to the sole declared 2xx status when the contract
124
+ * declares exactly one, required (typed to the union of declared 2xx
125
+ * statuses) otherwise.
126
+ */
127
+ export type BinderStatusOption<C extends HttpContractConfig> =
128
+ BinderStatusFromKeys<Success2xxKeys<C["responses"]>>;
129
+
130
+ /**
131
+ * Parsed request parts passed to a binder route's `input` mapper.
132
+ */
133
+ export type UseCaseRouteInputParts<C extends HttpContractConfig> = {
134
+ /**
135
+ * Parsed path parameters.
136
+ */
137
+ path: InferPath<C>;
138
+ /**
139
+ * Parsed query parameters.
140
+ */
141
+ query: InferQuery<C>;
142
+ /**
143
+ * Parsed request headers.
144
+ */
145
+ headers: InferHeaders<C>;
146
+ /**
147
+ * Parsed request body.
148
+ */
149
+ body: InferBody<C>;
150
+ };
151
+
152
+ /**
153
+ * Constraint that checks a use case against the route that binds it.
154
+ *
155
+ * Produces a readable branded mismatch object on the `useCase` property when
156
+ * the use case requires a context the server does not provide, or when its
157
+ * output does not match the contract's declared success response schema.
158
+ */
159
+ export type UseCaseFitsRoute<Ctx, C extends HttpContractConfig, UC> = [
160
+ Ctx,
161
+ ] extends [UseCaseRouteCtx<UC>]
162
+ ? [UseCaseRouteOutput<UC>] extends [
163
+ SuccessBodyFromKeys<C["responses"], Success2xxKeys<C["responses"]>>,
164
+ ]
165
+ ? unknown
166
+ : {
167
+ "~beignetError": "useCase output does not match the contract's success response schema";
168
+ }
169
+ : {
170
+ "~beignetError": "useCase requires a context this server does not provide";
171
+ };
172
+
173
+ type UseCaseRouteShape<
174
+ HandlerCtx,
175
+ CLike extends ContractLike,
176
+ C extends HttpContractConfig,
177
+ UC extends AnyUseCaseLike,
178
+ Hooks,
179
+ > = {
180
+ /**
181
+ * Contract builder or plain contract config for this route.
182
+ */
183
+ contract: CLike;
184
+ /**
185
+ * Route-scoped hooks that run after group hooks and before the use case.
186
+ */
187
+ hooks?: Hooks;
188
+ /**
189
+ * Use case bound directly to the contract.
190
+ */
191
+ useCase: UC & UseCaseFitsRoute<HandlerCtx, C, UC>;
192
+ /**
193
+ * Map parsed request parts to the use case input.
194
+ *
195
+ * Defaults to `defaultBinderInput`, which merges query, body, and path
196
+ * objects (path wins collisions) and never merges headers.
197
+ */
198
+ input?: (parts: UseCaseRouteInputParts<C>) => UseCaseRouteInput<UC>;
199
+ handle?: never;
200
+ } & BinderStatusOption<C>;
201
+
202
+ /**
203
+ * Route registration that binds a contract directly to a use case.
204
+ *
205
+ * The server synthesizes the handler: it maps parsed request parts to the use
206
+ * case input, runs the use case, and returns its output as the success
207
+ * response body. Use a full `handle` route for headers, streaming, native
208
+ * `Response` values, or multi-status handling.
209
+ */
210
+ export type UseCaseRouteDef<
211
+ Ctx,
212
+ CLike extends ContractLike,
213
+ UC extends AnyUseCaseLike,
214
+ Hooks extends readonly RouteHook<Ctx, object>[] = readonly [],
215
+ > = UseCaseRouteShape<
216
+ Ctx & AddedCtxFromHooks<Hooks>,
217
+ CLike,
218
+ ResolveContract<CLike>,
219
+ UC,
220
+ Hooks
221
+ >;
222
+
223
+ /**
224
+ * Structural check that a use case accepts the context this route provides.
225
+ *
226
+ * Enforced through `run` parameter contravariance so it applies even where
227
+ * contract types are erased, such as `defineRouteGroup<Ctx>({ ... })`.
228
+ */
229
+ export type UseCaseAcceptsCtx<Ctx> = {
230
+ run: (args: { ctx: Ctx; input: never }) => Promise<unknown>;
231
+ };
232
+
233
+ /**
234
+ * Loosely typed binder route used at collection boundaries where contract and
235
+ * use case types are erased. The use case's context requirement is still
236
+ * checked against the server context.
237
+ */
238
+ export type AnyUseCaseRouteDef<
239
+ Ctx,
240
+ CLike extends ContractLike = ContractLike,
241
+ Hooks extends readonly RouteHook<Ctx, object>[] = readonly RouteHook<
242
+ Ctx,
243
+ object
244
+ >[],
245
+ > = {
246
+ contract: CLike;
247
+ hooks?: Hooks;
248
+ useCase: AnyUseCaseLike & UseCaseAcceptsCtx<Ctx & AddedCtxFromHooks<Hooks>>;
249
+ // biome-ignore lint/suspicious/noExplicitAny: request part types are erased at collection boundaries
250
+ input?: (parts: any) => unknown;
251
+ status?: number;
252
+ handle?: never;
253
+ };
254
+
255
+ type HooksOf<E> = E extends { hooks: infer H extends readonly unknown[] }
256
+ ? H
257
+ : readonly [];
258
+
259
+ /**
260
+ * Per-element binder validation applied where route tuples are inferred, such
261
+ * as `defineRouteGroup<Ctx>()({ ... })`, so contract/use-case mismatches are
262
+ * reported on the individual route literal.
263
+ */
264
+ export type ValidatedRouteInput<Ctx, E> = E extends {
265
+ contract: infer CL extends ContractLike;
266
+ useCase: infer UC extends AnyUseCaseLike;
267
+ }
268
+ ? ResolveContract<CL> extends infer C extends HttpContractConfig
269
+ ? {
270
+ contract: CL;
271
+ hooks?: HooksOf<E>;
272
+ useCase: UC &
273
+ UseCaseFitsRoute<Ctx & AddedCtxFromHooks<HooksOf<E>>, C, UC>;
274
+ input?: (parts: UseCaseRouteInputParts<C>) => UseCaseRouteInput<UC>;
275
+ handle?: never;
276
+ } & BinderStatusOption<C>
277
+ : unknown
278
+ : unknown;
279
+
280
+ /**
281
+ * Element-wise binder validation for a route input list.
282
+ */
283
+ export type ValidatedRouteInputs<Ctx, R extends readonly unknown[]> = {
284
+ [K in keyof R]: ValidatedRouteInput<Ctx, R[K]>;
285
+ };
286
+
287
+ /**
288
+ * Trusted run key shared with `@beignet/core/application` via the global
289
+ * symbol registry, so the binder never imports the application builder at
290
+ * runtime.
291
+ */
292
+ const USE_CASE_TRUSTED_RUN_KEY: unique symbol = Symbol.for(
293
+ "beignet.useCase.trustedRun",
294
+ );
295
+
296
+ type RuntimeUseCase = AnyUseCaseLike & {
297
+ run: (args: { ctx: unknown; input: unknown }) => Promise<unknown>;
298
+ [USE_CASE_TRUSTED_RUN_KEY]?: (args: {
299
+ ctx: unknown;
300
+ input: unknown;
301
+ }) => Promise<unknown>;
302
+ };
303
+
304
+ /**
305
+ * Loosely typed binder route definition consumed by route registration.
306
+ */
307
+ export type RuntimeUseCaseRouteDef = {
308
+ useCase: RuntimeUseCase;
309
+ input?: (parts: {
310
+ path: unknown;
311
+ query: unknown;
312
+ headers: unknown;
313
+ body: unknown;
314
+ }) => unknown;
315
+ status?: number;
316
+ };
317
+
318
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
319
+ return typeof value === "object" && value !== null && !Array.isArray(value);
320
+ }
321
+
322
+ /**
323
+ * Default input mapping for binder routes.
324
+ *
325
+ * Merges parsed query, body, and path objects into one input object. Path
326
+ * keys win all collisions, then body keys, then query keys. Headers are never
327
+ * merged: parsed headers include every raw request header, so merging them
328
+ * would poison the use case input. Non-object bodies (text, arrays, scalars)
329
+ * are excluded. Routes that need headers or non-object bodies declare an
330
+ * explicit `input` mapper.
331
+ */
332
+ export function defaultBinderInput(parts: {
333
+ path: unknown;
334
+ query: unknown;
335
+ body: unknown;
336
+ }): Record<string, unknown> {
337
+ return {
338
+ ...(isPlainObject(parts.query) ? parts.query : {}),
339
+ ...(isPlainObject(parts.body) ? parts.body : {}),
340
+ ...(isPlainObject(parts.path) ? parts.path : {}),
341
+ };
342
+ }
343
+
344
+ /**
345
+ * Whether a route definition is a binder route.
346
+ */
347
+ export function isUseCaseRouteDef(route: {
348
+ handle?: unknown;
349
+ useCase?: unknown;
350
+ }): route is RuntimeUseCaseRouteDef {
351
+ return route.useCase !== undefined && route.useCase !== null;
352
+ }
353
+
354
+ function computeTrustedInput(
355
+ contract: HttpContractConfig,
356
+ def: RuntimeUseCaseRouteDef,
357
+ ): boolean {
358
+ if (def.input) return false;
359
+
360
+ const sources = [contract.pathParams, contract.query, contract.body].filter(
361
+ (schema) => schema !== null && schema !== undefined,
362
+ );
363
+ return sources.length === 1 && sources[0] === def.useCase.inputSchema;
364
+ }
365
+
366
+ function computeResponseExemption(
367
+ contract: HttpContractConfig,
368
+ def: RuntimeUseCaseRouteDef,
369
+ status: number,
370
+ ): number | undefined {
371
+ return contract.responses[status] === def.useCase.outputSchema
372
+ ? status
373
+ : undefined;
374
+ }
375
+
376
+ /**
377
+ * Synthesize the route handler for a binder route at registration time.
378
+ *
379
+ * Resolves the success status, decides whether the validated request parts can
380
+ * skip the use case's input parse, and computes whether server-side response
381
+ * validation is redundant for the success status.
382
+ */
383
+ export function createUseCaseRouteHandler<Ctx, C extends HttpContractConfig>(
384
+ contract: C,
385
+ def: RuntimeUseCaseRouteDef,
386
+ ): {
387
+ handler: Handler<Ctx, C>;
388
+ responseValidationExemptStatus?: number;
389
+ } {
390
+ const status = def.status ?? inferSoleSuccessStatus(contract);
391
+ if (status === undefined) {
392
+ throw new Error(
393
+ `Route binder for contract "${contract.name}" cannot infer a success ` +
394
+ `status: the contract declares ${
395
+ Object.keys(contract.responses).length === 0
396
+ ? "no responses"
397
+ : "zero or multiple 2xx responses"
398
+ }. Declare exactly one 2xx response or pass an explicit status.`,
399
+ );
400
+ }
401
+
402
+ const mapInput = def.input ?? defaultBinderInput;
403
+ const trustedRun = computeTrustedInput(contract, def)
404
+ ? def.useCase[USE_CASE_TRUSTED_RUN_KEY]
405
+ : undefined;
406
+ const run = trustedRun ?? def.useCase.run;
407
+
408
+ const handler: Handler<Ctx, C> = async ({
409
+ ctx,
410
+ path,
411
+ query,
412
+ headers,
413
+ body,
414
+ }) => ({
415
+ status,
416
+ body: await run.call(def.useCase, {
417
+ ctx,
418
+ input: mapInput({ path, query, headers, body }),
419
+ }),
420
+ });
421
+
422
+ return {
423
+ handler,
424
+ responseValidationExemptStatus: computeResponseExemption(
425
+ contract,
426
+ def,
427
+ status,
428
+ ),
429
+ };
430
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Static Beignet lint marker for modules that must stay out of client bundles.
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 beignetServerOnly = true;
@@ -0,0 +1,275 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ /**
4
+ * Any Standard Schema compatible validator.
5
+ */
6
+ export type StandardSchema = StandardSchemaV1<unknown, unknown>;
7
+
8
+ /**
9
+ * Value or promise of that value.
10
+ */
11
+ export type MaybePromise<T> = T | Promise<T>;
12
+
13
+ /**
14
+ * Infer the parsed output type from a Standard Schema.
15
+ */
16
+ export type InferSchemaOutput<T extends StandardSchemaV1> =
17
+ StandardSchemaV1.InferOutput<T>;
18
+
19
+ /**
20
+ * Operational task definition created by `defineTask(...)`.
21
+ */
22
+ export interface TaskDef<
23
+ Name extends string = string,
24
+ Input extends StandardSchema = StandardSchema,
25
+ Ctx = unknown,
26
+ Output = unknown,
27
+ > {
28
+ /**
29
+ * Discriminator for task definitions.
30
+ */
31
+ readonly kind: "task";
32
+ /**
33
+ * Stable task name used by CLIs and operational runners.
34
+ */
35
+ readonly name: Name;
36
+ /**
37
+ * Standard Schema input validator.
38
+ */
39
+ readonly input: Input;
40
+ /**
41
+ * Optional human-readable description for docs and tooling.
42
+ */
43
+ readonly description?: string;
44
+ /**
45
+ * Handle a parsed task input.
46
+ */
47
+ handle(
48
+ args: TaskHandleArgs<TaskDef<Name, Input, Ctx, Output>, Ctx>,
49
+ ): MaybePromise<Output>;
50
+ }
51
+
52
+ /**
53
+ * Infer the parsed input type for an operational task.
54
+ */
55
+ export type InferTaskInput<T extends TaskDef> =
56
+ T["input"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;
57
+
58
+ /**
59
+ * Infer the result type for an operational task.
60
+ */
61
+ export type InferTaskOutput<T extends TaskDef> =
62
+ T extends TaskDef<string, StandardSchema, unknown, infer Output>
63
+ ? Awaited<Output>
64
+ : never;
65
+
66
+ /**
67
+ * Arguments passed to a task handler.
68
+ */
69
+ export interface TaskHandleArgs<T extends TaskDef, Ctx> {
70
+ /**
71
+ * Task definition being handled.
72
+ */
73
+ task: T;
74
+ /**
75
+ * Parsed task input.
76
+ */
77
+ input: InferTaskInput<T>;
78
+ /**
79
+ * Handler context.
80
+ */
81
+ ctx: Ctx;
82
+ }
83
+
84
+ /**
85
+ * Options for `defineTask(...)`.
86
+ */
87
+ export interface DefineTaskOptions<
88
+ Name extends string,
89
+ Input extends StandardSchema,
90
+ Ctx,
91
+ Output,
92
+ > {
93
+ /**
94
+ * Standard Schema input validator.
95
+ */
96
+ input: Input;
97
+ /**
98
+ * Optional human-readable description for docs and tooling.
99
+ */
100
+ description?: string;
101
+ /**
102
+ * Handle a parsed task input.
103
+ */
104
+ handle(
105
+ args: TaskHandleArgs<TaskDef<Name, Input, Ctx, Output>, Ctx>,
106
+ ): MaybePromise<Output>;
107
+ }
108
+
109
+ /**
110
+ * Options for one task run.
111
+ */
112
+ export interface RunTaskOptions<Ctx> {
113
+ /**
114
+ * Raw task input. It is parsed with the task's Standard Schema before the
115
+ * handler runs.
116
+ */
117
+ input: unknown;
118
+ /**
119
+ * Handler context.
120
+ */
121
+ ctx: Ctx;
122
+ }
123
+
124
+ /**
125
+ * Context-bound operational task helper factory.
126
+ */
127
+ export interface Tasks<Ctx> {
128
+ /**
129
+ * Define a task with the bound context type.
130
+ */
131
+ defineTask<
132
+ Name extends string,
133
+ Input extends StandardSchema,
134
+ Output = unknown,
135
+ >(
136
+ name: Name,
137
+ options: DefineTaskOptions<Name, Input, Ctx, Output>,
138
+ ): TaskDef<Name, Input, Ctx, Output>;
139
+ }
140
+
141
+ /**
142
+ * Error thrown when task input validation fails.
143
+ */
144
+ export class TaskValidationError extends Error {
145
+ /**
146
+ * Raw Standard Schema validation issues.
147
+ */
148
+ readonly issues: readonly StandardSchemaV1.Issue[];
149
+
150
+ constructor(args: {
151
+ name: string;
152
+ issues: readonly StandardSchemaV1.Issue[];
153
+ }) {
154
+ super(
155
+ `Task "${args.name}" input validation failed: ${formatIssues(args.issues)}`,
156
+ );
157
+ this.name = "TaskValidationError";
158
+ this.issues = args.issues;
159
+ }
160
+ }
161
+
162
+ function formatPath(path: StandardSchemaV1.Issue["path"]): string {
163
+ if (!path || path.length === 0) return "";
164
+
165
+ return path
166
+ .map((segment) => {
167
+ if (typeof segment === "number") return `[${segment}]`;
168
+ const key = String(segment);
169
+ return /^[A-Za-z_$][\w$]*$/.test(key)
170
+ ? `.${key}`
171
+ : `[${JSON.stringify(key)}]`;
172
+ })
173
+ .join("")
174
+ .replace(/^\./, "");
175
+ }
176
+
177
+ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
178
+ return issues
179
+ .map((issue) => {
180
+ const path = formatPath(issue.path);
181
+ return path ? `${path}: ${issue.message}` : issue.message;
182
+ })
183
+ .join("; ");
184
+ }
185
+
186
+ async function parseInput<Schema extends StandardSchemaV1>(
187
+ schema: Schema,
188
+ input: unknown,
189
+ options: { name: string },
190
+ ): Promise<InferSchemaOutput<Schema>> {
191
+ const result = await schema["~standard"].validate(input);
192
+
193
+ if (result.issues?.length) {
194
+ throw new TaskValidationError({
195
+ name: options.name,
196
+ issues: result.issues,
197
+ });
198
+ }
199
+
200
+ return (result as { value: InferSchemaOutput<Schema> }).value;
201
+ }
202
+
203
+ function defineTaskImpl<
204
+ Name extends string,
205
+ Input extends StandardSchema,
206
+ Ctx = unknown,
207
+ Output = unknown,
208
+ >(
209
+ name: Name,
210
+ options: DefineTaskOptions<Name, Input, Ctx, Output>,
211
+ ): TaskDef<Name, Input, Ctx, Output> {
212
+ return {
213
+ kind: "task",
214
+ name,
215
+ input: options.input,
216
+ description: options.description,
217
+ handle: options.handle as TaskDef<Name, Input, Ctx, Output>["handle"],
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Validate and parse a task input with the task's Standard Schema.
223
+ */
224
+ export async function parseTaskInput<T extends TaskDef>(
225
+ task: T,
226
+ input: unknown,
227
+ ): Promise<InferTaskInput<T>> {
228
+ return (await parseInput(task.input, input, {
229
+ name: task.name,
230
+ })) as InferTaskInput<T>;
231
+ }
232
+
233
+ /**
234
+ * Parse input and run an operational task.
235
+ */
236
+ export async function runTask<
237
+ T extends TaskDef<string, StandardSchema, Ctx>,
238
+ Ctx,
239
+ >(task: T, options: RunTaskOptions<Ctx>): Promise<InferTaskOutput<T>> {
240
+ const parsed = await parseTaskInput(task, options.input);
241
+ return (await task.handle({
242
+ task,
243
+ input: parsed,
244
+ ctx: options.ctx,
245
+ })) as InferTaskOutput<T>;
246
+ }
247
+
248
+ /**
249
+ * Define a task registry while preserving tuple inference.
250
+ */
251
+ export function defineTasks<const Defs extends readonly TaskDef[]>(
252
+ tasks: Defs,
253
+ ): Defs {
254
+ return tasks;
255
+ }
256
+
257
+ /**
258
+ * Create task helper methods bound to an application context type.
259
+ *
260
+ * Call it once in `lib/tasks.ts`:
261
+ *
262
+ * ```ts
263
+ * export const { defineTask } = createTasks<AppContext>();
264
+ * ```
265
+ */
266
+ export function createTasks<Ctx>(): Tasks<Ctx> {
267
+ return {
268
+ defineTask<Name extends string, Input extends StandardSchema, Output>(
269
+ name: Name,
270
+ options: DefineTaskOptions<Name, Input, Ctx, Output>,
271
+ ) {
272
+ return defineTaskImpl<Name, Input, Ctx, Output>(name, options);
273
+ },
274
+ };
275
+ }