@beignet/core 0.0.1

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 (331) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +288 -0
  3. package/dist/application/index.d.ts +260 -0
  4. package/dist/application/index.d.ts.map +1 -0
  5. package/dist/application/index.js +324 -0
  6. package/dist/application/index.js.map +1 -0
  7. package/dist/client/client.d.ts +241 -0
  8. package/dist/client/client.d.ts.map +1 -0
  9. package/dist/client/client.js +531 -0
  10. package/dist/client/client.js.map +1 -0
  11. package/dist/client/index.d.ts +10 -0
  12. package/dist/client/index.d.ts.map +1 -0
  13. package/dist/client/index.js +8 -0
  14. package/dist/client/index.js.map +1 -0
  15. package/dist/client/types.d.ts +139 -0
  16. package/dist/client/types.d.ts.map +1 -0
  17. package/dist/client/types.js +2 -0
  18. package/dist/client/types.js.map +1 -0
  19. package/dist/config/index.d.ts +122 -0
  20. package/dist/config/index.d.ts.map +1 -0
  21. package/dist/config/index.js +216 -0
  22. package/dist/config/index.js.map +1 -0
  23. package/dist/contracts/contract-builder.d.ts +121 -0
  24. package/dist/contracts/contract-builder.d.ts.map +1 -0
  25. package/dist/contracts/contract-builder.js +346 -0
  26. package/dist/contracts/contract-builder.js.map +1 -0
  27. package/dist/contracts/contract-group.d.ts +106 -0
  28. package/dist/contracts/contract-group.d.ts.map +1 -0
  29. package/dist/contracts/contract-group.js +240 -0
  30. package/dist/contracts/contract-group.js.map +1 -0
  31. package/dist/contracts/contract-like.d.ts +21 -0
  32. package/dist/contracts/contract-like.d.ts.map +1 -0
  33. package/dist/contracts/contract-like.js +9 -0
  34. package/dist/contracts/contract-like.js.map +1 -0
  35. package/dist/contracts/index.d.ts +15 -0
  36. package/dist/contracts/index.d.ts.map +1 -0
  37. package/dist/contracts/index.js +11 -0
  38. package/dist/contracts/index.js.map +1 -0
  39. package/dist/contracts/openapi-meta.d.ts +23 -0
  40. package/dist/contracts/openapi-meta.d.ts.map +1 -0
  41. package/dist/contracts/openapi-meta.js +2 -0
  42. package/dist/contracts/openapi-meta.js.map +1 -0
  43. package/dist/contracts/path-template.d.ts +17 -0
  44. package/dist/contracts/path-template.d.ts.map +1 -0
  45. package/dist/contracts/path-template.js +50 -0
  46. package/dist/contracts/path-template.js.map +1 -0
  47. package/dist/contracts/rate-limit.d.ts +50 -0
  48. package/dist/contracts/rate-limit.d.ts.map +1 -0
  49. package/dist/contracts/rate-limit.js +2 -0
  50. package/dist/contracts/rate-limit.js.map +1 -0
  51. package/dist/contracts/types.d.ts +97 -0
  52. package/dist/contracts/types.d.ts.map +1 -0
  53. package/dist/contracts/types.js +54 -0
  54. package/dist/contracts/types.js.map +1 -0
  55. package/dist/contracts/utils.d.ts +3 -0
  56. package/dist/contracts/utils.d.ts.map +1 -0
  57. package/dist/contracts/utils.js +44 -0
  58. package/dist/contracts/utils.js.map +1 -0
  59. package/dist/domain/entity.d.ts +87 -0
  60. package/dist/domain/entity.d.ts.map +1 -0
  61. package/dist/domain/entity.js +155 -0
  62. package/dist/domain/entity.js.map +1 -0
  63. package/dist/domain/events.d.ts +41 -0
  64. package/dist/domain/events.d.ts.map +1 -0
  65. package/dist/domain/events.js +21 -0
  66. package/dist/domain/events.js.map +1 -0
  67. package/dist/domain/index.d.ts +14 -0
  68. package/dist/domain/index.d.ts.map +1 -0
  69. package/dist/domain/index.js +14 -0
  70. package/dist/domain/index.js.map +1 -0
  71. package/dist/domain/value-object.d.ts +60 -0
  72. package/dist/domain/value-object.d.ts.map +1 -0
  73. package/dist/domain/value-object.js +87 -0
  74. package/dist/domain/value-object.js.map +1 -0
  75. package/dist/errors/catalog.d.ts +71 -0
  76. package/dist/errors/catalog.d.ts.map +1 -0
  77. package/dist/errors/catalog.js +71 -0
  78. package/dist/errors/catalog.js.map +1 -0
  79. package/dist/errors/http.d.ts +77 -0
  80. package/dist/errors/http.d.ts.map +1 -0
  81. package/dist/errors/http.js +74 -0
  82. package/dist/errors/http.js.map +1 -0
  83. package/dist/errors/index.d.ts +10 -0
  84. package/dist/errors/index.d.ts.map +1 -0
  85. package/dist/errors/index.js +14 -0
  86. package/dist/errors/index.js.map +1 -0
  87. package/dist/errors/response.d.ts +26 -0
  88. package/dist/errors/response.d.ts.map +1 -0
  89. package/dist/errors/response.js +34 -0
  90. package/dist/errors/response.js.map +1 -0
  91. package/dist/errors/validation.d.ts +18 -0
  92. package/dist/errors/validation.d.ts.map +1 -0
  93. package/dist/errors/validation.js +21 -0
  94. package/dist/errors/validation.js.map +1 -0
  95. package/dist/events/index.d.ts +58 -0
  96. package/dist/events/index.d.ts.map +1 -0
  97. package/dist/events/index.js +102 -0
  98. package/dist/events/index.js.map +1 -0
  99. package/dist/jobs/index.d.ts +56 -0
  100. package/dist/jobs/index.d.ts.map +1 -0
  101. package/dist/jobs/index.js +89 -0
  102. package/dist/jobs/index.js.map +1 -0
  103. package/dist/mail/index.d.ts +75 -0
  104. package/dist/mail/index.d.ts.map +1 -0
  105. package/dist/mail/index.js +84 -0
  106. package/dist/mail/index.js.map +1 -0
  107. package/dist/openapi/index.d.ts +207 -0
  108. package/dist/openapi/index.d.ts.map +1 -0
  109. package/dist/openapi/index.js +449 -0
  110. package/dist/openapi/index.js.map +1 -0
  111. package/dist/openapi/schema-introspector.d.ts +38 -0
  112. package/dist/openapi/schema-introspector.d.ts.map +1 -0
  113. package/dist/openapi/schema-introspector.js +67 -0
  114. package/dist/openapi/schema-introspector.js.map +1 -0
  115. package/dist/ports/audit.d.ts +58 -0
  116. package/dist/ports/audit.d.ts.map +1 -0
  117. package/dist/ports/audit.js +74 -0
  118. package/dist/ports/audit.js.map +1 -0
  119. package/dist/ports/auth.d.ts +23 -0
  120. package/dist/ports/auth.d.ts.map +1 -0
  121. package/dist/ports/auth.js +31 -0
  122. package/dist/ports/auth.js.map +1 -0
  123. package/dist/ports/builder.d.ts +61 -0
  124. package/dist/ports/builder.d.ts.map +1 -0
  125. package/dist/ports/builder.js +48 -0
  126. package/dist/ports/builder.js.map +1 -0
  127. package/dist/ports/cache.d.ts +15 -0
  128. package/dist/ports/cache.d.ts.map +1 -0
  129. package/dist/ports/cache.js +57 -0
  130. package/dist/ports/cache.js.map +1 -0
  131. package/dist/ports/clock.d.ts +10 -0
  132. package/dist/ports/clock.d.ts.map +1 -0
  133. package/dist/ports/clock.js +21 -0
  134. package/dist/ports/clock.js.map +1 -0
  135. package/dist/ports/events.d.ts +71 -0
  136. package/dist/ports/events.d.ts.map +1 -0
  137. package/dist/ports/events.js +2 -0
  138. package/dist/ports/events.js.map +1 -0
  139. package/dist/ports/id-generator.d.ts +12 -0
  140. package/dist/ports/id-generator.d.ts.map +1 -0
  141. package/dist/ports/id-generator.js +22 -0
  142. package/dist/ports/id-generator.js.map +1 -0
  143. package/dist/ports/index.d.ts +98 -0
  144. package/dist/ports/index.d.ts.map +1 -0
  145. package/dist/ports/index.js +67 -0
  146. package/dist/ports/index.js.map +1 -0
  147. package/dist/ports/logger.d.ts +22 -0
  148. package/dist/ports/logger.d.ts.map +1 -0
  149. package/dist/ports/logger.js +34 -0
  150. package/dist/ports/logger.js.map +1 -0
  151. package/dist/ports/mailer.d.ts +6 -0
  152. package/dist/ports/mailer.d.ts.map +1 -0
  153. package/dist/ports/mailer.js +2 -0
  154. package/dist/ports/mailer.js.map +1 -0
  155. package/dist/ports/policy.d.ts +53 -0
  156. package/dist/ports/policy.d.ts.map +1 -0
  157. package/dist/ports/policy.js +81 -0
  158. package/dist/ports/policy.js.map +1 -0
  159. package/dist/ports/rate-limit.d.ts +41 -0
  160. package/dist/ports/rate-limit.d.ts.map +1 -0
  161. package/dist/ports/rate-limit.js +37 -0
  162. package/dist/ports/rate-limit.js.map +1 -0
  163. package/dist/ports/redaction.d.ts +26 -0
  164. package/dist/ports/redaction.d.ts.map +1 -0
  165. package/dist/ports/redaction.js +126 -0
  166. package/dist/ports/redaction.js.map +1 -0
  167. package/dist/ports/schedules.d.ts +9 -0
  168. package/dist/ports/schedules.d.ts.map +1 -0
  169. package/dist/ports/schedules.js +2 -0
  170. package/dist/ports/schedules.js.map +1 -0
  171. package/dist/ports/storage.d.ts +47 -0
  172. package/dist/ports/storage.d.ts.map +1 -0
  173. package/dist/ports/storage.js +185 -0
  174. package/dist/ports/storage.js.map +1 -0
  175. package/dist/ports/testing.d.ts +73 -0
  176. package/dist/ports/testing.d.ts.map +1 -0
  177. package/dist/ports/testing.js +105 -0
  178. package/dist/ports/testing.js.map +1 -0
  179. package/dist/ports/unit-of-work.d.ts +56 -0
  180. package/dist/ports/unit-of-work.d.ts.map +1 -0
  181. package/dist/ports/unit-of-work.js +64 -0
  182. package/dist/ports/unit-of-work.js.map +1 -0
  183. package/dist/providers/index.d.ts +8 -0
  184. package/dist/providers/index.d.ts.map +1 -0
  185. package/dist/providers/index.js +8 -0
  186. package/dist/providers/index.js.map +1 -0
  187. package/dist/providers/instrumentation.d.ts +91 -0
  188. package/dist/providers/instrumentation.d.ts.map +1 -0
  189. package/dist/providers/instrumentation.js +93 -0
  190. package/dist/providers/instrumentation.js.map +1 -0
  191. package/dist/providers/provider.d.ts +146 -0
  192. package/dist/providers/provider.d.ts.map +1 -0
  193. package/dist/providers/provider.js +31 -0
  194. package/dist/providers/provider.js.map +1 -0
  195. package/dist/schedules/index.d.ts +105 -0
  196. package/dist/schedules/index.d.ts.map +1 -0
  197. package/dist/schedules/index.js +178 -0
  198. package/dist/schedules/index.js.map +1 -0
  199. package/dist/server/contract-like.d.ts +5 -0
  200. package/dist/server/contract-like.d.ts.map +1 -0
  201. package/dist/server/contract-like.js +5 -0
  202. package/dist/server/contract-like.js.map +1 -0
  203. package/dist/server/health.d.ts +41 -0
  204. package/dist/server/health.d.ts.map +1 -0
  205. package/dist/server/health.js +46 -0
  206. package/dist/server/health.js.map +1 -0
  207. package/dist/server/hooks/auth.d.ts +42 -0
  208. package/dist/server/hooks/auth.d.ts.map +1 -0
  209. package/dist/server/hooks/auth.js +61 -0
  210. package/dist/server/hooks/auth.js.map +1 -0
  211. package/dist/server/hooks/cors.d.ts +13 -0
  212. package/dist/server/hooks/cors.d.ts.map +1 -0
  213. package/dist/server/hooks/cors.js +70 -0
  214. package/dist/server/hooks/cors.js.map +1 -0
  215. package/dist/server/hooks/errors.d.ts +66 -0
  216. package/dist/server/hooks/errors.d.ts.map +1 -0
  217. package/dist/server/hooks/errors.js +83 -0
  218. package/dist/server/hooks/errors.js.map +1 -0
  219. package/dist/server/hooks/index.d.ts +12 -0
  220. package/dist/server/hooks/index.d.ts.map +1 -0
  221. package/dist/server/hooks/index.js +12 -0
  222. package/dist/server/hooks/index.js.map +1 -0
  223. package/dist/server/hooks/logging.d.ts +33 -0
  224. package/dist/server/hooks/logging.d.ts.map +1 -0
  225. package/dist/server/hooks/logging.js +90 -0
  226. package/dist/server/hooks/logging.js.map +1 -0
  227. package/dist/server/hooks/rate-limit.d.ts +29 -0
  228. package/dist/server/hooks/rate-limit.d.ts.map +1 -0
  229. package/dist/server/hooks/rate-limit.js +93 -0
  230. package/dist/server/hooks/rate-limit.js.map +1 -0
  231. package/dist/server/hooks/utils.d.ts +9 -0
  232. package/dist/server/hooks/utils.d.ts.map +1 -0
  233. package/dist/server/hooks/utils.js +16 -0
  234. package/dist/server/hooks/utils.js.map +1 -0
  235. package/dist/server/hooks.d.ts +2 -0
  236. package/dist/server/hooks.d.ts.map +1 -0
  237. package/dist/server/hooks.js +2 -0
  238. package/dist/server/hooks.js.map +1 -0
  239. package/dist/server/http.d.ts +124 -0
  240. package/dist/server/http.d.ts.map +1 -0
  241. package/dist/server/http.js +2 -0
  242. package/dist/server/http.js.map +1 -0
  243. package/dist/server/index.d.ts +19 -0
  244. package/dist/server/index.d.ts.map +1 -0
  245. package/dist/server/index.js +15 -0
  246. package/dist/server/index.js.map +1 -0
  247. package/dist/server/openapi.d.ts +32 -0
  248. package/dist/server/openapi.d.ts.map +1 -0
  249. package/dist/server/openapi.js +43 -0
  250. package/dist/server/openapi.js.map +1 -0
  251. package/dist/server/providers/index.d.ts +4 -0
  252. package/dist/server/providers/index.d.ts.map +1 -0
  253. package/dist/server/providers/index.js +4 -0
  254. package/dist/server/providers/index.js.map +1 -0
  255. package/dist/server/providers/loadProviderConfig.d.ts +7 -0
  256. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -0
  257. package/dist/server/providers/loadProviderConfig.js +42 -0
  258. package/dist/server/providers/loadProviderConfig.js.map +1 -0
  259. package/dist/server/server.d.ts +86 -0
  260. package/dist/server/server.d.ts.map +1 -0
  261. package/dist/server/server.js +1031 -0
  262. package/dist/server/server.js.map +1 -0
  263. package/dist/server/types.d.ts +3 -0
  264. package/dist/server/types.d.ts.map +1 -0
  265. package/dist/server/types.js +3 -0
  266. package/dist/server/types.js.map +1 -0
  267. package/package.json +129 -0
  268. package/src/application/index.ts +747 -0
  269. package/src/client/client.ts +1105 -0
  270. package/src/client/index.ts +45 -0
  271. package/src/client/types.ts +305 -0
  272. package/src/config/index.ts +497 -0
  273. package/src/contracts/contract-builder.ts +583 -0
  274. package/src/contracts/contract-group.ts +502 -0
  275. package/src/contracts/contract-like.ts +29 -0
  276. package/src/contracts/index.ts +53 -0
  277. package/src/contracts/openapi-meta.ts +22 -0
  278. package/src/contracts/path-template.ts +91 -0
  279. package/src/contracts/rate-limit.ts +50 -0
  280. package/src/contracts/types.ts +207 -0
  281. package/src/contracts/utils.ts +56 -0
  282. package/src/domain/entity.ts +256 -0
  283. package/src/domain/events.ts +52 -0
  284. package/src/domain/index.ts +18 -0
  285. package/src/domain/value-object.ts +135 -0
  286. package/src/errors/catalog.ts +149 -0
  287. package/src/errors/http.ts +80 -0
  288. package/src/errors/index.ts +28 -0
  289. package/src/errors/response.ts +54 -0
  290. package/src/errors/validation.ts +35 -0
  291. package/src/events/index.ts +246 -0
  292. package/src/jobs/index.ts +211 -0
  293. package/src/mail/index.ts +177 -0
  294. package/src/openapi/index.ts +865 -0
  295. package/src/openapi/schema-introspector.ts +107 -0
  296. package/src/ports/audit.ts +176 -0
  297. package/src/ports/auth.ts +76 -0
  298. package/src/ports/builder.ts +97 -0
  299. package/src/ports/cache.ts +94 -0
  300. package/src/ports/clock.ts +34 -0
  301. package/src/ports/events.ts +100 -0
  302. package/src/ports/id-generator.ts +36 -0
  303. package/src/ports/index.ts +221 -0
  304. package/src/ports/logger.ts +67 -0
  305. package/src/ports/policy.ts +242 -0
  306. package/src/ports/rate-limit.ts +91 -0
  307. package/src/ports/redaction.ts +199 -0
  308. package/src/ports/storage.ts +282 -0
  309. package/src/ports/testing.ts +234 -0
  310. package/src/ports/unit-of-work.ts +134 -0
  311. package/src/providers/index.ts +40 -0
  312. package/src/providers/instrumentation.ts +248 -0
  313. package/src/providers/provider.ts +191 -0
  314. package/src/schedules/index.ts +442 -0
  315. package/src/server/contract-like.ts +8 -0
  316. package/src/server/health.ts +82 -0
  317. package/src/server/hooks/auth.ts +147 -0
  318. package/src/server/hooks/cors.ts +87 -0
  319. package/src/server/hooks/errors.ts +126 -0
  320. package/src/server/hooks/index.ts +43 -0
  321. package/src/server/hooks/logging.ts +121 -0
  322. package/src/server/hooks/rate-limit.ts +171 -0
  323. package/src/server/hooks/utils.ts +16 -0
  324. package/src/server/hooks.ts +1 -0
  325. package/src/server/http.ts +189 -0
  326. package/src/server/index.ts +35 -0
  327. package/src/server/openapi.ts +72 -0
  328. package/src/server/providers/index.ts +3 -0
  329. package/src/server/providers/loadProviderConfig.ts +72 -0
  330. package/src/server/server.ts +1521 -0
  331. package/src/server/types.ts +2 -0
