@beignet/core 0.0.1 → 0.0.2

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 (274) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +149 -4
  3. package/dist/application/index.d.ts +93 -9
  4. package/dist/application/index.d.ts.map +1 -1
  5. package/dist/application/index.js +11 -11
  6. package/dist/application/index.js.map +1 -1
  7. package/dist/client/client.d.ts +73 -12
  8. package/dist/client/client.d.ts.map +1 -1
  9. package/dist/client/client.js +37 -12
  10. package/dist/client/client.js.map +1 -1
  11. package/dist/client/index.d.ts +12 -0
  12. package/dist/client/index.d.ts.map +1 -1
  13. package/dist/client/index.js +6 -0
  14. package/dist/client/index.js.map +1 -1
  15. package/dist/client/types.d.ts +69 -8
  16. package/dist/client/types.d.ts.map +1 -1
  17. package/dist/config/index.d.ts +84 -0
  18. package/dist/config/index.d.ts.map +1 -1
  19. package/dist/config/index.js +36 -0
  20. package/dist/config/index.js.map +1 -1
  21. package/dist/contracts/contract-builder.d.ts +49 -22
  22. package/dist/contracts/contract-builder.d.ts.map +1 -1
  23. package/dist/contracts/contract-builder.js +48 -21
  24. package/dist/contracts/contract-builder.js.map +1 -1
  25. package/dist/contracts/contract-group.d.ts +35 -19
  26. package/dist/contracts/contract-group.d.ts.map +1 -1
  27. package/dist/contracts/contract-group.js +35 -19
  28. package/dist/contracts/contract-group.js.map +1 -1
  29. package/dist/contracts/contract-like.d.ts +4 -4
  30. package/dist/contracts/contract-like.d.ts.map +1 -1
  31. package/dist/contracts/contract-like.js +2 -1
  32. package/dist/contracts/contract-like.js.map +1 -1
  33. package/dist/contracts/index.d.ts +28 -0
  34. package/dist/contracts/index.d.ts.map +1 -1
  35. package/dist/contracts/index.js +12 -0
  36. package/dist/contracts/index.js.map +1 -1
  37. package/dist/contracts/openapi-meta.d.ts +8 -8
  38. package/dist/contracts/openapi-meta.d.ts.map +1 -1
  39. package/dist/contracts/path-template.d.ts +27 -0
  40. package/dist/contracts/path-template.d.ts.map +1 -1
  41. package/dist/contracts/path-template.js +6 -0
  42. package/dist/contracts/path-template.js.map +1 -1
  43. package/dist/contracts/types.d.ts +104 -10
  44. package/dist/contracts/types.d.ts.map +1 -1
  45. package/dist/contracts/types.js +15 -0
  46. package/dist/contracts/types.js.map +1 -1
  47. package/dist/contracts/utils.d.ts +6 -0
  48. package/dist/contracts/utils.d.ts.map +1 -1
  49. package/dist/contracts/utils.js +6 -0
  50. package/dist/contracts/utils.js.map +1 -1
  51. package/dist/domain/entity.d.ts +22 -11
  52. package/dist/domain/entity.d.ts.map +1 -1
  53. package/dist/domain/entity.js +5 -1
  54. package/dist/domain/entity.js.map +1 -1
  55. package/dist/domain/events.d.ts +5 -2
  56. package/dist/domain/events.d.ts.map +1 -1
  57. package/dist/domain/events.js +4 -1
  58. package/dist/domain/events.js.map +1 -1
  59. package/dist/domain/value-object.d.ts +19 -9
  60. package/dist/domain/value-object.d.ts.map +1 -1
  61. package/dist/domain/value-object.js +5 -1
  62. package/dist/domain/value-object.js.map +1 -1
  63. package/dist/errors/catalog.d.ts +40 -16
  64. package/dist/errors/catalog.d.ts.map +1 -1
  65. package/dist/errors/catalog.js +18 -7
  66. package/dist/errors/catalog.js.map +1 -1
  67. package/dist/errors/response.d.ts +16 -4
  68. package/dist/errors/response.d.ts.map +1 -1
  69. package/dist/errors/response.js +3 -3
  70. package/dist/errors/response.js.map +1 -1
  71. package/dist/errors/validation.d.ts +10 -1
  72. package/dist/errors/validation.d.ts.map +1 -1
  73. package/dist/errors/validation.js +3 -0
  74. package/dist/errors/validation.js.map +1 -1
  75. package/dist/events/index.d.ts +133 -0
  76. package/dist/events/index.d.ts.map +1 -1
  77. package/dist/events/index.js +30 -0
  78. package/dist/events/index.js.map +1 -1
  79. package/dist/idempotency/index.d.ts +355 -0
  80. package/dist/idempotency/index.d.ts.map +1 -0
  81. package/dist/idempotency/index.js +360 -0
  82. package/dist/idempotency/index.js.map +1 -0
  83. package/dist/jobs/index.d.ts +110 -0
  84. package/dist/jobs/index.d.ts.map +1 -1
  85. package/dist/jobs/index.js +22 -0
  86. package/dist/jobs/index.js.map +1 -1
  87. package/dist/mail/index.d.ts +149 -0
  88. package/dist/mail/index.d.ts.map +1 -1
  89. package/dist/mail/index.js +30 -0
  90. package/dist/mail/index.js.map +1 -1
  91. package/dist/notifications/index.d.ts +369 -0
  92. package/dist/notifications/index.d.ts.map +1 -0
  93. package/dist/notifications/index.js +310 -0
  94. package/dist/notifications/index.js.map +1 -0
  95. package/dist/openapi/index.d.ts +132 -16
  96. package/dist/openapi/index.d.ts.map +1 -1
  97. package/dist/openapi/index.js +1 -1
  98. package/dist/openapi/index.js.map +1 -1
  99. package/dist/outbox/index.d.ts +469 -0
  100. package/dist/outbox/index.d.ts.map +1 -0
  101. package/dist/outbox/index.js +482 -0
  102. package/dist/outbox/index.js.map +1 -0
  103. package/dist/pagination/index.d.ts +166 -0
  104. package/dist/pagination/index.d.ts.map +1 -0
  105. package/dist/pagination/index.js +96 -0
  106. package/dist/pagination/index.js.map +1 -0
  107. package/dist/ports/audit.d.ts +271 -0
  108. package/dist/ports/audit.d.ts.map +1 -1
  109. package/dist/ports/audit.js +128 -0
  110. package/dist/ports/audit.js.map +1 -1
  111. package/dist/ports/auth.d.ts +70 -0
  112. package/dist/ports/auth.d.ts.map +1 -1
  113. package/dist/ports/auth.js +30 -0
  114. package/dist/ports/auth.js.map +1 -1
  115. package/dist/ports/cache.d.ts +41 -0
  116. package/dist/ports/cache.d.ts.map +1 -1
  117. package/dist/ports/cache.js +10 -0
  118. package/dist/ports/cache.js.map +1 -1
  119. package/dist/ports/clock.d.ts +38 -0
  120. package/dist/ports/clock.d.ts.map +1 -1
  121. package/dist/ports/clock.js +20 -0
  122. package/dist/ports/clock.js.map +1 -1
  123. package/dist/ports/id-generator.d.ts +37 -0
  124. package/dist/ports/id-generator.d.ts.map +1 -1
  125. package/dist/ports/id-generator.js +22 -0
  126. package/dist/ports/id-generator.js.map +1 -1
  127. package/dist/ports/index.d.ts +83 -0
  128. package/dist/ports/index.d.ts.map +1 -1
  129. package/dist/ports/index.js +41 -5
  130. package/dist/ports/index.js.map +1 -1
  131. package/dist/ports/logger.d.ts +56 -0
  132. package/dist/ports/logger.d.ts.map +1 -1
  133. package/dist/ports/logger.js +17 -0
  134. package/dist/ports/logger.js.map +1 -1
  135. package/dist/ports/policy.d.ts +132 -0
  136. package/dist/ports/policy.d.ts.map +1 -1
  137. package/dist/ports/policy.js +45 -0
  138. package/dist/ports/policy.js.map +1 -1
  139. package/dist/ports/rate-limit.d.ts +25 -0
  140. package/dist/ports/rate-limit.d.ts.map +1 -1
  141. package/dist/ports/rate-limit.js +10 -0
  142. package/dist/ports/rate-limit.js.map +1 -1
  143. package/dist/ports/redaction.d.ts +101 -0
  144. package/dist/ports/redaction.d.ts.map +1 -1
  145. package/dist/ports/redaction.js +59 -0
  146. package/dist/ports/redaction.js.map +1 -1
  147. package/dist/ports/storage.d.ts +100 -0
  148. package/dist/ports/storage.d.ts.map +1 -1
  149. package/dist/ports/storage.js +10 -0
  150. package/dist/ports/storage.js.map +1 -1
  151. package/dist/ports/testing.d.ts +47 -0
  152. package/dist/ports/testing.d.ts.map +1 -1
  153. package/dist/ports/testing.js +23 -0
  154. package/dist/ports/testing.js.map +1 -1
  155. package/dist/ports/unit-of-work.d.ts +60 -3
  156. package/dist/ports/unit-of-work.d.ts.map +1 -1
  157. package/dist/ports/unit-of-work.js +11 -2
  158. package/dist/ports/unit-of-work.js.map +1 -1
  159. package/dist/providers/instrumentation.d.ts +204 -0
  160. package/dist/providers/instrumentation.d.ts.map +1 -1
  161. package/dist/providers/instrumentation.js +14 -0
  162. package/dist/providers/instrumentation.js.map +1 -1
  163. package/dist/providers/provider.d.ts +14 -1
  164. package/dist/providers/provider.d.ts.map +1 -1
  165. package/dist/providers/provider.js.map +1 -1
  166. package/dist/schedules/index.d.ts +246 -0
  167. package/dist/schedules/index.d.ts.map +1 -1
  168. package/dist/schedules/index.js +27 -0
  169. package/dist/schedules/index.js.map +1 -1
  170. package/dist/server/health.d.ts +14 -5
  171. package/dist/server/health.d.ts.map +1 -1
  172. package/dist/server/health.js +5 -2
  173. package/dist/server/health.js.map +1 -1
  174. package/dist/server/hooks/auth.d.ts +57 -0
  175. package/dist/server/hooks/auth.d.ts.map +1 -1
  176. package/dist/server/hooks/auth.js.map +1 -1
  177. package/dist/server/hooks/cors.d.ts +27 -0
  178. package/dist/server/hooks/cors.d.ts.map +1 -1
  179. package/dist/server/hooks/cors.js +12 -0
  180. package/dist/server/hooks/cors.js.map +1 -1
  181. package/dist/server/hooks/errors.d.ts +15 -6
  182. package/dist/server/hooks/errors.d.ts.map +1 -1
  183. package/dist/server/hooks/errors.js.map +1 -1
  184. package/dist/server/hooks/index.d.ts +3 -0
  185. package/dist/server/hooks/index.d.ts.map +1 -1
  186. package/dist/server/hooks/index.js +3 -0
  187. package/dist/server/hooks/index.js.map +1 -1
  188. package/dist/server/hooks/logging.d.ts +36 -0
  189. package/dist/server/hooks/logging.d.ts.map +1 -1
  190. package/dist/server/hooks/logging.js +6 -0
  191. package/dist/server/hooks/logging.js.map +1 -1
  192. package/dist/server/hooks/rate-limit.d.ts +33 -0
  193. package/dist/server/hooks/rate-limit.d.ts.map +1 -1
  194. package/dist/server/hooks/rate-limit.js +11 -0
  195. package/dist/server/hooks/rate-limit.js.map +1 -1
  196. package/dist/server/http.d.ts +170 -0
  197. package/dist/server/http.d.ts.map +1 -1
  198. package/dist/server/index.d.ts +18 -0
  199. package/dist/server/index.d.ts.map +1 -1
  200. package/dist/server/index.js +6 -0
  201. package/dist/server/index.js.map +1 -1
  202. package/dist/server/openapi.d.ts +5 -3
  203. package/dist/server/openapi.d.ts.map +1 -1
  204. package/dist/server/openapi.js +4 -2
  205. package/dist/server/openapi.js.map +1 -1
  206. package/dist/server/providers/loadProviderConfig.d.ts +9 -0
  207. package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
  208. package/dist/server/providers/loadProviderConfig.js +9 -0
  209. package/dist/server/providers/loadProviderConfig.js.map +1 -1
  210. package/dist/server/server.d.ts +107 -8
  211. package/dist/server/server.d.ts.map +1 -1
  212. package/dist/server/server.js +27 -7
  213. package/dist/server/server.js.map +1 -1
  214. package/dist/testing/index.d.ts +167 -0
  215. package/dist/testing/index.d.ts.map +1 -0
  216. package/dist/testing/index.js +119 -0
  217. package/dist/testing/index.js.map +1 -0
  218. package/package.json +21 -1
  219. package/src/application/index.ts +85 -22
  220. package/src/client/client.ts +73 -12
  221. package/src/client/index.ts +12 -0
  222. package/src/client/types.ts +70 -9
  223. package/src/config/index.ts +86 -0
  224. package/src/contracts/contract-builder.ts +49 -22
  225. package/src/contracts/contract-group.ts +35 -19
  226. package/src/contracts/contract-like.ts +4 -4
  227. package/src/contracts/index.ts +28 -1
  228. package/src/contracts/openapi-meta.ts +8 -8
  229. package/src/contracts/path-template.ts +27 -0
  230. package/src/contracts/types.ts +111 -10
  231. package/src/contracts/utils.ts +6 -0
  232. package/src/domain/entity.ts +22 -11
  233. package/src/domain/events.ts +5 -2
  234. package/src/domain/value-object.ts +19 -9
  235. package/src/errors/catalog.ts +40 -16
  236. package/src/errors/response.ts +16 -4
  237. package/src/errors/validation.ts +10 -1
  238. package/src/events/index.ts +134 -0
  239. package/src/idempotency/index.ts +767 -0
  240. package/src/jobs/index.ts +111 -0
  241. package/src/mail/index.ts +149 -0
  242. package/src/notifications/index.ts +771 -0
  243. package/src/openapi/index.ts +133 -16
  244. package/src/outbox/index.ts +1024 -0
  245. package/src/pagination/index.ts +278 -0
  246. package/src/ports/audit.ts +271 -0
  247. package/src/ports/auth.ts +70 -0
  248. package/src/ports/cache.ts +41 -0
  249. package/src/ports/clock.ts +38 -0
  250. package/src/ports/id-generator.ts +37 -0
  251. package/src/ports/index.ts +106 -11
  252. package/src/ports/logger.ts +56 -0
  253. package/src/ports/policy.ts +133 -0
  254. package/src/ports/rate-limit.ts +25 -0
  255. package/src/ports/redaction.ts +101 -0
  256. package/src/ports/storage.ts +100 -0
  257. package/src/ports/testing.ts +47 -0
  258. package/src/ports/unit-of-work.ts +60 -3
  259. package/src/providers/instrumentation.ts +204 -0
  260. package/src/providers/provider.ts +14 -1
  261. package/src/schedules/index.ts +247 -0
  262. package/src/server/health.ts +14 -5
  263. package/src/server/hooks/auth.ts +58 -0
  264. package/src/server/hooks/cors.ts +27 -0
  265. package/src/server/hooks/errors.ts +15 -6
  266. package/src/server/hooks/index.ts +3 -0
  267. package/src/server/hooks/logging.ts +36 -0
  268. package/src/server/hooks/rate-limit.ts +33 -0
  269. package/src/server/http.ts +170 -1
  270. package/src/server/index.ts +18 -1
  271. package/src/server/openapi.ts +5 -3
  272. package/src/server/providers/loadProviderConfig.ts +9 -0
  273. package/src/server/server.ts +107 -9
  274. package/src/testing/index.ts +337 -0
