@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,246 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ export type StandardSchema = StandardSchemaV1<unknown, unknown>;
4
+ export type MaybePromise<T> = T | Promise<T>;
5
+
6
+ export type InferSchemaOutput<T extends StandardSchemaV1> =
7
+ StandardSchemaV1.InferOutput<T>;
8
+
9
+ export interface EventPayloadDef<
10
+ Name extends string = string,
11
+ Payload extends StandardSchema = StandardSchema,
12
+ > {
13
+ readonly name: Name;
14
+ readonly payload: Payload;
15
+ readonly description?: string;
16
+ }
17
+
18
+ export interface EventDef<
19
+ Name extends string = string,
20
+ Payload extends StandardSchema = StandardSchema,
21
+ > extends EventPayloadDef<Name, Payload> {
22
+ readonly kind: "event";
23
+ }
24
+
25
+ export type InferEventPayload<E extends EventPayloadDef> =
26
+ E["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;
27
+
28
+ export interface DefineEventOptions<Payload extends StandardSchema> {
29
+ payload: Payload;
30
+ description?: string;
31
+ }
32
+
33
+ export interface ListenerHandleArgs<E extends EventDef, Ctx> {
34
+ event: E;
35
+ payload: InferEventPayload<E>;
36
+ ctx: Ctx;
37
+ }
38
+
39
+ export interface ListenerDef<
40
+ E extends EventDef = EventDef,
41
+ Ctx = unknown,
42
+ Name extends string = string,
43
+ > {
44
+ readonly kind: "listener";
45
+ readonly name: Name;
46
+ readonly event: E;
47
+ handle(args: ListenerHandleArgs<E, Ctx>): MaybePromise<void>;
48
+ }
49
+
50
+ export interface DefineListenerOptions<
51
+ E extends EventDef,
52
+ Ctx,
53
+ Name extends string,
54
+ > {
55
+ name: Name;
56
+ handle(args: ListenerHandleArgs<E, Ctx>): MaybePromise<void>;
57
+ }
58
+
59
+ export interface EventBusLike {
60
+ publish<E extends EventPayloadDef>(
61
+ event: E,
62
+ payload: InferEventPayload<E>,
63
+ ): MaybePromise<void>;
64
+ subscribe<E extends EventPayloadDef>(
65
+ event: E,
66
+ handler: (payload: InferEventPayload<E>) => MaybePromise<void>,
67
+ ): () => void;
68
+ }
69
+
70
+ export interface RegisterListenersOptions<Ctx> {
71
+ ctx?: Ctx | (() => MaybePromise<Ctx>);
72
+ onError?: (error: unknown, listener: ListenerDef<EventDef, Ctx>) => void;
73
+ }
74
+
75
+ export interface EventHandlers<Ctx> {
76
+ defineListener<E extends EventDef, Name extends string = string>(
77
+ event: E,
78
+ options: DefineListenerOptions<E, Ctx, Name>,
79
+ ): ListenerDef<E, Ctx, Name>;
80
+
81
+ registerListeners(
82
+ eventBus: EventBusLike,
83
+ listeners: readonly ListenerDef<EventDef, Ctx>[],
84
+ options?: RegisterListenersOptions<Ctx>,
85
+ ): () => void;
86
+ }
87
+
88
+ export class EventValidationError extends Error {
89
+ readonly issues: readonly StandardSchemaV1.Issue[];
90
+
91
+ constructor(args: {
92
+ name: string;
93
+ issues: readonly StandardSchemaV1.Issue[];
94
+ }) {
95
+ super(
96
+ `Event "${args.name}" payload validation failed: ${formatIssues(args.issues)}`,
97
+ );
98
+ this.name = "EventValidationError";
99
+ this.issues = args.issues;
100
+ }
101
+ }
102
+
103
+ function formatPath(path: StandardSchemaV1.Issue["path"]): string {
104
+ if (!path?.length) return "";
105
+
106
+ return path
107
+ .map((segment) =>
108
+ typeof segment === "object" && segment !== null && "key" in segment
109
+ ? String(segment.key)
110
+ : String(segment),
111
+ )
112
+ .join(".");
113
+ }
114
+
115
+ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
116
+ return issues
117
+ .map((issue) => {
118
+ const path = formatPath(issue.path);
119
+ return path ? `${path}: ${issue.message}` : issue.message;
120
+ })
121
+ .join("; ");
122
+ }
123
+
124
+ async function parsePayload<Schema extends StandardSchemaV1>(
125
+ schema: Schema,
126
+ input: unknown,
127
+ args: { name: string },
128
+ ): Promise<InferSchemaOutput<Schema>> {
129
+ const result = await schema["~standard"].validate(input);
130
+
131
+ if (result.issues?.length) {
132
+ throw new EventValidationError({
133
+ name: args.name,
134
+ issues: result.issues,
135
+ });
136
+ }
137
+
138
+ if ("value" in result) {
139
+ return result.value as InferSchemaOutput<Schema>;
140
+ }
141
+
142
+ throw new Error("Invalid Standard Schema result: missing value");
143
+ }
144
+
145
+ async function resolveCtx<Ctx>(
146
+ ctx: Ctx | (() => MaybePromise<Ctx>) | undefined,
147
+ ): Promise<Ctx> {
148
+ if (typeof ctx === "function") {
149
+ return (ctx as () => MaybePromise<Ctx>)();
150
+ }
151
+
152
+ return ctx as Ctx;
153
+ }
154
+
155
+ export function defineEvent<
156
+ Name extends string,
157
+ Payload extends StandardSchema,
158
+ >(name: Name, options: DefineEventOptions<Payload>): EventDef<Name, Payload> {
159
+ return {
160
+ kind: "event",
161
+ name,
162
+ payload: options.payload,
163
+ description: options.description,
164
+ };
165
+ }
166
+
167
+ export function defineListener<
168
+ E extends EventDef,
169
+ Ctx = unknown,
170
+ Name extends string = string,
171
+ >(
172
+ event: E,
173
+ options: DefineListenerOptions<E, Ctx, Name>,
174
+ ): ListenerDef<E, Ctx, Name> {
175
+ return {
176
+ kind: "listener",
177
+ name: options.name,
178
+ event,
179
+ handle: options.handle,
180
+ };
181
+ }
182
+
183
+ export async function parseEventPayload<E extends EventPayloadDef>(
184
+ event: E,
185
+ payload: unknown,
186
+ ): Promise<InferEventPayload<E>> {
187
+ return (await parsePayload(event.payload, payload, {
188
+ name: event.name,
189
+ })) as InferEventPayload<E>;
190
+ }
191
+
192
+ export async function publishEvent<E extends EventPayloadDef>(
193
+ eventBus: EventBusLike,
194
+ event: E,
195
+ payload: InferEventPayload<E>,
196
+ ): Promise<void> {
197
+ const parsed = await parseEventPayload(event, payload);
198
+ await eventBus.publish(event, parsed);
199
+ }
200
+
201
+ export function registerListeners<Ctx>(
202
+ eventBus: EventBusLike,
203
+ listeners: readonly ListenerDef<EventDef, Ctx>[],
204
+ options: RegisterListenersOptions<Ctx> = {},
205
+ ): () => void {
206
+ const unsubscribes = listeners.map((listener) =>
207
+ eventBus.subscribe(listener.event, async (rawPayload) => {
208
+ try {
209
+ const payload = await parseEventPayload(listener.event, rawPayload);
210
+ await listener.handle({
211
+ event: listener.event,
212
+ payload,
213
+ ctx: await resolveCtx(options.ctx),
214
+ });
215
+ } catch (error) {
216
+ options.onError?.(error, listener);
217
+ if (!options.onError) throw error;
218
+ }
219
+ }),
220
+ );
221
+
222
+ return () => {
223
+ for (const unsubscribe of [...unsubscribes].reverse()) {
224
+ unsubscribe();
225
+ }
226
+ };
227
+ }
228
+
229
+ export function createEventHandlers<Ctx>(): EventHandlers<Ctx> {
230
+ return {
231
+ defineListener<E extends EventDef, Name extends string = string>(
232
+ event: E,
233
+ options: DefineListenerOptions<E, Ctx, Name>,
234
+ ): ListenerDef<E, Ctx, Name> {
235
+ return defineListener(event, options);
236
+ },
237
+
238
+ registerListeners(
239
+ eventBus: EventBusLike,
240
+ listeners: readonly ListenerDef<EventDef, Ctx>[],
241
+ options: RegisterListenersOptions<Ctx> = {},
242
+ ): () => void {
243
+ return registerListeners(eventBus, listeners, options);
244
+ },
245
+ };
246
+ }
@@ -0,0 +1,211 @@
1
+ import type { StandardSchemaV1 } from "@standard-schema/spec";
2
+
3
+ export type StandardSchema = StandardSchemaV1<unknown, unknown>;
4
+ export type MaybePromise<T> = T | Promise<T>;
5
+
6
+ export type InferSchemaOutput<T extends StandardSchemaV1> =
7
+ StandardSchemaV1.InferOutput<T>;
8
+
9
+ export interface JobDef<
10
+ Name extends string = string,
11
+ Payload extends StandardSchema = StandardSchema,
12
+ Ctx = unknown,
13
+ > {
14
+ readonly kind: "job";
15
+ readonly name: Name;
16
+ readonly payload: Payload;
17
+ readonly description?: string;
18
+ readonly retry?: JobRetryOptions;
19
+ handle(
20
+ args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>,
21
+ ): MaybePromise<void>;
22
+ }
23
+
24
+ export type InferJobPayload<J extends JobDef> =
25
+ J["payload"] extends StandardSchemaV1<unknown, infer Output> ? Output : never;
26
+
27
+ export interface JobHandleArgs<J extends JobDef, Ctx> {
28
+ job: J;
29
+ payload: InferJobPayload<J>;
30
+ ctx: Ctx;
31
+ }
32
+
33
+ export interface JobRetryOptions {
34
+ /**
35
+ * Maximum number of retry attempts a durable job adapter should request.
36
+ *
37
+ * Providers may impose their own range limits. For example, the Inngest
38
+ * adapter validates this as a function-level `retries` value.
39
+ */
40
+ attempts?: number;
41
+ }
42
+
43
+ export interface DefineJobOptions<
44
+ Name extends string,
45
+ Payload extends StandardSchema,
46
+ Ctx,
47
+ > {
48
+ payload: Payload;
49
+ description?: string;
50
+ retry?: JobRetryOptions;
51
+ handle(
52
+ args: JobHandleArgs<JobDef<Name, Payload, Ctx>, Ctx>,
53
+ ): MaybePromise<void>;
54
+ }
55
+
56
+ export interface InlineJobDispatcherOptions<Ctx> {
57
+ ctx?: Ctx | (() => MaybePromise<Ctx>);
58
+ onError?: (error: unknown, job: JobDef<string, StandardSchema, Ctx>) => void;
59
+ }
60
+
61
+ export interface InlineJobDispatcher<Ctx = unknown> {
62
+ dispatch<J extends JobDef<string, StandardSchema, Ctx>>(
63
+ job: J,
64
+ payload: InferJobPayload<J>,
65
+ ): Promise<void>;
66
+ }
67
+
68
+ export interface JobHandlers<Ctx> {
69
+ defineJob<Name extends string, Payload extends StandardSchema>(
70
+ name: Name,
71
+ options: DefineJobOptions<Name, Payload, Ctx>,
72
+ ): JobDef<Name, Payload, Ctx>;
73
+
74
+ createInlineJobDispatcher(
75
+ options?: InlineJobDispatcherOptions<Ctx>,
76
+ ): InlineJobDispatcher<Ctx>;
77
+ }
78
+
79
+ export class JobValidationError extends Error {
80
+ readonly issues: readonly StandardSchemaV1.Issue[];
81
+
82
+ constructor(args: {
83
+ name: string;
84
+ issues: readonly StandardSchemaV1.Issue[];
85
+ }) {
86
+ super(
87
+ `Job "${args.name}" payload validation failed: ${formatIssues(args.issues)}`,
88
+ );
89
+ this.name = "JobValidationError";
90
+ this.issues = args.issues;
91
+ }
92
+ }
93
+
94
+ function formatPath(path: StandardSchemaV1.Issue["path"]): string {
95
+ if (!path?.length) return "";
96
+
97
+ return path
98
+ .map((segment) =>
99
+ typeof segment === "object" && segment !== null && "key" in segment
100
+ ? String(segment.key)
101
+ : String(segment),
102
+ )
103
+ .join(".");
104
+ }
105
+
106
+ function formatIssues(issues: readonly StandardSchemaV1.Issue[]): string {
107
+ return issues
108
+ .map((issue) => {
109
+ const path = formatPath(issue.path);
110
+ return path ? `${path}: ${issue.message}` : issue.message;
111
+ })
112
+ .join("; ");
113
+ }
114
+
115
+ async function parsePayload<Schema extends StandardSchemaV1>(
116
+ schema: Schema,
117
+ input: unknown,
118
+ args: { name: string },
119
+ ): Promise<InferSchemaOutput<Schema>> {
120
+ const result = await schema["~standard"].validate(input);
121
+
122
+ if (result.issues?.length) {
123
+ throw new JobValidationError({
124
+ name: args.name,
125
+ issues: result.issues,
126
+ });
127
+ }
128
+
129
+ if ("value" in result) {
130
+ return result.value as InferSchemaOutput<Schema>;
131
+ }
132
+
133
+ throw new Error("Invalid Standard Schema result: missing value");
134
+ }
135
+
136
+ async function resolveCtx<Ctx>(
137
+ ctx: Ctx | (() => MaybePromise<Ctx>) | undefined,
138
+ ): Promise<Ctx> {
139
+ if (typeof ctx === "function") {
140
+ return (ctx as () => MaybePromise<Ctx>)();
141
+ }
142
+
143
+ return ctx as Ctx;
144
+ }
145
+
146
+ export function defineJob<
147
+ Name extends string,
148
+ Payload extends StandardSchema,
149
+ Ctx = unknown,
150
+ >(
151
+ name: Name,
152
+ options: DefineJobOptions<Name, Payload, Ctx>,
153
+ ): JobDef<Name, Payload, Ctx> {
154
+ return {
155
+ kind: "job",
156
+ name,
157
+ payload: options.payload,
158
+ description: options.description,
159
+ retry: options.retry,
160
+ handle: options.handle as JobDef<Name, Payload, Ctx>["handle"],
161
+ };
162
+ }
163
+
164
+ export async function parseJobPayload<J extends JobDef>(
165
+ job: J,
166
+ payload: unknown,
167
+ ): Promise<InferJobPayload<J>> {
168
+ return (await parsePayload(job.payload, payload, {
169
+ name: job.name,
170
+ })) as InferJobPayload<J>;
171
+ }
172
+
173
+ export function createInlineJobDispatcher<Ctx>(
174
+ options: InlineJobDispatcherOptions<Ctx> = {},
175
+ ): InlineJobDispatcher<Ctx> {
176
+ return {
177
+ async dispatch<J extends JobDef<string, StandardSchema, Ctx>>(
178
+ job: J,
179
+ payload: InferJobPayload<J>,
180
+ ) {
181
+ try {
182
+ const parsed = await parseJobPayload(job, payload);
183
+ await job.handle({
184
+ job,
185
+ payload: parsed,
186
+ ctx: await resolveCtx(options.ctx),
187
+ });
188
+ } catch (error) {
189
+ options.onError?.(error, job);
190
+ if (!options.onError) throw error;
191
+ }
192
+ },
193
+ };
194
+ }
195
+
196
+ export function createJobHandlers<Ctx>(): JobHandlers<Ctx> {
197
+ return {
198
+ defineJob<Name extends string, Payload extends StandardSchema>(
199
+ name: Name,
200
+ options: DefineJobOptions<Name, Payload, Ctx>,
201
+ ): JobDef<Name, Payload, Ctx> {
202
+ return defineJob(name, options);
203
+ },
204
+
205
+ createInlineJobDispatcher(
206
+ options: InlineJobDispatcherOptions<Ctx> = {},
207
+ ): InlineJobDispatcher<Ctx> {
208
+ return createInlineJobDispatcher(options);
209
+ },
210
+ };
211
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * @beignet/core/mail
3
+ *
4
+ * Shared mail port and test adapters for Beignet applications.
5
+ */
6
+
7
+ export type MaybePromise<T> = T | Promise<T>;
8
+
9
+ export type MailAddress =
10
+ | string
11
+ | {
12
+ email: string;
13
+ name?: string;
14
+ };
15
+
16
+ export type MailAddressList = MailAddress | readonly MailAddress[];
17
+
18
+ export interface MailBaseMessage {
19
+ to: MailAddressList;
20
+ subject: string;
21
+ from?: MailAddress;
22
+ cc?: MailAddressList;
23
+ bcc?: MailAddressList;
24
+ replyTo?: MailAddressList;
25
+ headers?: Record<string, string>;
26
+ }
27
+
28
+ export type SendMailOptions = MailBaseMessage &
29
+ (
30
+ | {
31
+ text: string;
32
+ html?: string;
33
+ }
34
+ | {
35
+ html: string;
36
+ text?: string;
37
+ }
38
+ );
39
+
40
+ export interface NormalizedMailMessage extends MailBaseMessage {
41
+ to: readonly MailAddress[];
42
+ from?: MailAddress;
43
+ cc?: readonly MailAddress[];
44
+ bcc?: readonly MailAddress[];
45
+ replyTo?: readonly MailAddress[];
46
+ text?: string;
47
+ html?: string;
48
+ }
49
+
50
+ export interface SendMailResult {
51
+ id?: string;
52
+ provider?: string;
53
+ }
54
+
55
+ export interface MailerPort {
56
+ send(message: SendMailOptions): Promise<SendMailResult>;
57
+ }
58
+
59
+ export interface MemoryMailDelivery {
60
+ id: string;
61
+ message: NormalizedMailMessage;
62
+ sentAt: Date;
63
+ }
64
+
65
+ export interface MemoryMailerPort extends MailerPort {
66
+ readonly deliveries: readonly MemoryMailDelivery[];
67
+ clear(): void;
68
+ }
69
+
70
+ export interface CreateMemoryMailerOptions {
71
+ defaultFrom?: MailAddress;
72
+ now?: () => Date;
73
+ id?: () => string;
74
+ onSend?: (delivery: MemoryMailDelivery) => MaybePromise<void>;
75
+ }
76
+
77
+ export class MailDeliveryError extends Error {
78
+ readonly provider?: string;
79
+ readonly cause?: unknown;
80
+
81
+ constructor(args: { provider?: string; message: string; cause?: unknown }) {
82
+ super(args.message);
83
+ this.name = "MailDeliveryError";
84
+ this.provider = args.provider;
85
+ this.cause = args.cause;
86
+ }
87
+ }
88
+
89
+ export function normalizeMailAddressList(
90
+ addresses: MailAddressList | undefined,
91
+ ): readonly MailAddress[] | undefined {
92
+ if (addresses === undefined) return undefined;
93
+ return Array.isArray(addresses) ? [...addresses] : [addresses as MailAddress];
94
+ }
95
+
96
+ function normalizeOptionalMailAddressList(
97
+ addresses: MailAddressList | undefined,
98
+ ): readonly MailAddress[] | undefined {
99
+ const normalized = normalizeMailAddressList(addresses);
100
+ return normalized && normalized.length > 0 ? normalized : undefined;
101
+ }
102
+
103
+ export function formatMailAddress(address: MailAddress): string {
104
+ if (typeof address === "string") return address;
105
+ if (!address.name) return address.email;
106
+
107
+ const escapedName = address.name.replace(/"/g, '\\"');
108
+ return `"${escapedName}" <${address.email}>`;
109
+ }
110
+
111
+ export function formatMailAddressList(
112
+ addresses: MailAddressList,
113
+ ): string | string[] {
114
+ const normalized = normalizeMailAddressList(addresses) ?? [];
115
+ const formatted = normalized.map(formatMailAddress);
116
+ return formatted.length === 1 ? formatted[0] : formatted;
117
+ }
118
+
119
+ export function normalizeMailMessage(
120
+ message: SendMailOptions,
121
+ options: { defaultFrom?: MailAddress } = {},
122
+ ): NormalizedMailMessage {
123
+ const to = normalizeMailAddressList(message.to) ?? [];
124
+
125
+ if (to.length === 0) {
126
+ throw new MailDeliveryError({
127
+ message: "Cannot send email without at least one recipient.",
128
+ });
129
+ }
130
+
131
+ return {
132
+ ...message,
133
+ to,
134
+ from: message.from ?? options.defaultFrom,
135
+ cc: normalizeOptionalMailAddressList(message.cc),
136
+ bcc: normalizeOptionalMailAddressList(message.bcc),
137
+ replyTo: normalizeOptionalMailAddressList(message.replyTo),
138
+ text: "text" in message ? message.text : undefined,
139
+ html: "html" in message ? message.html : undefined,
140
+ };
141
+ }
142
+
143
+ export function createMemoryMailer(
144
+ options: CreateMemoryMailerOptions = {},
145
+ ): MemoryMailerPort {
146
+ const deliveries: MemoryMailDelivery[] = [];
147
+ const now = options.now ?? (() => new Date());
148
+ const id = options.id ?? (() => crypto.randomUUID());
149
+
150
+ return {
151
+ get deliveries() {
152
+ return deliveries;
153
+ },
154
+
155
+ async send(message) {
156
+ const delivery: MemoryMailDelivery = {
157
+ id: id(),
158
+ message: normalizeMailMessage(message, {
159
+ defaultFrom: options.defaultFrom,
160
+ }),
161
+ sentAt: now(),
162
+ };
163
+
164
+ deliveries.push(delivery);
165
+ await options.onSend?.(delivery);
166
+
167
+ return {
168
+ id: delivery.id,
169
+ provider: "memory",
170
+ };
171
+ },
172
+
173
+ clear() {
174
+ deliveries.length = 0;
175
+ },
176
+ };
177
+ }