@@ -0,0 +1,502 @@
1
+ import { ContractBuilder } from "./contract-builder";
2
+ import { parsePathTemplate } from "./path-template";
3
+ import type {
4
+ ContractErrorResponses,
5
+ ContractHeaderSchemas,
6
+ ContractMeta,
7
+ ContractResponses,
8
+ HttpMethod,
9
+ ResponsesFromErrorDefinitions,
10
+ StandardSchema,
11
+ } from "./types";
12
+ import { STANDARD_ERROR_RESPONSE_SCHEMA } from "./types";
13
+ import { generateContractName } from "./utils";
14
+
15
+ type TrimLeadingSlash<T extends string> = T extends `/${infer Rest}`
16
+ ? TrimLeadingSlash<Rest>
17
+ : T;
18
+
19
+ type TrimTrailingSlash<T extends string> = T extends "/"
20
+ ? ""
21
+ : T extends `${infer Rest}/`
22
+ ? TrimTrailingSlash<Rest>
23
+ : T;
24
+
25
+ type NormalizedPrefix<T extends string> = T extends "" | "/"
26
+ ? ""
27
+ : TrimTrailingSlash<T>;
28
+
29
+ type NormalizedChildPath<T extends string> = TrimTrailingSlash<
30
+ TrimLeadingSlash<T>
31
+ >;
32
+
33
+ type JoinPaths<TPrefix extends string, TPath extends string> = string extends
34
+ | TPrefix
35
+ | TPath
36
+ ? string
37
+ : NormalizedPrefix<TPrefix> extends ""
38
+ ? TPath
39
+ : NormalizedChildPath<TPath> extends ""
40
+ ? NormalizedPrefix<TPrefix>
41
+ : `${NormalizedPrefix<TPrefix>}/${NormalizedChildPath<TPath>}`;
42
+
43
+ function joinPathPrefix(prefix: string, path: string): string {
44
+ const segments = [
45
+ ...prefix.split("/").filter(Boolean),
46
+ ...path.split("/").filter(Boolean),
47
+ ];
48
+
49
+ return `/${segments.join("/")}`;
50
+ }
51
+
52
+ function responsesFromErrors(
53
+ errors: ContractErrorResponses,
54
+ ): ContractResponses {
55
+ const responses: ContractResponses = {};
56
+ for (const error of Object.values(errors)) {
57
+ responses[error.status] = STANDARD_ERROR_RESPONSE_SCHEMA;
58
+ }
59
+ return responses;
60
+ }
61
+
62
+ function assertNoResponseStatusConflicts(
63
+ current: ContractResponses,
64
+ next: ContractResponses,
65
+ ): void {
66
+ const currentStatuses = new Set(Object.keys(current));
67
+ const conflicts = Object.keys(next).filter((status) =>
68
+ currentStatuses.has(status),
69
+ );
70
+ if (conflicts.length) {
71
+ throw new Error(
72
+ `.errors() cannot declare status ${conflicts.join(", ")} because a response schema for that status already exists. Use .responses() without .errors() when you need a custom error response body.`,
73
+ );
74
+ }
75
+ }
76
+
77
+ function assertNoCatalogErrorStatusConflicts(
78
+ metadata: ContractMeta,
79
+ next: ContractResponses,
80
+ ): void {
81
+ const errors = metadata.errors;
82
+ if (typeof errors !== "object" || errors === null) return;
83
+
84
+ const errorStatuses = new Set(
85
+ Object.values(errors)
86
+ .map((error) =>
87
+ typeof error === "object" &&
88
+ error !== null &&
89
+ typeof (error as { status?: unknown }).status === "number"
90
+ ? String((error as { status: number }).status)
91
+ : undefined,
92
+ )
93
+ .filter((status): status is string => status !== undefined),
94
+ );
95
+ const conflicts = Object.keys(next).filter((status) =>
96
+ errorStatuses.has(status),
97
+ );
98
+ if (conflicts.length) {
99
+ throw new Error(
100
+ `.responses() cannot declare status ${conflicts.join(", ")} because that status is already declared by .errors(). Use .responses() without .errors() when you need a custom error response body.`,
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * ContractGroup is a feature-scoped factory for creating related contracts
107
+ * with shared configuration. Immutable — each method returns a new instance.
108
+ */
109
+ export class ContractGroup<
110
+ TSharedResponses extends ContractResponses = Record<never, never>,
111
+ TSharedMeta extends ContractMeta = ContractMeta,
112
+ TSharedHeaders extends ContractHeaderSchemas = null,
113
+ TPathPrefix extends string = "",
114
+ > {
115
+ private readonly _namespace: string;
116
+ private readonly _meta: TSharedMeta;
117
+ private readonly _responses: TSharedResponses;
118
+ private readonly _headers: TSharedHeaders;
119
+ private readonly _pathPrefix: TPathPrefix;
120
+
121
+ constructor(
122
+ state: {
123
+ namespace?: string;
124
+ meta?: TSharedMeta;
125
+ responses?: TSharedResponses;
126
+ headers?: TSharedHeaders;
127
+ pathPrefix?: TPathPrefix;
128
+ } = {} as {
129
+ namespace?: string;
130
+ meta?: TSharedMeta;
131
+ responses?: TSharedResponses;
132
+ headers?: TSharedHeaders;
133
+ pathPrefix?: TPathPrefix;
134
+ },
135
+ ) {
136
+ this._namespace = state.namespace ?? "";
137
+ this._meta = state.meta ?? ({} as TSharedMeta);
138
+ this._responses = state.responses ?? ({} as TSharedResponses);
139
+ this._headers = state.headers ?? (null as TSharedHeaders);
140
+ this._pathPrefix = state.pathPrefix ?? ("" as TPathPrefix);
141
+ }
142
+
143
+ /**
144
+ * Set the namespace for all contracts created from this group
145
+ */
146
+ namespace(
147
+ ns: string,
148
+ ): ContractGroup<TSharedResponses, TSharedMeta, TSharedHeaders, TPathPrefix> {
149
+ return new ContractGroup({
150
+ namespace: ns,
151
+ meta: this._meta,
152
+ responses: this._responses,
153
+ headers: this._headers,
154
+ pathPrefix: this._pathPrefix,
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Add a path prefix to all contracts created from this group.
160
+ *
161
+ * Prefixes compose immutably, so `prefix("/api").prefix("/v1")`
162
+ * produces paths under `/api/v1`.
163
+ */
164
+ prefix<const TNewPrefix extends string>(
165
+ pathPrefix: TNewPrefix,
166
+ ): ContractGroup<
167
+ TSharedResponses,
168
+ TSharedMeta,
169
+ TSharedHeaders,
170
+ JoinPaths<TPathPrefix, TNewPrefix>
171
+ > {
172
+ parsePathTemplate(pathPrefix);
173
+ const nextPrefix = joinPathPrefix(this._pathPrefix, pathPrefix);
174
+ const normalizedPrefix = nextPrefix === "/" ? "" : nextPrefix;
175
+
176
+ return new ContractGroup({
177
+ namespace: this._namespace,
178
+ meta: this._meta,
179
+ responses: this._responses,
180
+ headers: this._headers,
181
+ pathPrefix: normalizedPrefix as JoinPaths<TPathPrefix, TNewPrefix>,
182
+ });
183
+ }
184
+
185
+ /**
186
+ * Set shared metadata for all contracts created from this group
187
+ */
188
+ meta<TNewMeta extends ContractMeta>(
189
+ meta: TNewMeta,
190
+ ): ContractGroup<
191
+ TSharedResponses,
192
+ Omit<TSharedMeta, keyof TNewMeta> & TNewMeta,
193
+ TSharedHeaders,
194
+ TPathPrefix
195
+ > {
196
+ return new ContractGroup({
197
+ namespace: this._namespace,
198
+ meta: { ...this._meta, ...meta } as unknown as Omit<
199
+ TSharedMeta,
200
+ keyof TNewMeta
201
+ > &
202
+ TNewMeta,
203
+ responses: this._responses,
204
+ headers: this._headers,
205
+ pathPrefix: this._pathPrefix,
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Set shared response schemas for all contracts created from this group.
211
+ * Typically used for shared error responses (e.g. 401, 403, 500).
212
+ */
213
+ responses<TNewResponses extends ContractResponses>(
214
+ responseSchemas: TNewResponses,
215
+ ): ContractGroup<
216
+ Omit<TSharedResponses, keyof TNewResponses> & TNewResponses,
217
+ TSharedMeta,
218
+ TSharedHeaders,
219
+ TPathPrefix
220
+ > {
221
+ assertNoCatalogErrorStatusConflicts(this._meta, responseSchemas);
222
+ return new ContractGroup({
223
+ namespace: this._namespace,
224
+ meta: this._meta,
225
+ responses: { ...this._responses, ...responseSchemas } as unknown as Omit<
226
+ TSharedResponses,
227
+ keyof TNewResponses
228
+ > &
229
+ TNewResponses,
230
+ headers: this._headers,
231
+ pathPrefix: this._pathPrefix,
232
+ });
233
+ }
234
+
235
+ /**
236
+ * Set shared route-owned application errors for all contracts in the group.
237
+ */
238
+ errors<TErrorDefs extends ContractErrorResponses>(
239
+ errorDefs: TErrorDefs,
240
+ ): ContractGroup<
241
+ Omit<TSharedResponses, keyof ResponsesFromErrorDefinitions<TErrorDefs>> &
242
+ ResponsesFromErrorDefinitions<TErrorDefs>,
243
+ Omit<TSharedMeta, "errors"> & { errors: TErrorDefs },
244
+ TSharedHeaders,
245
+ TPathPrefix
246
+ > {
247
+ const errorResponses = responsesFromErrors(errorDefs);
248
+ assertNoResponseStatusConflicts(this._responses, errorResponses);
249
+ return new ContractGroup({
250
+ namespace: this._namespace,
251
+ meta: {
252
+ ...this._meta,
253
+ errors: errorDefs,
254
+ } as Omit<TSharedMeta, "errors"> & { errors: TErrorDefs },
255
+ responses: { ...this._responses, ...errorResponses } as unknown as Omit<
256
+ TSharedResponses,
257
+ keyof ResponsesFromErrorDefinitions<TErrorDefs>
258
+ > &
259
+ ResponsesFromErrorDefinitions<TErrorDefs>,
260
+ headers: this._headers,
261
+ pathPrefix: this._pathPrefix,
262
+ });
263
+ }
264
+
265
+ /**
266
+ * Add shared request header schema for all contracts created from this group.
267
+ */
268
+ headers<TNewHeaders extends StandardSchema>(
269
+ schema: TNewHeaders,
270
+ ): ContractGroup<
271
+ TSharedResponses,
272
+ TSharedMeta,
273
+ TSharedHeaders extends readonly StandardSchema[]
274
+ ? readonly [...TSharedHeaders, TNewHeaders]
275
+ : TSharedHeaders extends StandardSchema
276
+ ? readonly [TSharedHeaders, TNewHeaders]
277
+ : readonly [TNewHeaders],
278
+ TPathPrefix
279
+ > {
280
+ const existingHeaders = this._headers
281
+ ? Array.isArray(this._headers)
282
+ ? this._headers
283
+ : [this._headers]
284
+ : [];
285
+
286
+ return new ContractGroup({
287
+ namespace: this._namespace,
288
+ meta: this._meta,
289
+ responses: this._responses,
290
+ headers: [
291
+ ...existingHeaders,
292
+ schema,
293
+ ] as unknown as TSharedHeaders extends readonly StandardSchema[]
294
+ ? readonly [...TSharedHeaders, TNewHeaders]
295
+ : TSharedHeaders extends StandardSchema
296
+ ? readonly [TSharedHeaders, TNewHeaders]
297
+ : readonly [TNewHeaders],
298
+ pathPrefix: this._pathPrefix,
299
+ });
300
+ }
301
+
302
+ /**
303
+ * Create a GET contract
304
+ */
305
+ get<const TPath extends string>(
306
+ path: TPath,
307
+ name?: string,
308
+ ): ContractBuilder<
309
+ "GET",
310
+ null,
311
+ null,
312
+ null,
313
+ TSharedHeaders,
314
+ TSharedResponses,
315
+ TSharedMeta,
316
+ JoinPaths<TPathPrefix, TPath>
317
+ > {
318
+ return this.createBuilder("GET", path, name);
319
+ }
320
+
321
+ /**
322
+ * Create a POST contract
323
+ */
324
+ post<const TPath extends string>(
325
+ path: TPath,
326
+ name?: string,
327
+ ): ContractBuilder<
328
+ "POST",
329
+ null,
330
+ null,
331
+ null,
332
+ TSharedHeaders,
333
+ TSharedResponses,
334
+ TSharedMeta,
335
+ JoinPaths<TPathPrefix, TPath>
336
+ > {
337
+ return this.createBuilder("POST", path, name);
338
+ }
339
+
340
+ /**
341
+ * Create a PUT contract
342
+ */
343
+ put<const TPath extends string>(
344
+ path: TPath,
345
+ name?: string,
346
+ ): ContractBuilder<
347
+ "PUT",
348
+ null,
349
+ null,
350
+ null,
351
+ TSharedHeaders,
352
+ TSharedResponses,
353
+ TSharedMeta,
354
+ JoinPaths<TPathPrefix, TPath>
355
+ > {
356
+ return this.createBuilder("PUT", path, name);
357
+ }
358
+
359
+ /**
360
+ * Create a PATCH contract
361
+ */
362
+ patch<const TPath extends string>(
363
+ path: TPath,
364
+ name?: string,
365
+ ): ContractBuilder<
366
+ "PATCH",
367
+ null,
368
+ null,
369
+ null,
370
+ TSharedHeaders,
371
+ TSharedResponses,
372
+ TSharedMeta,
373
+ JoinPaths<TPathPrefix, TPath>
374
+ > {
375
+ return this.createBuilder("PATCH", path, name);
376
+ }
377
+
378
+ /**
379
+ * Create a DELETE contract
380
+ */
381
+ delete<const TPath extends string>(
382
+ path: TPath,
383
+ name?: string,
384
+ ): ContractBuilder<
385
+ "DELETE",
386
+ null,
387
+ null,
388
+ null,
389
+ TSharedHeaders,
390
+ TSharedResponses,
391
+ TSharedMeta,
392
+ JoinPaths<TPathPrefix, TPath>
393
+ > {
394
+ return this.createBuilder("DELETE", path, name);
395
+ }
396
+
397
+ /**
398
+ * Create a HEAD contract
399
+ */
400
+ head<const TPath extends string>(
401
+ path: TPath,
402
+ name?: string,
403
+ ): ContractBuilder<
404
+ "HEAD",
405
+ null,
406
+ null,
407
+ null,
408
+ TSharedHeaders,
409
+ TSharedResponses,
410
+ TSharedMeta,
411
+ JoinPaths<TPathPrefix, TPath>
412
+ > {
413
+ return this.createBuilder("HEAD", path, name);
414
+ }
415
+
416
+ /**
417
+ * Create a OPTIONS contract
418
+ */
419
+ options<const TPath extends string>(
420
+ path: TPath,
421
+ name?: string,
422
+ ): ContractBuilder<
423
+ "OPTIONS",
424
+ null,
425
+ null,
426
+ null,
427
+ TSharedHeaders,
428
+ TSharedResponses,
429
+ TSharedMeta,
430
+ JoinPaths<TPathPrefix, TPath>
431
+ > {
432
+ return this.createBuilder("OPTIONS", path, name);
433
+ }
434
+
435
+ /**
436
+ * Internal helper to create a contract builder with shared config
437
+ */
438
+ private createBuilder<TMethod extends HttpMethod, const TPath extends string>(
439
+ method: TMethod,
440
+ path: TPath,
441
+ name?: string,
442
+ ): ContractBuilder<
443
+ TMethod,
444
+ null,
445
+ null,
446
+ null,
447
+ TSharedHeaders,
448
+ TSharedResponses,
449
+ TSharedMeta,
450
+ JoinPaths<TPathPrefix, TPath>
451
+ > {
452
+ parsePathTemplate(path);
453
+ const fullPath = (
454
+ this._pathPrefix ? joinPathPrefix(this._pathPrefix, path) : path
455
+ ) as JoinPaths<TPathPrefix, TPath>;
456
+ parsePathTemplate(fullPath);
457
+ const contractName = name || generateContractName(method, fullPath);
458
+ const fullName = this._namespace
459
+ ? `${this._namespace}.${contractName}`
460
+ : contractName;
461
+
462
+ return new ContractBuilder({
463
+ kind: "http",
464
+ name: fullName,
465
+ namespace: this._namespace || undefined,
466
+ localName: contractName,
467
+ method,
468
+ path: fullPath,
469
+ pathParams: null,
470
+ query: null,
471
+ headers: this._headers,
472
+ body: null,
473
+ responses: { ...this._responses } as unknown as TSharedResponses,
474
+ metadata: { ...this._meta } as unknown as TSharedMeta,
475
+ });
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Create a new ContractGroup for grouping related contracts with shared configuration.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * const todos = createContractGroup()
485
+ * .namespace("todos")
486
+ * .prefix("/api/todos")
487
+ * .meta({ auth: "required" })
488
+ * .responses({
489
+ * 401: z.object({ message: z.literal("Unauthorized") }),
490
+ * });
491
+ *
492
+ * const getTodo = todos.get("/:id")...
493
+ * ```
494
+ */
495
+ export function createContractGroup(): ContractGroup<
496
+ Record<never, never>,
497
+ ContractMeta,
498
+ null,
499
+ ""
500
+ > {
501
+ return new ContractGroup();
502
+ }
@@ -0,0 +1,29 @@
1
+ import type { HttpContractConfig } from "./types";
2
+
3
+ /**
4
+ * A type that represents either a raw HttpContractConfig
5
+ * or a contract-like object with a .config property (e.g., ContractBuilder)
6
+ */
7
+ export type ContractLike = HttpContractConfig | { config: HttpContractConfig };
8
+
9
+ /**
10
+ * Resolves a ContractLike to its underlying HttpContractConfig
11
+ */
12
+ export type ResolveContract<T> = T extends { config: infer C }
13
+ ? C extends HttpContractConfig
14
+ ? C
15
+ : never
16
+ : T extends HttpContractConfig
17
+ ? T
18
+ : never;
19
+
20
+ /**
21
+ * Helper function to resolve a ContractLike to its underlying HttpContractConfig
22
+ * @param c - The contract-like object to resolve
23
+ * @returns The underlying HttpContractConfig
24
+ */
25
+ export function resolveContract<T extends ContractLike>(
26
+ c: T,
27
+ ): ResolveContract<T> {
28
+ return ("config" in c ? c.config : c) as ResolveContract<T>;
29
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * @beignet/core/contracts
3
+ *
4
+ * HTTP contract definitions and builders for Beignet.
5
+ */
6
+
7
+ export type { StandardSchemaV1 } from "@standard-schema/spec";
8
+
9
+ export {
10
+ ContractBuilder,
11
+ type CreateContractOptions,
12
+ createContract,
13
+ } from "./contract-builder";
14
+ export { ContractGroup, createContractGroup } from "./contract-group";
15
+ export {
16
+ type ContractLike,
17
+ type ResolveContract,
18
+ resolveContract,
19
+ } from "./contract-like";
20
+ export type { OpenAPIOperationMeta } from "./openapi-meta";
21
+ export {
22
+ type ParsedPathTemplate,
23
+ type PathTemplateSegment,
24
+ parsePathTemplate,
25
+ } from "./path-template";
26
+ export type { RateLimitMeta, RateLimitScope } from "./rate-limit";
27
+ export type {
28
+ AnyContract,
29
+ BodyHttpMethod,
30
+ ContractErrorDefinition,
31
+ ContractErrorResponses,
32
+ ContractHeaderSchemas,
33
+ ContractMeta,
34
+ ContractResponseSchema,
35
+ ContractResponses,
36
+ HttpContractConfig,
37
+ HttpMethod,
38
+ InferHeaderSchemaInput,
39
+ InferHeaderSchemaOutput,
40
+ InferInput,
41
+ InferOutput,
42
+ ResponsesFromErrorDefinitions,
43
+ StandardErrorResponseBody,
44
+ StandardErrorResponseSchema,
45
+ StandardSchema,
46
+ } from "./types";
47
+ export {
48
+ BEIGNET_ERROR_OWNER_HEADER,
49
+ BODY_HTTP_METHODS,
50
+ getContractHeaderSchemas,
51
+ methodSupportsRequestBody,
52
+ STANDARD_ERROR_RESPONSE_SCHEMA,
53
+ } from "./types";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * OpenAPI operation-level metadata for HTTP contracts
3
+ */
4
+ export type OpenAPIOperationMeta = {
5
+ /** A brief summary of the operation */
6
+ summary?: string;
7
+ /** A detailed description of the operation */
8
+ description?: string;
9
+ /** Tags for grouping operations */
10
+ tags?: string[];
11
+ /** Marks the operation as deprecated */
12
+ deprecated?: boolean;
13
+ /** External documentation reference */
14
+ externalDocs?: {
15
+ description?: string;
16
+ url: string;
17
+ };
18
+ /** Override for operationId (default is contract.name) */
19
+ operationId?: string;
20
+ /** Per-operation security requirements */
21
+ security?: Array<Record<string, string[]>>;
22
+ };
@@ -0,0 +1,91 @@
1
+ export type PathTemplateSegment =
2
+ | { kind: "static"; value: string }
3
+ | { kind: "dynamic"; name: string; raw: string };
4
+
5
+ export interface ParsedPathTemplate {
6
+ keys: string[];
7
+ segments: PathTemplateSegment[];
8
+ normalizedPath: string;
9
+ shapeKey: string;
10
+ openApiPath: string;
11
+ }
12
+
13
+ const PARAM_NAME = "[A-Za-z0-9_-]+";
14
+ const COLON_PARAM = new RegExp(`^:(${PARAM_NAME})$`);
15
+ const BRACKET_PARAM = new RegExp(`^\\[(${PARAM_NAME})\\]$`);
16
+
17
+ function createPathTemplateError(path: string, segment: string): Error {
18
+ return new Error(
19
+ `Unsupported path template segment "${segment}" in "${path}". ` +
20
+ "Use single-segment params like :id or [id]. Catch-all and partial-segment params are not supported.",
21
+ );
22
+ }
23
+
24
+ function parsePathSegment(path: string, segment: string): PathTemplateSegment {
25
+ const colonMatch = segment.match(COLON_PARAM);
26
+ if (colonMatch) {
27
+ return { kind: "dynamic", name: colonMatch[1], raw: segment };
28
+ }
29
+
30
+ const bracketMatch = segment.match(BRACKET_PARAM);
31
+ if (bracketMatch) {
32
+ return { kind: "dynamic", name: bracketMatch[1], raw: segment };
33
+ }
34
+
35
+ if (
36
+ segment.startsWith(":") ||
37
+ segment.startsWith("[") ||
38
+ segment.endsWith("]")
39
+ ) {
40
+ throw createPathTemplateError(path, segment);
41
+ }
42
+
43
+ return { kind: "static", value: segment };
44
+ }
45
+
46
+ export function parsePathTemplate(path: string): ParsedPathTemplate {
47
+ if (!path.startsWith("/")) {
48
+ throw new Error(
49
+ `Invalid path template "${path}". Paths must start with "/".`,
50
+ );
51
+ }
52
+
53
+ const segments = path
54
+ .split("/")
55
+ .filter(Boolean)
56
+ .map((segment) => parsePathSegment(path, segment));
57
+
58
+ const keys = segments
59
+ .filter(
60
+ (segment): segment is Extract<PathTemplateSegment, { kind: "dynamic" }> =>
61
+ segment.kind === "dynamic",
62
+ )
63
+ .map((segment) => segment.name);
64
+
65
+ const duplicateKeys = keys.filter(
66
+ (key, index) => keys.indexOf(key) !== index,
67
+ );
68
+ if (duplicateKeys.length) {
69
+ throw new Error(
70
+ `Invalid path template "${path}". Path parameter names must be unique; duplicate parameter "${duplicateKeys[0]}" was found.`,
71
+ );
72
+ }
73
+
74
+ const normalizedPath = `/${segments
75
+ .map((segment) =>
76
+ segment.kind === "static" ? segment.value : `:${segment.name}`,
77
+ )
78
+ .join("/")}`;
79
+
80
+ const shapeKey = `/${segments
81
+ .map((segment) => (segment.kind === "static" ? segment.value : ":"))
82
+ .join("/")}`;
83
+
84
+ const openApiPath = `/${segments
85
+ .map((segment) =>
86
+ segment.kind === "static" ? segment.value : `{${segment.name}}`,
87
+ )
88
+ .join("/")}`;
89
+
90
+ return { keys, segments, normalizedPath, shapeKey, openApiPath };
91
+ }