@@ -42,22 +42,47 @@ import {
42
42
  } from "./providers";
43
43
 
44
44
  /**
45
- * Route definition
45
+ * Route registration for one contract.
46
+ *
47
+ * Route definitions connect a contract to the handler that implements it. Most
48
+ * apps keep these in `features/<feature>/routes.ts` and compose them with
49
+ * `defineRoutes(...)`.
46
50
  */
47
51
  export type RouteDef<Ctx, CLike extends ContractLike = ContractLike> = {
52
+ /**
53
+ * Contract builder or plain contract config for this route.
54
+ */
48
55
  contract: CLike;
56
+ /**
57
+ * Handler that implements the contract.
58
+ */
49
59
  handle: Handler<Ctx, ResolveContract<CLike>>;
50
60
  };
51
61
 
52
62
  const ROUTE_GROUP_KIND = "beignet.route-group";
53
63
 
64
+ /**
65
+ * Named collection of related route registrations.
66
+ *
67
+ * Route groups are an organization helper only. `defineRoutes(...)` flattens
68
+ * them before server registration.
69
+ */
54
70
  export type RouteGroup<
55
71
  Ctx,
56
72
  // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
57
73
  Routes extends readonly RouteDef<Ctx, any>[] = readonly RouteDef<Ctx, any>[],
