@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,747 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ /**
4
+ * Any Standard Schema validator type
5
+ */
6
+ export type StandardSchema = StandardSchemaV1<unknown, unknown>;
7
+
8
+ /**
9
+ * Infer the output type from a Standard Schema
10
+ */
11
+ export type InferOutput<T extends StandardSchemaV1> =
12
+ StandardSchemaV1.InferOutput<T>;
13
+
14
+ /**
15
+ * Infer the input type accepted by a Standard Schema
16
+ */
17
+ export type InferInput<T extends StandardSchemaV1> =
18
+ StandardSchemaV1.InferInput<T>;
19
+
20
+ type SchemaInput<T> = T extends StandardSchemaV1 ? InferInput<T> : never;
21
+ type SchemaOutput<T> = T extends StandardSchemaV1 ? InferOutput<T> : never;
22
+
23
+ export type UseCaseValidationPhase = "input" | "output";
24
+
25
+ /**
26
+ * Error thrown when a use case input or output fails schema validation.
27
+ */
28
+ export class UseCaseValidationError extends Error {
29
+ readonly name = "UseCaseValidationError";
30
+ readonly useCaseName: string;
31
+ readonly phase: UseCaseValidationPhase;
32
+ readonly issues: readonly StandardSchemaV1.Issue[];
33
+
34
+ constructor(args: {
35
+ useCaseName: string;
36
+ phase: UseCaseValidationPhase;
37
+ issues: readonly StandardSchemaV1.Issue[];
38
+ }) {
39
+ super(
40
+ `Use case "${args.useCaseName}" ${args.phase} validation failed: ${formatIssues(args.issues)}`,
41
+ );
42
+ this.useCaseName = args.useCaseName;
43
+ this.phase = args.phase;
44
+ this.issues = args.issues;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Error thrown when a use case tries to emit an event it did not declare with
50
+ * `.emits(...)`.
51
+ */
52
+ export class UseCaseEventDeclarationError extends Error {
53
+ readonly name = "UseCaseEventDeclarationError";
54
+ readonly useCaseName: string;
55
+ readonly eventName: string;
56
+ readonly declaredEventNames: readonly string[];
57
+
58
+ constructor(args: {
59
+ useCaseName: string;
60
+ eventName: string;
61
+ declaredEventNames: readonly string[];
62
+ }) {
63
+ const declared =
64
+ args.declaredEventNames.length > 0
65
+ ? args.declaredEventNames.map((name) => `"${name}"`).join(", ")
66
+ : "none";
67
+
68
+ super(
69
+ `Use case "${args.useCaseName}" cannot emit undeclared event "${args.eventName}". Declare it with .emits([...]). Declared events: ${declared}.`,
70
+ );
71
+ this.useCaseName = args.useCaseName;
72
+ this.eventName = args.eventName;
73
+ this.declaredEventNames = args.declaredEventNames;
74
+ }
75
+ }
76
+
77
+ function formatPath(path: StandardSchemaV1.Issue["path"]): string {
78
+ if (!path?.length) return "";
79
+ return path
80
+ .map((segment) =>
81
+ typeof segment === "object" && segment !== null && "key" in segment
82
+ ? String(segment.key)
83
+ : String(segment),
84
+ )
85
+ .join(".");
86
+ }
87
+
88
+ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
89
+ return issues
90
+ .map((issue) => {
91
+ const path = formatPath(issue.path);
92
+ return path ? `${path}: ${issue.message}` : issue.message;
93
+ })
94
+ .join("; ");
95
+ }
96
+
97
+ /**
98
+ * Error thrown when a use-case event helper fails to validate an event payload
99
+ * before recording or publishing it.
100
+ */
101
+ export class UseCaseEventValidationError extends Error {
102
+ readonly name = "UseCaseEventValidationError";
103
+ readonly useCaseName: string;
104
+ readonly eventName: string;
105
+ readonly issues: readonly StandardSchemaV1.Issue[];
106
+
107
+ constructor(args: {
108
+ useCaseName: string;
109
+ eventName: string;
110
+ issues: readonly StandardSchemaV1.Issue[];
111
+ }) {
112
+ super(
113
+ `Use case "${args.useCaseName}" event "${args.eventName}" payload validation failed: ${formatIssues(args.issues)}`,
114
+ );
115
+ this.useCaseName = args.useCaseName;
116
+ this.eventName = args.eventName;
117
+ this.issues = args.issues;
118
+ }
119
+ }
120
+
121
+ async function parseSchema<TSchema extends StandardSchemaV1>(
122
+ schema: TSchema,
123
+ value: unknown,
124
+ useCaseName: string,
125
+ phase: UseCaseValidationPhase,
126
+ ): Promise<InferOutput<TSchema>> {
127
+ const result = await schema["~standard"].validate(value);
128
+ if (result.issues?.length) {
129
+ throw new UseCaseValidationError({
130
+ useCaseName,
131
+ phase,
132
+ issues: result.issues,
133
+ });
134
+ }
135
+ if ("value" in result) {
136
+ return result.value as InferOutput<TSchema>;
137
+ }
138
+ throw new Error("Invalid Standard Schema result: missing value");
139
+ }
140
+
141
+ /**
142
+ * Represents a domain event definition (from @beignet/core/domain or compatible).
143
+ * This is a minimal interface to avoid hard dependency on @beignet/core/domain.
144
+ */
145
+ export interface DomainEventLike {
146
+ name: string;
147
+ payload: StandardSchema;
148
+ }
149
+
150
+ /**
151
+ * Infer the output payload type from a use-case event definition.
152
+ */
153
+ export type InferUseCaseEventPayload<E extends DomainEventLike> =
154
+ E["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;
155
+
156
+ /**
157
+ * Minimal recorder shape accepted by use-case event helpers.
158
+ */
159
+ export interface UseCaseEventRecorderTarget {
160
+ record<E extends DomainEventLike>(
161
+ event: E,
162
+ payload: InferUseCaseEventPayload<E>,
163
+ ): void;
164
+ }
165
+
166
+ /**
167
+ * Minimal event-bus shape accepted by use-case event helpers.
168
+ */
169
+ export interface UseCaseEventBusTarget {
170
+ publish<E extends DomainEventLike>(
171
+ event: E,
172
+ payload: InferUseCaseEventPayload<E>,
173
+ ): Promise<void> | void;
174
+ }
175
+
176
+ /**
177
+ * Event helper scoped to the events declared by a use case.
178
+ */
179
+ export interface UseCaseEventHelpers<Emits extends readonly DomainEventLike[]> {
180
+ /**
181
+ * The exact event definitions declared with `.emits(...)`.
182
+ */
183
+ readonly declared: Emits;
184
+
185
+ /**
186
+ * Return whether an event is declared by this use case.
187
+ */
188
+ isDeclared(event: DomainEventLike): boolean;
189
+
190
+ /**
191
+ * Throw if an event is not declared by this use case.
192
+ */
193
+ assertDeclared(event: DomainEventLike): void;
194
+
195
+ /**
196
+ * Validate and record a declared event into a transaction-scoped recorder.
197
+ */
198
+ record<E extends Emits[number]>(
199
+ recorder: UseCaseEventRecorderTarget,
200
+ event: E,
201
+ payload: InferUseCaseEventPayload<E>,
202
+ ): Promise<void>;
203
+
204
+ /**
205
+ * Validate and publish a declared event directly through an event bus.
206
+ */
207
+ publish<E extends Emits[number]>(
208
+ eventBus: UseCaseEventBusTarget,
209
+ event: E,
210
+ payload: InferUseCaseEventPayload<E>,
211
+ ): Promise<void>;
212
+ }
213
+
214
+ async function parseEventPayload<E extends DomainEventLike>(
215
+ event: E,
216
+ payload: unknown,
217
+ useCaseName: string,
218
+ ): Promise<InferUseCaseEventPayload<E>> {
219
+ const result = await event.payload["~standard"].validate(payload);
220
+
221
+ if (result.issues?.length) {
222
+ throw new UseCaseEventValidationError({
223
+ useCaseName,
224
+ eventName: event.name,
225
+ issues: result.issues,
226
+ });
227
+ }
228
+
229
+ if ("value" in result) {
230
+ return result.value as InferUseCaseEventPayload<E>;
231
+ }
232
+
233
+ throw new Error("Invalid Standard Schema result: missing value");
234
+ }
235
+
236
+ function createUseCaseEventHelpers<Emits extends readonly DomainEventLike[]>(
237
+ useCaseName: string,
238
+ declared: Emits,
239
+ ): UseCaseEventHelpers<Emits> {
240
+ const declaredEventNames = declared.map((event) => event.name);
241
+ const declaredEventByName = new Map<string, DomainEventLike>();
242
+
243
+ for (const event of declared) {
244
+ if (declaredEventByName.has(event.name)) {
245
+ throw new Error(
246
+ `Use case "${useCaseName}" declares duplicate event "${event.name}". Event names must be unique within .emits([...]).`,
247
+ );
248
+ }
249
+
250
+ declaredEventByName.set(event.name, event);
251
+ }
252
+
253
+ function resolveDeclared(event: DomainEventLike): DomainEventLike {
254
+ const declaredEvent = declaredEventByName.get(event.name);
255
+ if (declaredEvent) return declaredEvent;
256
+
257
+ throw new UseCaseEventDeclarationError({
258
+ useCaseName,
259
+ eventName: event.name,
260
+ declaredEventNames,
261
+ });
262
+ }
263
+
264
+ function assertDeclared(event: DomainEventLike): void {
265
+ resolveDeclared(event);
266
+ }
267
+
268
+ return {
269
+ declared,
270
+ isDeclared(event) {
271
+ return declaredEventByName.has(event.name);
272
+ },
273
+ assertDeclared,
274
+ async record(recorder, event, payload) {
275
+ const declaredEvent = resolveDeclared(event) as typeof event;
276
+ const parsed = await parseEventPayload(
277
+ declaredEvent,
278
+ payload,
279
+ useCaseName,
280
+ );
281
+ recorder.record(declaredEvent, parsed);
282
+ },
283
+ async publish(eventBus, event, payload) {
284
+ const declaredEvent = resolveDeclared(event) as typeof event;
285
+ const parsed = await parseEventPayload(
286
+ declaredEvent,
287
+ payload,
288
+ useCaseName,
289
+ );
290
+ await eventBus.publish(declaredEvent, parsed);
291
+ },
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Use case kind - distinguishes commands (write/side-effect) from queries (read-only)
297
+ */
298
+ export type UseCaseKind = "command" | "query";
299
+
300
+ /**
301
+ * Use case definition - the final form consumed by server adapters
302
+ */
303
+ export interface UseCaseDef<
304
+ Ctx,
305
+ Name extends string,
306
+ Kind extends UseCaseKind,
307
+ InputSchema extends StandardSchemaV1,
308
+ OutputSchema extends StandardSchemaV1,
309
+ Emits extends readonly DomainEventLike[] = readonly [],
310
+ > {
311
+ name: Name;
312
+ kind: Kind;
313
+ /** Input schema, suitable for reuse in HTTP contracts and forms. */
314
+ inputSchema: InputSchema;
315
+ /** Output schema, suitable for reuse in HTTP contracts and clients. */
316
+ outputSchema: OutputSchema;
317
+ emits: Emits;
318
+ run: (args: {
319
+ ctx: Ctx;
320
+ input: InferInput<InputSchema>;
321
+ }) => Promise<InferOutput<OutputSchema>>;
322
+ }
323
+
324
+ /**
325
+ * Event passed to the onRun hook for instrumentation
326
+ */
327
+ export interface UseCaseRunEvent<Ctx> {
328
+ name: string;
329
+ kind: UseCaseKind;
330
+ phase: "start" | "end" | "error";
331
+ durationMs?: number;
332
+ error?: unknown;
333
+ ctx: Ctx;
334
+ }
335
+
336
+ /**
337
+ * Options for createUseCase
338
+ */
339
+ export interface CreateUseCaseOptions<Ctx> {
340
+ /**
341
+ * Optional instrumentation hook called on use case start, end, and error.
342
+ */
343
+ onRun?: (event: UseCaseRunEvent<Ctx>) => void;
344
+
345
+ /**
346
+ * Enable or disable schema validation for use case boundaries.
347
+ *
348
+ * Defaults to validating both input and output. Pass `false` to opt out, or
349
+ * configure phases independently with `{ input: boolean, output: boolean }`.
350
+ */
351
+ validate?: boolean | { input?: boolean; output?: boolean };
352
+ }
353
+
354
+ type ValidationOptions = {
355
+ input: boolean;
356
+ output: boolean;
357
+ };
358
+
359
+ function normalizeValidationOptions(
360
+ validate: CreateUseCaseOptions<unknown>["validate"],
361
+ ): ValidationOptions {
362
+ if (validate === false) {
363
+ return { input: false, output: false };
364
+ }
365
+ if (typeof validate === "object" && validate !== null) {
366
+ return {
367
+ input: validate.input ?? true,
368
+ output: validate.output ?? true,
369
+ };
370
+ }
371
+ return { input: true, output: true };
372
+ }
373
+
374
+ /**
375
+ * Internal configuration for the use case builder
376
+ */
377
+ interface UseCaseBuilderConfig<
378
+ Name extends string,
379
+ Kind extends UseCaseKind,
380
+ InputSchema extends StandardSchemaV1 | undefined,
381
+ OutputSchema extends StandardSchemaV1 | undefined,
382
+ Emits extends readonly DomainEventLike[],
383
+ > {
384
+ name: Name;
385
+ kind: Kind;
386
+ input?: InputSchema;
387
+ output?: OutputSchema;
388
+ emits: Emits;
389
+ }
390
+
391
+ /**
392
+ * Fluent builder for creating use cases
393
+ */
394
+ class UseCaseBuilder<
395
+ Ctx,
396
+ Name extends string,
397
+ Kind extends UseCaseKind,
398
+ InputSchema extends StandardSchemaV1 | undefined,
399
+ OutputSchema extends StandardSchemaV1 | undefined,
400
+ Emits extends readonly DomainEventLike[] = readonly [],
401
+ > {
402
+ constructor(
403
+ private readonly config: UseCaseBuilderConfig<
404
+ Name,
405
+ Kind,
406
+ InputSchema,
407
+ OutputSchema,
408
+ Emits
409
+ >,
410
+ private readonly onRun?: (event: UseCaseRunEvent<Ctx>) => void,
411
+ private readonly validation: ValidationOptions = {
412
+ input: true,
413
+ output: true,
414
+ },
415
+ ) {}
416
+
417
+ /**
418
+ * Define the input schema for this use case
419
+ */
420
+ input<I extends StandardSchemaV1>(
421
+ schema: I,
422
+ ): UseCaseBuilder<Ctx, Name, Kind, I, OutputSchema, Emits> {
423
+ return new UseCaseBuilder<Ctx, Name, Kind, I, OutputSchema, Emits>(
424
+ {
425
+ ...this.config,
426
+ input: schema,
427
+ },
428
+ this.onRun,
429
+ this.validation,
430
+ );
431
+ }
432
+
433
+ /**
434
+ * Define the output schema for this use case
435
+ */
436
+ output<O extends StandardSchemaV1>(
437
+ schema: O,
438
+ ): UseCaseBuilder<Ctx, Name, Kind, InputSchema, O, Emits> {
439
+ return new UseCaseBuilder<Ctx, Name, Kind, InputSchema, O, Emits>(
440
+ {
441
+ ...this.config,
442
+ output: schema,
443
+ },
444
+ this.onRun,
445
+ this.validation,
446
+ );
447
+ }
448
+
449
+ /**
450
+ * Define the domain events that this use case may emit.
451
+ */
452
+ emits<E extends readonly DomainEventLike[]>(
453
+ events: E,
454
+ ): UseCaseBuilder<Ctx, Name, Kind, InputSchema, OutputSchema, E> {
455
+ return new UseCaseBuilder<Ctx, Name, Kind, InputSchema, OutputSchema, E>(
456
+ {
457
+ ...this.config,
458
+ emits: events,
459
+ },
460
+ this.onRun,
461
+ this.validation,
462
+ );
463
+ }
464
+
465
+ /**
466
+ * Define the run function and finalize the use case definition
467
+ */
468
+ run(
469
+ fn: InputSchema extends StandardSchemaV1
470
+ ? OutputSchema extends StandardSchemaV1
471
+ ? (args: {
472
+ ctx: Ctx;
473
+ input: SchemaOutput<InputSchema>;
474
+ events: UseCaseEventHelpers<Emits>;
475
+ }) => Promise<SchemaOutput<OutputSchema>> | SchemaOutput<OutputSchema>
476
+ : never
477
+ : never,
478
+ ): InputSchema extends StandardSchemaV1
479
+ ? OutputSchema extends StandardSchemaV1
480
+ ? UseCaseDef<Ctx, Name, Kind, InputSchema, OutputSchema, Emits>
481
+ : never
482
+ : never {
483
+ if (!this.config.input) {
484
+ throw new Error(`Use case "${this.config.name}" is missing input schema`);
485
+ }
486
+ if (!this.config.output) {
487
+ throw new Error(
488
+ `Use case "${this.config.name}" is missing output schema`,
489
+ );
490
+ }
491
+
492
+ const useCaseName = this.config.name;
493
+ const useCaseKind = this.config.kind;
494
+ const onRun = this.onRun;
495
+ const inputSchema = this.config.input as Extract<
496
+ InputSchema,
497
+ StandardSchemaV1
498
+ >;
499
+ const outputSchema = this.config.output as Extract<
500
+ OutputSchema,
501
+ StandardSchemaV1
502
+ >;
503
+ const validation = this.validation;
504
+ const eventHelpers = createUseCaseEventHelpers(
505
+ useCaseName,
506
+ this.config.emits,
507
+ );
508
+
509
+ const runFn = async (
510
+ args: InputSchema extends StandardSchemaV1
511
+ ? OutputSchema extends StandardSchemaV1
512
+ ? {
513
+ ctx: Ctx;
514
+ input: SchemaInput<InputSchema>;
515
+ }
516
+ : never
517
+ : never,
518
+ ) => {
519
+ const startedAt = Date.now();
520
+ onRun?.({
521
+ name: useCaseName,
522
+ kind: useCaseKind,
523
+ phase: "start",
524
+ ctx: args.ctx,
525
+ });
526
+
527
+ try {
528
+ const parsedInput = validation.input
529
+ ? await parseSchema(inputSchema, args.input, useCaseName, "input")
530
+ : (args.input as SchemaOutput<InputSchema>);
531
+
532
+ const rawResult = await fn({
533
+ ctx: args.ctx,
534
+ input: parsedInput,
535
+ events: eventHelpers,
536
+ });
537
+
538
+ const result = validation.output
539
+ ? await parseSchema(outputSchema, rawResult, useCaseName, "output")
540
+ : (rawResult as SchemaOutput<OutputSchema>);
541
+
542
+ onRun?.({
543
+ name: useCaseName,
544
+ kind: useCaseKind,
545
+ phase: "end",
546
+ durationMs: Date.now() - startedAt,
547
+ ctx: args.ctx,
548
+ });
549
+
550
+ return result;
551
+ } catch (err) {
552
+ onRun?.({
553
+ name: useCaseName,
554
+ kind: useCaseKind,
555
+ phase: "error",
556
+ durationMs: Date.now() - startedAt,
557
+ error: err,
558
+ ctx: args.ctx,
559
+ });
560
+ throw err;
561
+ }
562
+ };
563
+
564
+ // Type assertion required to satisfy the conditional return type.
565
+ // The runtime checks above ensure input/output schemas are set.
566
+ // The conditional types ensure type safety at compile time - run() returns
567
+ // UseCaseDef only when both InputSchema and OutputSchema are StandardSchemaV1.
568
+ return {
569
+ name: this.config.name,
570
+ kind: this.config.kind,
571
+ inputSchema: this.config.input,
572
+ outputSchema: this.config.output,
573
+ emits: this.config.emits,
574
+ run: runFn,
575
+ } as unknown as InputSchema extends StandardSchemaV1
576
+ ? OutputSchema extends StandardSchemaV1
577
+ ? UseCaseDef<Ctx, Name, Kind, InputSchema, OutputSchema, Emits>
578
+ : never
579
+ : never;
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Root builder returned by createUseCase.
585
+ */
586
+ export interface UseCaseBuilderRoot<Ctx> {
587
+ /**
588
+ * Create a command use case (write/side-effect path)
589
+ */
590
+ command<Name extends string>(
591
+ name: Name,
592
+ ): UseCaseBuilder<Ctx, Name, "command", undefined, undefined, readonly []>;
593
+
594
+ /**
595
+ * Create a query use case (read-only path)
596
+ */
597
+ query<Name extends string>(
598
+ name: Name,
599
+ ): UseCaseBuilder<Ctx, Name, "query", undefined, undefined, readonly []>;
600
+ }
601
+
602
+ export type UseCaseContext<TUseCase> = TUseCase extends {
603
+ run: (args: {
604
+ ctx: infer Ctx;
605
+ input: infer _Input;
606
+ }) => Promise<infer _Output>;
607
+ }
608
+ ? Ctx
609
+ : never;
610
+
611
+ export type UseCaseInput<TUseCase> = TUseCase extends {
612
+ run: (args: {
613
+ ctx: infer _Ctx;
614
+ input: infer Input;
615
+ }) => Promise<infer _Output>;
616
+ }
617
+ ? Input
618
+ : never;
619
+
620
+ export type UseCaseOutput<TUseCase> = TUseCase extends {
621
+ run: (args: {
622
+ ctx: infer _Ctx;
623
+ input: infer _Input;
624
+ }) => Promise<infer Output>;
625
+ }
626
+ ? Output
627
+ : never;
628
+
629
+ type MaybePromise<T> = T | Promise<T>;
630
+
631
+ export interface UseCaseTester<Ctx> {
632
+ /**
633
+ * Create a fresh test context.
634
+ */
635
+ ctx(): Promise<Ctx>;
636
+
637
+ /**
638
+ * Run a use case with a typed input and either a fresh or explicit context.
639
+ */
640
+ run<Input, Output>(
641
+ useCase: {
642
+ run(args: { ctx: Ctx; input: Input }): Promise<Output>;
643
+ },
644
+ input: Input,
645
+ options?: { ctx?: Ctx },
646
+ ): Promise<Output>;
647
+ }
648
+
649
+ /**
650
+ * Create a small test harness for use cases.
651
+ *
652
+ * Pass a context factory when tests mutate ports or state. Pass a fixed context
653
+ * for simple, immutable tests.
654
+ */
655
+ export function createUseCaseTester<Ctx>(
656
+ createContext: Ctx | (() => MaybePromise<Ctx>),
657
+ ): UseCaseTester<Ctx> {
658
+ const ctx = async () =>
659
+ typeof createContext === "function"
660
+ ? await (createContext as () => MaybePromise<Ctx>)()
661
+ : createContext;
662
+
663
+ return {
664
+ ctx,
665
+ async run<Input, Output>(
666
+ useCase: {
667
+ run(args: { ctx: Ctx; input: Input }): Promise<Output>;
668
+ },
669
+ input: Input,
670
+ options?: { ctx?: Ctx },
671
+ ): Promise<Output> {
672
+ return useCase.run({
673
+ ctx: options?.ctx ?? (await ctx()),
674
+ input,
675
+ });
676
+ },
677
+ };
678
+ }
679
+
680
+ /**
681
+ * Create a use case builder with a specific context type.
682
+ *
683
+ * @example
684
+ * ```ts
685
+ * import { createUseCase } from "@beignet/core/application";
686
+ * import type { AppCtx } from "./ctx";
687
+ *
688
+ * export const useCase = createUseCase<AppCtx>();
689
+ *
690
+ * export const createTodo = useCase
691
+ * .command("todos.create")
692
+ * .input(CreateTodoInput)
693
+ * .output(CreateTodoOutput)
694
+ * .run(async ({ ctx, input }) => {
695
+ * const todo = await ctx.ports.db.todos.create(input);
696
+ * return { id: todo.id, title: todo.title };
697
+ * });
698
+ * ```
699
+ */
700
+
701
+ /** Empty emits array used as default */
702
+ const EMPTY_EMITS = [] as const;
703
+
704
+ export function createUseCase<Ctx>(
705
+ options?: CreateUseCaseOptions<Ctx>,
706
+ ): UseCaseBuilderRoot<Ctx> {
707
+ const onRun = options?.onRun;
708
+ const validation = normalizeValidationOptions(options?.validate);
709
+ return {
710
+ command<Name extends string>(name: Name) {
711
+ return new UseCaseBuilder<
712
+ Ctx,
713
+ Name,
714
+ "command",
715
+ undefined,
716
+ undefined,
717
+ readonly []
718
+ >(
719
+ {
720
+ name,
721
+ kind: "command",
722
+ emits: EMPTY_EMITS,
723
+ },
724
+ onRun,
725
+ validation,
726
+ );
727
+ },
728
+ query<Name extends string>(name: Name) {
729
+ return new UseCaseBuilder<
730
+ Ctx,
731
+ Name,
732
+ "query",
733
+ undefined,
734
+ undefined,
735
+ readonly []
736
+ >(
737
+ {
738
+ name,
739
+ kind: "query",
740
+ emits: EMPTY_EMITS,
741
+ },
742
+ onRun,
743
+ validation,
744
+ );
745
+ },
746
+ };
747
+ }