@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.
- package/CHANGELOG.md +11 -0
- package/README.md +149 -4
- package/dist/application/index.d.ts +93 -9
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +11 -11
- package/dist/application/index.js.map +1 -1
- package/dist/client/client.d.ts +73 -12
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +37 -12
- package/dist/client/client.js.map +1 -1
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +6 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +69 -8
- package/dist/client/types.d.ts.map +1 -1
- package/dist/config/index.d.ts +84 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +36 -0
- package/dist/config/index.js.map +1 -1
- package/dist/contracts/contract-builder.d.ts +49 -22
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +48 -21
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/contracts/contract-group.d.ts +35 -19
- package/dist/contracts/contract-group.d.ts.map +1 -1
- package/dist/contracts/contract-group.js +35 -19
- package/dist/contracts/contract-group.js.map +1 -1
- package/dist/contracts/contract-like.d.ts +4 -4
- package/dist/contracts/contract-like.d.ts.map +1 -1
- package/dist/contracts/contract-like.js +2 -1
- package/dist/contracts/contract-like.js.map +1 -1
- package/dist/contracts/index.d.ts +28 -0
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +12 -0
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/openapi-meta.d.ts +8 -8
- package/dist/contracts/openapi-meta.d.ts.map +1 -1
- package/dist/contracts/path-template.d.ts +27 -0
- package/dist/contracts/path-template.d.ts.map +1 -1
- package/dist/contracts/path-template.js +6 -0
- package/dist/contracts/path-template.js.map +1 -1
- package/dist/contracts/types.d.ts +104 -10
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js +15 -0
- package/dist/contracts/types.js.map +1 -1
- package/dist/contracts/utils.d.ts +6 -0
- package/dist/contracts/utils.d.ts.map +1 -1
- package/dist/contracts/utils.js +6 -0
- package/dist/contracts/utils.js.map +1 -1
- package/dist/domain/entity.d.ts +22 -11
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +5 -1
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/events.d.ts +5 -2
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +4 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/value-object.d.ts +19 -9
- package/dist/domain/value-object.d.ts.map +1 -1
- package/dist/domain/value-object.js +5 -1
- package/dist/domain/value-object.js.map +1 -1
- package/dist/errors/catalog.d.ts +40 -16
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +18 -7
- package/dist/errors/catalog.js.map +1 -1
- package/dist/errors/response.d.ts +16 -4
- package/dist/errors/response.d.ts.map +1 -1
- package/dist/errors/response.js +3 -3
- package/dist/errors/response.js.map +1 -1
- package/dist/errors/validation.d.ts +10 -1
- package/dist/errors/validation.d.ts.map +1 -1
- package/dist/errors/validation.js +3 -0
- package/dist/errors/validation.js.map +1 -1
- package/dist/events/index.d.ts +133 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +30 -0
- package/dist/events/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +355 -0
- package/dist/idempotency/index.d.ts.map +1 -0
- package/dist/idempotency/index.js +360 -0
- package/dist/idempotency/index.js.map +1 -0
- package/dist/jobs/index.d.ts +110 -0
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +22 -0
- package/dist/jobs/index.js.map +1 -1
- package/dist/mail/index.d.ts +149 -0
- package/dist/mail/index.d.ts.map +1 -1
- package/dist/mail/index.js +30 -0
- package/dist/mail/index.js.map +1 -1
- package/dist/notifications/index.d.ts +369 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +310 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/openapi/index.d.ts +132 -16
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/index.js.map +1 -1
- package/dist/outbox/index.d.ts +469 -0
- package/dist/outbox/index.d.ts.map +1 -0
- package/dist/outbox/index.js +482 -0
- package/dist/outbox/index.js.map +1 -0
- package/dist/pagination/index.d.ts +166 -0
- package/dist/pagination/index.d.ts.map +1 -0
- package/dist/pagination/index.js +96 -0
- package/dist/pagination/index.js.map +1 -0
- package/dist/ports/audit.d.ts +271 -0
- package/dist/ports/audit.d.ts.map +1 -1
- package/dist/ports/audit.js +128 -0
- package/dist/ports/audit.js.map +1 -1
- package/dist/ports/auth.d.ts +70 -0
- package/dist/ports/auth.d.ts.map +1 -1
- package/dist/ports/auth.js +30 -0
- package/dist/ports/auth.js.map +1 -1
- package/dist/ports/cache.d.ts +41 -0
- package/dist/ports/cache.d.ts.map +1 -1
- package/dist/ports/cache.js +10 -0
- package/dist/ports/cache.js.map +1 -1
- package/dist/ports/clock.d.ts +38 -0
- package/dist/ports/clock.d.ts.map +1 -1
- package/dist/ports/clock.js +20 -0
- package/dist/ports/clock.js.map +1 -1
- package/dist/ports/id-generator.d.ts +37 -0
- package/dist/ports/id-generator.d.ts.map +1 -1
- package/dist/ports/id-generator.js +22 -0
- package/dist/ports/id-generator.js.map +1 -1
- package/dist/ports/index.d.ts +83 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +41 -5
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/logger.d.ts +56 -0
- package/dist/ports/logger.d.ts.map +1 -1
- package/dist/ports/logger.js +17 -0
- package/dist/ports/logger.js.map +1 -1
- package/dist/ports/policy.d.ts +132 -0
- package/dist/ports/policy.d.ts.map +1 -1
- package/dist/ports/policy.js +45 -0
- package/dist/ports/policy.js.map +1 -1
- package/dist/ports/rate-limit.d.ts +25 -0
- package/dist/ports/rate-limit.d.ts.map +1 -1
- package/dist/ports/rate-limit.js +10 -0
- package/dist/ports/rate-limit.js.map +1 -1
- package/dist/ports/redaction.d.ts +101 -0
- package/dist/ports/redaction.d.ts.map +1 -1
- package/dist/ports/redaction.js +59 -0
- package/dist/ports/redaction.js.map +1 -1
- package/dist/ports/storage.d.ts +100 -0
- package/dist/ports/storage.d.ts.map +1 -1
- package/dist/ports/storage.js +10 -0
- package/dist/ports/storage.js.map +1 -1
- package/dist/ports/testing.d.ts +47 -0
- package/dist/ports/testing.d.ts.map +1 -1
- package/dist/ports/testing.js +23 -0
- package/dist/ports/testing.js.map +1 -1
- package/dist/ports/unit-of-work.d.ts +60 -3
- package/dist/ports/unit-of-work.d.ts.map +1 -1
- package/dist/ports/unit-of-work.js +11 -2
- package/dist/ports/unit-of-work.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +204 -0
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js +14 -0
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/provider.d.ts +14 -1
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +246 -0
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +27 -0
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/health.d.ts +14 -5
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/health.js +5 -2
- package/dist/server/health.js.map +1 -1
- package/dist/server/hooks/auth.d.ts +57 -0
- package/dist/server/hooks/auth.d.ts.map +1 -1
- package/dist/server/hooks/auth.js.map +1 -1
- package/dist/server/hooks/cors.d.ts +27 -0
- package/dist/server/hooks/cors.d.ts.map +1 -1
- package/dist/server/hooks/cors.js +12 -0
- package/dist/server/hooks/cors.js.map +1 -1
- package/dist/server/hooks/errors.d.ts +15 -6
- package/dist/server/hooks/errors.d.ts.map +1 -1
- package/dist/server/hooks/errors.js.map +1 -1
- package/dist/server/hooks/index.d.ts +3 -0
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +3 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/logging.d.ts +36 -0
- package/dist/server/hooks/logging.d.ts.map +1 -1
- package/dist/server/hooks/logging.js +6 -0
- package/dist/server/hooks/logging.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +33 -0
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +11 -0
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/http.d.ts +170 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/index.d.ts +18 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +6 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/openapi.d.ts +5 -3
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/openapi.js +4 -2
- package/dist/server/openapi.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts +9 -0
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +9 -0
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/server.d.ts +107 -8
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +27 -7
- package/dist/server/server.js.map +1 -1
- package/dist/testing/index.d.ts +167 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +119 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +21 -1
- package/src/application/index.ts +85 -22
- package/src/client/client.ts +73 -12
- package/src/client/index.ts +12 -0
- package/src/client/types.ts +70 -9
- package/src/config/index.ts +86 -0
- package/src/contracts/contract-builder.ts +49 -22
- package/src/contracts/contract-group.ts +35 -19
- package/src/contracts/contract-like.ts +4 -4
- package/src/contracts/index.ts +28 -1
- package/src/contracts/openapi-meta.ts +8 -8
- package/src/contracts/path-template.ts +27 -0
- package/src/contracts/types.ts +111 -10
- package/src/contracts/utils.ts +6 -0
- package/src/domain/entity.ts +22 -11
- package/src/domain/events.ts +5 -2
- package/src/domain/value-object.ts +19 -9
- package/src/errors/catalog.ts +40 -16
- package/src/errors/response.ts +16 -4
- package/src/errors/validation.ts +10 -1
- package/src/events/index.ts +134 -0
- package/src/idempotency/index.ts +767 -0
- package/src/jobs/index.ts +111 -0
- package/src/mail/index.ts +149 -0
- package/src/notifications/index.ts +771 -0
- package/src/openapi/index.ts +133 -16
- package/src/outbox/index.ts +1024 -0
- package/src/pagination/index.ts +278 -0
- package/src/ports/audit.ts +271 -0
- package/src/ports/auth.ts +70 -0
- package/src/ports/cache.ts +41 -0
- package/src/ports/clock.ts +38 -0
- package/src/ports/id-generator.ts +37 -0
- package/src/ports/index.ts +106 -11
- package/src/ports/logger.ts +56 -0
- package/src/ports/policy.ts +133 -0
- package/src/ports/rate-limit.ts +25 -0
- package/src/ports/redaction.ts +101 -0
- package/src/ports/storage.ts +100 -0
- package/src/ports/testing.ts +47 -0
- package/src/ports/unit-of-work.ts +60 -3
- package/src/providers/instrumentation.ts +204 -0
- package/src/providers/provider.ts +14 -1
- package/src/schedules/index.ts +247 -0
- package/src/server/health.ts +14 -5
- package/src/server/hooks/auth.ts +58 -0
- package/src/server/hooks/cors.ts +27 -0
- package/src/server/hooks/errors.ts +15 -6
- package/src/server/hooks/index.ts +3 -0
- package/src/server/hooks/logging.ts +36 -0
- package/src/server/hooks/rate-limit.ts +33 -0
- package/src/server/http.ts +170 -1
- package/src/server/index.ts +18 -1
- package/src/server/openapi.ts +5 -3
- package/src/server/providers/loadProviderConfig.ts +9 -0
- package/src/server/server.ts +107 -9
- package/src/testing/index.ts +337 -0
package/src/server/server.ts
CHANGED
|
@@ -42,22 +42,47 @@ import {
|
|
|
42
42
|
} from "./providers";
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Route
|
|
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
|
-
|
|
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
|
-
*
|
|
1451
|
-
*
|
|
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:
|
|
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.
|
|
1477
|
-
*
|
|
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
|
-
*
|
|
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
|
+
}
|