58
74
  > = {
75
+ /**
76
+ * Internal marker used by `defineRoutes(...)`.
77
+ */
59
78
  kind: typeof ROUTE_GROUP_KIND;
79
+ /**
80
+ * Human-readable group name.
81
+ */
60
82
  name: string;
83
+ /**
84
+ * Route definitions in this group.
85
+ */
61
86
  routes: Routes;
62
87
  };
63
88
 
@@ -97,6 +122,9 @@ type ContractsFromRouteList<
97
122
  : never;
98
123
  };
99
124
 
125
+ /**
126
+ * Options for creating a Beignet server instance.
127
+ */
100
128
  export type CreateServerOptions<
101
129
  Ctx,
102
130
  Ports extends AnyPorts,
@@ -109,21 +137,53 @@ export type CreateServerOptions<
109
137
  AnyPorts
110
138
  >[] = readonly [],
111
139
  > = {
140
+ /**
141
+ * App-owned ports available to context creation, hooks, and handlers.
142
+ */
112
143
  ports: Ports;
144
+ /**
145
+ * Providers installed during server startup.
146
+ *
147
+ * Provider ports are merged into `ports` before request handling and are
148
+ * stopped in reverse setup order when `server.stop()` runs.
149
+ */
113
150
  providers?: Providers;
114
151
 
115
- // config sources
152
+ /**
153
+ * Runtime env used by providers. Defaults to `process.env`.
154
+ */
116
155
  providerEnv?: Record<string, string | undefined>;
156
+ /**
157
+ * Provider config overrides keyed by provider name.
158
+ */
117
159
  providerConfig?: Record<string, unknown>;
118
160
 
161
+ /**
162
+ * Create request context after a route is matched.
163
+ *
164
+ * The `ports` argument includes app ports plus ports provided during server
165
+ * startup.
166
+ */
119
167
  createContext: (args: {
120
168
  req: HttpRequestLike;
121
169
  ports: Ports & ProvidedPortsOfList<Providers>;
122
170
  contract?: HttpContractConfig;
123
171
  }) => Ctx | Promise<Ctx>;
172
+ /**
173
+ * Server hooks that wrap every registered route.
174
+ */
124
175
  hooks?: ServerHook<Ctx, Ports & ProvidedPortsOfList<Providers>>[];
176
+ /**
177
+ * Route list to register up front.
178
+ */
125
179
  routes?: Routes;
180
+ /**
181
+ * Global caught-error observer.
182
+ */
126
183
  onCaughtError?: ServerCaughtErrorHook<Ctx>;
184
+ /**
185
+ * Global mapper for unexpected errors not handled by app error catalogs.
186
+ */
127
187
  mapUnhandledError?: ServerUnhandledErrorMapper<Ctx>;
128
188
  };
129
189
 
@@ -133,13 +193,31 @@ interface RouteBuilder<Ctx, C extends HttpContractConfig> {
133
193
  ) => (req: HttpRequestLike) => Promise<HttpResponse>;
134
194
  }
135
195
 
196
+ /**
197
+ * Runtime server object returned by `createServer(...)`.
198
+ */
136
199
  export interface ServerInstance<Ctx, Ports extends AnyPorts = AnyPorts> {
200
+ /**
201
+ * Catch-all request handler for platform adapters.
202
+ */
137
203
  api: (req: HttpRequestLike) => Promise<HttpResponse>;
204
+ /**
205
+ * Register and build a single route handler imperatively.
206
+ */
138
207
  route: <CLike extends ContractLike>(
139
208
  contractLike: CLike,
140
209
  ) => RouteBuilder<Ctx, ResolveContract<CLike>>;
210
+ /**
211
+ * Contract configs registered through the `routes` option.
212
+ */
141
213
  contracts: readonly HttpContractConfig[];
214
+ /**
215
+ * Stop installed providers in reverse setup order.
216
+ */
142
217
  stop: () => Promise<void>;
218
+ /**
219
+ * Final app ports after provider setup.
220
+ */
143
221
  ports: Ports;
144
222
  }
145
223
 
@@ -1234,6 +1312,18 @@ function buildHandler<
1234
1312
  };
1235
1313
  }
1236
1314
 
1315
+ /**
1316
+ * Create a Beignet server instance.
1317
+ *
1318
+ * The server owns route registration, provider setup/startup, request
1319
+ * validation, hook execution, response validation, and framework error mapping.
1320
+ * Use adapter packages such as `@beignet/next` to expose `server.api` to a
1321
+ * specific runtime.
1322
+ *
1323
+ * @param options - Ports, providers, routes, hooks, context factory, and error
1324
+ * mapping hooks for the server.
1325
+ * @returns A started server instance with final ports and a catch-all handler.
1326
+ */
1237
1327
  export async function createServer<
1238
1328
  Ctx,
1239
1329
  Ports extends AnyPorts,
@@ -1447,13 +1537,17 @@ export async function createServer<
1447
1537
  }
1448
1538
 
1449
1539
  /**
1450
- * Helper function to define routes with proper type inference.
1451
- * Eliminates the need for type assertions when passing routes to createServer.
1540
+ * Define and flatten route registrations with strong type inference.
1541
+ *
1542
+ * Pass route definitions and route groups here before `createServer(...)`.
1543
+ * Group entries are flattened so downstream tooling receives one route list.
1452
1544
  *
1453
1545
  * @example
1546
+ * ```ts
1454
1547
  * const routes = defineRoutes<AppContext>([
1455
- * { contract: myContract, handle: async ({ query }) => { ... } }
1548
+ * { contract: listPosts, handle: async ({ ctx }) => ctx.posts.list() },
1456
1549
  * ]);
1550
+ * ```
1457
1551
  */
1458
1552
  export function defineRoutes<
1459
1553
  Ctx,
@@ -1473,8 +1567,10 @@ export function defineRoutes<
1473
1567
  }
1474
1568
 
1475
1569
  /**
1476
- * Extract contract configs from a route list. Use this to drive OpenAPI from
1477
- * the same route registration list that createServer receives.
1570
+ * Extract contract configs from a route list.
1571
+ *
1572
+ * Use this to drive clients, OpenAPI, and docs from the same route list passed
1573
+ * to `createServer(...)`.
1478
1574
  */
1479
1575
  export function contractsFromRoutes<
1480
1576
  // biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
@@ -1486,18 +1582,20 @@ export function contractsFromRoutes<
1486
1582
  }
1487
1583
 
1488
1584
  /**
1489
- * Helper function to group related route registrations.
1585
+ * Define a named group of related route registrations.
1490
1586
  *
1491
1587
  * Route groups are flattened by defineRoutes, so createServer still receives
1492
1588
  * a regular route list while app code can keep feature route wiring colocated.
1493
1589
  *
1494
1590
  * @example
1591
+ * ```ts
1495
1592
  * const todoRoutes = defineRouteGroup<AppContext>({
1496
1593
  * name: "todos",
1497
1594
  * routes: [
1498
- * { contract: listTodos, handle: async ({ ctx }) => { ... } }
1595
+ * { contract: listTodos, handle: async ({ ctx }) => ctx.todos.list() },
1499
1596
  * ]
1500
1597
  * });
1598
+ * ```
1501
1599
  */
1502
1600
  export function defineRouteGroup<
1503
1601
  Ctx,
@@ -0,0 +1,337 @@
1
+ import { type ClockPort, createSystemClock } from "../ports/clock";
2
+ import {
3
+ createUuidIdGenerator,
4
+ type IdGeneratorPort,
5
+ } from "../ports/id-generator";
6
+
7
+ /**
8
+ * Value that may be returned synchronously or asynchronously.
9
+ */
10
+ export type MaybePromise<T> = T | Promise<T>;
11
+
12
+ /**
13
+ * Arguments passed to a factory default builder.
14
+ */
15
+ export interface FactoryBuildArgs<Name extends string = string> {
16
+ /**
17
+ * Factory name.
18
+ */
19
+ readonly name: Name;
20
+ /**
21
+ * Current sequence value.
22
+ */
23
+ readonly sequence: number;
24
+ /**
25
+ * ID generator available to the factory.
26
+ */
27
+ readonly ids: IdGeneratorPort;
28
+ /**
29
+ * Clock available to the factory.
30
+ */
31
+ readonly clock: ClockPort;
32
+ }
33
+
34
+ /**
35
+ * Arguments passed to a factory persistence function.
36
+ */
37
+ export type FactoryPersistArgs<Name extends string = string> =
38
+ FactoryBuildArgs<Name>;
39
+
40
+ /**
41
+ * Overrides accepted by factory build and create methods.
42
+ */
43
+ export type FactoryOverrides<
44
+ Value extends object,
45
+ Name extends string = string,
46
+ > =
47
+ | Partial<Value>
48
+ | ((value: Value, args: FactoryBuildArgs<Name>) => Partial<Value>);
49
+
50
+ /**
51
+ * Options for declaring a test data factory.
52
+ */
53
+ export interface DefineFactoryOptions<
54
+ Name extends string,
55
+ Value extends object,
56
+ Ctx = unknown,
57
+ Created = Value,
58
+ > {
59
+ /**
60
+ * Build the default value for the current sequence.
61
+ */
62
+ defaults(args: FactoryBuildArgs<Name>): Value;
63
+ /**
64
+ * Persist a built value and return the created record.
65
+ */
66
+ persist?(
67
+ ctx: Ctx,
68
+ value: Value,
69
+ args: FactoryPersistArgs<Name>,
70
+ ): MaybePromise<Created>;
71
+ /**
72
+ * ID generator to expose to the factory.
73
+ */
74
+ ids?: IdGeneratorPort;
75
+ /**
76
+ * Clock to expose to the factory.
77
+ */
78
+ clock?: ClockPort;
79
+ /**
80
+ * Initial sequence number. Defaults to `1`.
81
+ */
82
+ start?: number;
83
+ }
84
+
85
+ /**
86
+ * Declared test data factory.
87
+ */
88
+ export interface FactoryDef<
89
+ Name extends string = string,
90
+ Value extends object = object,
91
+ Ctx = unknown,
92
+ Created = Value,
93
+ > {
94
+ /**
95
+ * Discriminator for factory definitions.
96
+ */
97
+ readonly kind: "factory";
98
+ /**
99
+ * Factory name.
100
+ */
101
+ readonly name: Name;
102
+ /**
103
+ * Build an in-memory value.
104
+ */
105
+ build(overrides?: FactoryOverrides<Value, Name>): Value;
106
+ /**
107
+ * Build multiple in-memory values.
108
+ */
109
+ buildList(count: number, overrides?: FactoryOverrides<Value, Name>): Value[];
110
+ /**
111
+ * Build and persist a value.
112
+ */
113
+ create(ctx: Ctx, overrides?: FactoryOverrides<Value, Name>): Promise<Created>;
114
+ /**
115
+ * Build and persist multiple values.
116
+ */
117
+ createList(
118
+ ctx: Ctx,
119
+ count: number,
120
+ overrides?: FactoryOverrides<Value, Name>,
121
+ ): Promise<Created[]>;
122
+ /**
123
+ * Reset the factory sequence.
124
+ */
125
+ resetSequence(next?: number): void;
126
+ }
127
+
128
+ /**
129
+ * Options for declaring a seed.
130
+ */
131
+ export interface DefineSeedOptions<Ctx> {
132
+ /**
133
+ * Optional human-readable seed description.
134
+ */
135
+ description?: string;
136
+ /**
137
+ * Execute the seed.
138
+ */
139
+ run(ctx: Ctx): MaybePromise<void>;
140
+ }
141
+
142
+ /**
143
+ * Declared seed that can be run with `runSeeds(...)`.
144
+ */
145
+ export interface SeedDef<Ctx = unknown, Name extends string = string> {
146
+ /**
147
+ * Discriminator for seed definitions.
148
+ */
149
+ readonly kind: "seed";
150
+ /**
151
+ * Seed name.
152
+ */
153
+ readonly name: Name;
154
+ /**
155
+ * Optional human-readable seed description.
156
+ */
157
+ readonly description?: string;
158
+ /**
159
+ * Execute the seed.
160
+ */
161
+ run(ctx: Ctx): MaybePromise<void>;
162
+ }
163
+
164
+ /**
165
+ * Options for running a list of seeds.
166
+ */
167
+ export interface RunSeedsOptions<Ctx> {
168
+ /**
169
+ * Context passed to every seed.
170
+ */
171
+ ctx: Ctx;
172
+ /**
173
+ * Seeds to run in order.
174
+ */
175
+ seeds: readonly SeedDef<Ctx>[];
176
+ }
177
+
178
+ /**
179
+ * Error thrown when a seed fails.
180
+ */
181
+ export class SeedRunError extends Error {
182
+ /**
183
+ * Seed that failed.
184
+ */
185
+ readonly seed: SeedDef;
186
+ /**
187
+ * Original thrown value.
188
+ */
189
+ readonly cause: unknown;
190
+
191
+ constructor(seed: SeedDef, cause: unknown) {
192
+ super(`Seed "${seed.name}" failed: ${errorMessage(cause)}`);
193
+ this.name = "SeedRunError";
194
+ this.seed = seed;
195
+ this.cause = cause;
196
+ }
197
+ }
198
+
199
+ function errorMessage(error: unknown): string {
200
+ return error instanceof Error ? error.message : String(error);
201
+ }
202
+
203
+ function factoryArgs<Name extends string>(args: {
204
+ name: Name;
205
+ sequence: number;
206
+ ids: IdGeneratorPort;
207
+ clock: ClockPort;
208
+ }): FactoryBuildArgs<Name> {
209
+ return args;
210
+ }
211
+
212
+ function applyOverrides<Value extends object, Name extends string>(
213
+ value: Value,
214
+ args: FactoryBuildArgs<Name>,
215
+ overrides: FactoryOverrides<Value, Name> | undefined,
216
+ ): Value {
217
+ if (!overrides) return value;
218
+
219
+ const resolved =
220
+ typeof overrides === "function" ? overrides(value, args) : overrides;
221
+
222
+ return {
223
+ ...value,
224
+ ...resolved,
225
+ };
226
+ }
227
+
228
+ function assertCount(kind: "buildList" | "createList", count: number): void {
229
+ if (!Number.isInteger(count) || count < 0) {
230
+ throw new Error(`Factory ${kind} count must be a non-negative integer.`);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Define a typed test data factory with deterministic sequence support.
236
+ */
237
+ export function defineFactory<
238
+ const Name extends string,
239
+ Value extends object,
240
+ Ctx = unknown,
241
+ Created = Value,
242
+ >(
243
+ name: Name,
244
+ options: DefineFactoryOptions<Name, Value, Ctx, Created>,
245
+ ): FactoryDef<Name, Value, Ctx, Created> {
246
+ const start = options.start ?? 1;
247
+ const ids = options.ids ?? createUuidIdGenerator();
248
+ const clock = options.clock ?? createSystemClock();
249
+ let nextSequence = start;
250
+
251
+ function build(overrides?: FactoryOverrides<Value, Name>): Value {
252
+ const args = factoryArgs({
253
+ name,
254
+ sequence: nextSequence++,
255
+ ids,
256
+ clock,
257
+ });
258
+ const value = options.defaults(args);
259
+
260
+ return applyOverrides(value, args, overrides);
261
+ }
262
+
263
+ async function create(
264
+ ctx: Ctx,
265
+ overrides?: FactoryOverrides<Value, Name>,
266
+ ): Promise<Created> {
267
+ if (!options.persist) {
268
+ throw new Error(
269
+ `Factory "${name}" cannot create persisted records without a persist function.`,
270
+ );
271
+ }
272
+
273
+ const value = build(overrides);
274
+ const args = factoryArgs({
275
+ name,
276
+ sequence: nextSequence - 1,
277
+ ids,
278
+ clock,
279
+ });
280
+
281
+ return options.persist(ctx, value, args);
282
+ }
283
+
284
+ return {
285
+ kind: "factory",
286
+ name,
287
+ build,
288
+ buildList(count, overrides) {
289
+ assertCount("buildList", count);
290
+ return Array.from({ length: count }, () => build(overrides));
291
+ },
292
+ create,
293
+ async createList(ctx, count, overrides) {
294
+ assertCount("createList", count);
295
+ const created: Created[] = [];
296
+
297
+ for (let index = 0; index < count; index += 1) {
298
+ created.push(await create(ctx, overrides));
299
+ }
300
+
301
+ return created;
302
+ },
303
+ resetSequence(next = start) {
304
+ nextSequence = next;
305
+ },
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Define a named seed.
311
+ */
312
+ export function defineSeed<const Name extends string, Ctx = unknown>(
313
+ name: Name,
314
+ options: DefineSeedOptions<Ctx>,
315
+ ): SeedDef<Ctx, Name> {
316
+ return {
317
+ kind: "seed",
318
+ name,
319
+ description: options.description,
320
+ run: options.run,
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Run seeds in order and wrap failures with the seed name.
326
+ */
327
+ export async function runSeeds<Ctx>(
328
+ options: RunSeedsOptions<Ctx>,
329
+ ): Promise<void> {
330
+ for (const seed of options.seeds) {
331
+ try {
332
+ await seed.run(options.ctx);
333
+ } catch (error) {
334
+ throw new SeedRunError(seed, error);
335
+ }
336
+ }
337
+ }