@beignet/core 0.0.13 → 0.0.15
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 +28 -0
- package/README.md +198 -2
- package/dist/entitlements/index.d.ts +234 -0
- package/dist/entitlements/index.d.ts.map +1 -0
- package/dist/entitlements/index.js +172 -0
- package/dist/entitlements/index.js.map +1 -0
- package/dist/error-reporting/index.d.ts +131 -0
- package/dist/error-reporting/index.d.ts.map +1 -0
- package/dist/error-reporting/index.js +112 -0
- package/dist/error-reporting/index.js.map +1 -0
- package/dist/flags/index.d.ts +293 -0
- package/dist/flags/index.d.ts.map +1 -0
- package/dist/flags/index.js +204 -0
- package/dist/flags/index.js.map +1 -0
- package/dist/locks/index.d.ts +155 -0
- package/dist/locks/index.d.ts.map +1 -0
- package/dist/locks/index.js +248 -0
- package/dist/locks/index.js.map +1 -0
- package/dist/payments/index.d.ts +430 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +230 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/ports/index.d.ts +74 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +38 -0
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/policy.d.ts +104 -0
- package/dist/ports/policy.d.ts.map +1 -1
- package/dist/ports/policy.js +102 -7
- package/dist/ports/policy.js.map +1 -1
- package/dist/search/index.d.ts +175 -0
- package/dist/search/index.d.ts.map +1 -0
- package/dist/search/index.js +344 -0
- package/dist/search/index.js.map +1 -0
- package/dist/server/hooks/error-reporting.d.ts +79 -0
- package/dist/server/hooks/error-reporting.d.ts.map +1 -0
- package/dist/server/hooks/error-reporting.js +147 -0
- package/dist/server/hooks/error-reporting.js.map +1 -0
- package/dist/server/hooks/index.d.ts +1 -0
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +1 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/server.d.ts +5 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +9 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/use-case-route.d.ts +2 -2
- package/dist/testing/index.d.ts +46 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +33 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +195 -0
- package/dist/webhooks/index.d.ts.map +1 -0
- package/dist/webhooks/index.js +313 -0
- package/dist/webhooks/index.js.map +1 -0
- package/package.json +29 -1
- package/src/entitlements/index.ts +479 -0
- package/src/error-reporting/index.ts +273 -0
- package/src/flags/index.ts +608 -0
- package/src/locks/index.ts +440 -0
- package/src/payments/index.ts +699 -0
- package/src/ports/index.ts +231 -0
- package/src/ports/policy.ts +272 -7
- package/src/search/index.ts +632 -0
- package/src/server/hooks/error-reporting.ts +249 -0
- package/src/server/hooks/index.ts +8 -0
- package/src/server/server.ts +20 -0
- package/src/server/use-case-route.ts +2 -2
- package/src/testing/index.ts +83 -1
- package/src/webhooks/index.ts +555 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error reporting hook utilities for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { HttpContractConfig } from "../../contracts/index.js";
|
|
6
|
+
import type {
|
|
7
|
+
ErrorReporterPort,
|
|
8
|
+
ErrorReportOptions,
|
|
9
|
+
} from "../../error-reporting/index.js";
|
|
10
|
+
import { isAppError, SchemaValidationError } from "../../errors/index.js";
|
|
11
|
+
import {
|
|
12
|
+
IdempotencyConflictError,
|
|
13
|
+
IdempotencyInProgressError,
|
|
14
|
+
} from "../../idempotency/index.js";
|
|
15
|
+
import {
|
|
16
|
+
AuthUnauthorizedError,
|
|
17
|
+
EntitlementRequiredError,
|
|
18
|
+
GateAuthorizationError,
|
|
19
|
+
TenantRequiredError,
|
|
20
|
+
} from "../../ports/index.js";
|
|
21
|
+
import type {
|
|
22
|
+
HttpRequestLike,
|
|
23
|
+
ServerCaughtErrorHook,
|
|
24
|
+
ServerHook,
|
|
25
|
+
} from "../types.js";
|
|
26
|
+
|
|
27
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Minimal context shape used by `createErrorReportingHooks(...)`.
|
|
31
|
+
*/
|
|
32
|
+
export type ErrorReportingContext = {
|
|
33
|
+
ports?: {
|
|
34
|
+
errorReporter?: ErrorReporterPort;
|
|
35
|
+
};
|
|
36
|
+
requestId?: string;
|
|
37
|
+
traceId?: string;
|
|
38
|
+
spanId?: string;
|
|
39
|
+
parentSpanId?: string;
|
|
40
|
+
traceparent?: string;
|
|
41
|
+
actor?: {
|
|
42
|
+
type?: string;
|
|
43
|
+
id?: string;
|
|
44
|
+
};
|
|
45
|
+
tenant?: {
|
|
46
|
+
id?: string;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Arguments passed to error reporting hook callbacks.
|
|
52
|
+
*/
|
|
53
|
+
export type ErrorReportingHookArgs<Ctx> = Parameters<
|
|
54
|
+
ServerCaughtErrorHook<Ctx>
|
|
55
|
+
>[0];
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Error reporter resolver accepted by `createErrorReportingHooks(...)`.
|
|
59
|
+
*/
|
|
60
|
+
export type ErrorReporterResolver<Ctx> = (
|
|
61
|
+
args: ErrorReportingHookArgs<Ctx>,
|
|
62
|
+
) => MaybePromise<ErrorReporterPort | undefined>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options accepted by `createErrorReportingHooks(...)`.
|
|
66
|
+
*/
|
|
67
|
+
export interface ErrorReportingHooksOptions<Ctx> {
|
|
68
|
+
/**
|
|
69
|
+
* Reporter to use instead of `ctx.ports.errorReporter`.
|
|
70
|
+
*/
|
|
71
|
+
reporter?: ErrorReporterPort | ErrorReporterResolver<Ctx>;
|
|
72
|
+
/**
|
|
73
|
+
* Decide whether a caught error should be captured. The default reports
|
|
74
|
+
* unexpected failures and skips expected catalog/auth/validation outcomes.
|
|
75
|
+
*/
|
|
76
|
+
shouldReport?: (args: ErrorReportingHookArgs<Ctx>) => MaybePromise<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Additional capture options. These are merged over Beignet's request,
|
|
79
|
+
* actor, tenant, contract, and trace metadata.
|
|
80
|
+
*/
|
|
81
|
+
reportOptions?:
|
|
82
|
+
| ErrorReportOptions
|
|
83
|
+
| ((args: ErrorReportingHookArgs<Ctx>) => MaybePromise<ErrorReportOptions>);
|
|
84
|
+
/**
|
|
85
|
+
* Observer for reporter failures. Reporter failures are otherwise ignored so
|
|
86
|
+
* error reporting cannot change the HTTP response.
|
|
87
|
+
*/
|
|
88
|
+
onReporterError?: (args: {
|
|
89
|
+
error: unknown;
|
|
90
|
+
reportingError: unknown;
|
|
91
|
+
ctx?: Ctx;
|
|
92
|
+
req: HttpRequestLike;
|
|
93
|
+
contract: HttpContractConfig;
|
|
94
|
+
}) => MaybePromise<void>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create server hooks that capture unexpected HTTP request failures through
|
|
99
|
+
* `ctx.ports.errorReporter`.
|
|
100
|
+
*
|
|
101
|
+
* The hook observes `onCaughtError`, never maps responses, and ignores capture
|
|
102
|
+
* failures by default so reporting cannot affect request handling.
|
|
103
|
+
*/
|
|
104
|
+
export function createErrorReportingHooks<
|
|
105
|
+
Ctx extends ErrorReportingContext = ErrorReportingContext,
|
|
106
|
+
>(config: ErrorReportingHooksOptions<Ctx> = {}): ServerHook<Ctx> {
|
|
107
|
+
return {
|
|
108
|
+
name: "error-reporting",
|
|
109
|
+
async onCaughtError(args) {
|
|
110
|
+
const shouldReport = config.shouldReport
|
|
111
|
+
? await config.shouldReport(args)
|
|
112
|
+
: shouldReportServerError(args.err);
|
|
113
|
+
if (!shouldReport) return;
|
|
114
|
+
|
|
115
|
+
const reporter = await resolveReporter(config.reporter, args);
|
|
116
|
+
if (!reporter) return;
|
|
117
|
+
|
|
118
|
+
const baseOptions = createDefaultReportOptions(args);
|
|
119
|
+
const customOptions =
|
|
120
|
+
typeof config.reportOptions === "function"
|
|
121
|
+
? await config.reportOptions(args)
|
|
122
|
+
: config.reportOptions;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await reporter.captureException(
|
|
126
|
+
args.err,
|
|
127
|
+
mergeReportOptions(baseOptions, customOptions),
|
|
128
|
+
);
|
|
129
|
+
} catch (reportingError) {
|
|
130
|
+
try {
|
|
131
|
+
await config.onReporterError?.({
|
|
132
|
+
error: args.err,
|
|
133
|
+
reportingError,
|
|
134
|
+
ctx: args.ctx,
|
|
135
|
+
req: args.req,
|
|
136
|
+
contract: args.contract,
|
|
137
|
+
});
|
|
138
|
+
} catch {
|
|
139
|
+
// Reporter failure observers must not affect request handling.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Default filter for HTTP error reporting.
|
|
148
|
+
*/
|
|
149
|
+
export function shouldReportServerError(error: unknown): boolean {
|
|
150
|
+
if (isAppError(error)) return false;
|
|
151
|
+
if (error instanceof AuthUnauthorizedError) return false;
|
|
152
|
+
if (error instanceof TenantRequiredError) return false;
|
|
153
|
+
if (error instanceof IdempotencyConflictError) return false;
|
|
154
|
+
if (error instanceof IdempotencyInProgressError) return false;
|
|
155
|
+
if (error instanceof GateAuthorizationError) return false;
|
|
156
|
+
if (error instanceof EntitlementRequiredError) return false;
|
|
157
|
+
if (error instanceof SchemaValidationError) return false;
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function resolveReporter<Ctx extends ErrorReportingContext>(
|
|
162
|
+
reporter: ErrorReportingHooksOptions<Ctx>["reporter"],
|
|
163
|
+
args: ErrorReportingHookArgs<Ctx>,
|
|
164
|
+
): Promise<ErrorReporterPort | undefined> {
|
|
165
|
+
if (typeof reporter === "function") {
|
|
166
|
+
return reporter(args);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (reporter) {
|
|
170
|
+
return reporter;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return args.ctx?.ports?.errorReporter;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function createDefaultReportOptions<Ctx extends ErrorReportingContext>(
|
|
177
|
+
args: ErrorReportingHookArgs<Ctx>,
|
|
178
|
+
): ErrorReportOptions {
|
|
179
|
+
const ctx = args.ctx;
|
|
180
|
+
const url = safeUrl(args.req.url);
|
|
181
|
+
const actor = ctx?.actor;
|
|
182
|
+
const tenant = ctx?.tenant;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
level: "error",
|
|
186
|
+
mechanism: "beignet.server",
|
|
187
|
+
handled: false,
|
|
188
|
+
requestId: ctx?.requestId,
|
|
189
|
+
traceId: ctx?.traceId,
|
|
190
|
+
spanId: ctx?.spanId,
|
|
191
|
+
parentSpanId: ctx?.parentSpanId,
|
|
192
|
+
traceparent: ctx?.traceparent,
|
|
193
|
+
user: actor?.type === "user" && actor.id ? { id: actor.id } : undefined,
|
|
194
|
+
tags: {
|
|
195
|
+
"beignet.kind": "http",
|
|
196
|
+
"http.method": args.req.method,
|
|
197
|
+
"http.route": args.contract.path,
|
|
198
|
+
"http.contract": args.contract.name,
|
|
199
|
+
},
|
|
200
|
+
contexts: {
|
|
201
|
+
request: {
|
|
202
|
+
method: args.req.method,
|
|
203
|
+
path: url?.pathname ?? args.contract.path,
|
|
204
|
+
route: args.contract.path,
|
|
205
|
+
contract: args.contract.name,
|
|
206
|
+
},
|
|
207
|
+
actor:
|
|
208
|
+
actor?.type || actor?.id
|
|
209
|
+
? {
|
|
210
|
+
type: actor.type,
|
|
211
|
+
id: actor.id,
|
|
212
|
+
}
|
|
213
|
+
: undefined,
|
|
214
|
+
tenant: tenant?.id ? { id: tenant.id } : undefined,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function mergeReportOptions(
|
|
220
|
+
base: ErrorReportOptions,
|
|
221
|
+
custom: ErrorReportOptions | undefined,
|
|
222
|
+
): ErrorReportOptions {
|
|
223
|
+
if (!custom) return base;
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...base,
|
|
227
|
+
...custom,
|
|
228
|
+
tags: {
|
|
229
|
+
...base.tags,
|
|
230
|
+
...custom.tags,
|
|
231
|
+
},
|
|
232
|
+
contexts: {
|
|
233
|
+
...base.contexts,
|
|
234
|
+
...custom.contexts,
|
|
235
|
+
},
|
|
236
|
+
extra: {
|
|
237
|
+
...base.extra,
|
|
238
|
+
...custom.extra,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function safeUrl(value: string): URL | undefined {
|
|
244
|
+
try {
|
|
245
|
+
return new URL(value);
|
|
246
|
+
} catch {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -16,6 +16,14 @@ export {
|
|
|
16
16
|
type CorsConfig,
|
|
17
17
|
createCorsHooks,
|
|
18
18
|
} from "./cors.js";
|
|
19
|
+
export {
|
|
20
|
+
createErrorReportingHooks,
|
|
21
|
+
type ErrorReporterResolver,
|
|
22
|
+
type ErrorReportingContext,
|
|
23
|
+
type ErrorReportingHookArgs,
|
|
24
|
+
type ErrorReportingHooksOptions,
|
|
25
|
+
shouldReportServerError,
|
|
26
|
+
} from "./error-reporting.js";
|
|
19
27
|
export {
|
|
20
28
|
defaultMapErrorToResponse,
|
|
21
29
|
type ErrorMappingConfig,
|
package/src/server/server.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import type { AnyPorts } from "../ports/index.js";
|
|
28
28
|
import {
|
|
29
29
|
AuthUnauthorizedError,
|
|
30
|
+
EntitlementRequiredError,
|
|
30
31
|
GateAuthorizationError,
|
|
31
32
|
isUnboundPort,
|
|
32
33
|
TenantRequiredError,
|
|
@@ -1158,6 +1159,20 @@ function createRequestExecutor<
|
|
|
1158
1159
|
};
|
|
1159
1160
|
}
|
|
1160
1161
|
|
|
1162
|
+
if (currentError instanceof EntitlementRequiredError) {
|
|
1163
|
+
return {
|
|
1164
|
+
ctx,
|
|
1165
|
+
response: errorResponse(
|
|
1166
|
+
currentError.status,
|
|
1167
|
+
currentError.code,
|
|
1168
|
+
currentError.message,
|
|
1169
|
+
currentError.details,
|
|
1170
|
+
),
|
|
1171
|
+
error: currentError,
|
|
1172
|
+
owner: "framework",
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1161
1176
|
for (const hook of hooks) {
|
|
1162
1177
|
if (!hook.mapUnhandledError) continue;
|
|
1163
1178
|
try {
|
|
@@ -2381,6 +2396,11 @@ export function contractsFromRoutes<
|
|
|
2381
2396
|
* ```
|
|
2382
2397
|
*/
|
|
2383
2398
|
export function defineRouteGroup<Ctx>(): RouteGroupBuilder<Ctx>;
|
|
2399
|
+
/**
|
|
2400
|
+
* @deprecated Prefer `defineRouteGroup<Ctx>()({ ... })`. TypeScript cannot
|
|
2401
|
+
* infer the route tuple generic when callers provide only `Ctx`, so the direct
|
|
2402
|
+
* form preserves context checking but erases per-route contract output checks.
|
|
2403
|
+
*/
|
|
2384
2404
|
export function defineRouteGroup<
|
|
2385
2405
|
Ctx,
|
|
2386
2406
|
// biome-ignore lint/suspicious/noExplicitAny: route contract types are erased at this level
|
|
@@ -223,8 +223,8 @@ export type UseCaseRouteDef<
|
|
|
223
223
|
/**
|
|
224
224
|
* Structural check that a use case accepts the context this route provides.
|
|
225
225
|
*
|
|
226
|
-
* Enforced through `run` parameter contravariance so it applies even
|
|
227
|
-
* contract types are erased
|
|
226
|
+
* Enforced through `run` parameter contravariance so it applies even at loose
|
|
227
|
+
* collection boundaries where contract types are erased.
|
|
228
228
|
*/
|
|
229
229
|
export type UseCaseAcceptsCtx<Ctx> = {
|
|
230
230
|
run: (args: { ctx: Ctx; input: never }) => Promise<unknown>;
|
package/src/testing/index.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMemoryErrorReporter,
|
|
3
|
+
type MemoryErrorReporterPort,
|
|
4
|
+
} from "../error-reporting/index.js";
|
|
5
|
+
import { createMemoryFlags, type MemoryFlagsPort } from "../flags/index.js";
|
|
1
6
|
import {
|
|
2
7
|
createMemoryIdempotencyStore,
|
|
3
8
|
type MemoryIdempotencyStore,
|
|
4
9
|
} from "../idempotency/index.js";
|
|
10
|
+
import { createMemoryLocks, type MemoryLocksPort } from "../locks/index.js";
|
|
5
11
|
import { createMemoryMailer, type MemoryMailerPort } from "../mail/index.js";
|
|
6
12
|
import {
|
|
7
13
|
createMemoryNotificationPort,
|
|
8
14
|
type MemoryNotificationPort,
|
|
9
15
|
} from "../notifications/index.js";
|
|
10
16
|
import { createMemoryOutbox, type MemoryOutboxPort } from "../outbox/index.js";
|
|
17
|
+
import {
|
|
18
|
+
createMemoryPayments,
|
|
19
|
+
type MemoryPaymentsPort,
|
|
20
|
+
} from "../payments/index.js";
|
|
11
21
|
import {
|
|
12
22
|
type ActivityActor,
|
|
13
23
|
type ActivityTenant,
|
|
@@ -54,6 +64,7 @@ import type {
|
|
|
54
64
|
AnyServiceProvider,
|
|
55
65
|
ProviderSetupResult,
|
|
56
66
|
} from "../providers/provider.js";
|
|
67
|
+
import { createMemorySearch, type MemorySearchPort } from "../search/index.js";
|
|
57
68
|
import { createAmbientAuditLog } from "../server/audit-context.js";
|
|
58
69
|
import {
|
|
59
70
|
clearActiveRequestContext,
|
|
@@ -89,11 +100,19 @@ export interface CommonTestPorts<TxPorts = AnyPorts> {
|
|
|
89
100
|
* Recording event bus.
|
|
90
101
|
*/
|
|
91
102
|
eventBus: EventBusPort;
|
|
103
|
+
/**
|
|
104
|
+
* In-memory error reporter.
|
|
105
|
+
*/
|
|
106
|
+
errorReporter: MemoryErrorReporterPort;
|
|
92
107
|
/**
|
|
93
108
|
* Authorization gate. Apps with real policies should provide their own gate
|
|
94
109
|
* through `overrides`.
|
|
95
110
|
*/
|
|
96
111
|
gate: GatePort<unknown, []>;
|
|
112
|
+
/**
|
|
113
|
+
* In-memory feature flags.
|
|
114
|
+
*/
|
|
115
|
+
flags: MemoryFlagsPort;
|
|
97
116
|
/**
|
|
98
117
|
* In-memory idempotency store.
|
|
99
118
|
*/
|
|
@@ -110,6 +129,10 @@ export interface CommonTestPorts<TxPorts = AnyPorts> {
|
|
|
110
129
|
* No-op logger.
|
|
111
130
|
*/
|
|
112
131
|
logger: LoggerPort;
|
|
132
|
+
/**
|
|
133
|
+
* In-memory lease-backed locks.
|
|
134
|
+
*/
|
|
135
|
+
locks: MemoryLocksPort;
|
|
113
136
|
/**
|
|
114
137
|
* In-memory mailer.
|
|
115
138
|
*/
|
|
@@ -122,6 +145,14 @@ export interface CommonTestPorts<TxPorts = AnyPorts> {
|
|
|
122
145
|
* In-memory outbox.
|
|
123
146
|
*/
|
|
124
147
|
outbox: MemoryOutboxPort;
|
|
148
|
+
/**
|
|
149
|
+
* In-memory payments port.
|
|
150
|
+
*/
|
|
151
|
+
payments: MemoryPaymentsPort;
|
|
152
|
+
/**
|
|
153
|
+
* In-memory search port.
|
|
154
|
+
*/
|
|
155
|
+
search: MemorySearchPort;
|
|
125
156
|
/**
|
|
126
157
|
* In-memory rate limiter.
|
|
127
158
|
*/
|
|
@@ -159,6 +190,10 @@ export interface TestPortsFixture<
|
|
|
159
190
|
* Recorded events published through `eventBus`.
|
|
160
191
|
*/
|
|
161
192
|
events: RecordedEvent[];
|
|
193
|
+
/**
|
|
194
|
+
* Memory error reporter, exposed for assertions.
|
|
195
|
+
*/
|
|
196
|
+
errorReporter: MemoryErrorReporterPort;
|
|
162
197
|
/**
|
|
163
198
|
* Recording job dispatcher port.
|
|
164
199
|
*/
|
|
@@ -179,6 +214,10 @@ export interface TestPortsFixture<
|
|
|
179
214
|
* Memory outbox, exposed for durable workflow assertions.
|
|
180
215
|
*/
|
|
181
216
|
outbox: MemoryOutboxPort;
|
|
217
|
+
/**
|
|
218
|
+
* Memory payments port.
|
|
219
|
+
*/
|
|
220
|
+
payments: MemoryPaymentsPort;
|
|
182
221
|
/**
|
|
183
222
|
* Memory storage port.
|
|
184
223
|
*/
|
|
@@ -191,6 +230,10 @@ export interface TestPortsFixture<
|
|
|
191
230
|
* Test cache port.
|
|
192
231
|
*/
|
|
193
232
|
cache: CachePort;
|
|
233
|
+
/**
|
|
234
|
+
* Test feature flags port.
|
|
235
|
+
*/
|
|
236
|
+
flags: MemoryFlagsPort;
|
|
194
237
|
/**
|
|
195
238
|
* Test rate-limit port.
|
|
196
239
|
*/
|
|
@@ -199,6 +242,14 @@ export interface TestPortsFixture<
|
|
|
199
242
|
* Test logger port.
|
|
200
243
|
*/
|
|
201
244
|
logger: LoggerPort;
|
|
245
|
+
/**
|
|
246
|
+
* Test locks port.
|
|
247
|
+
*/
|
|
248
|
+
locks: MemoryLocksPort;
|
|
249
|
+
/**
|
|
250
|
+
* Test search port.
|
|
251
|
+
*/
|
|
252
|
+
search: MemorySearchPort;
|
|
202
253
|
/**
|
|
203
254
|
* Test clock port.
|
|
204
255
|
*/
|
|
@@ -323,14 +374,22 @@ export function createTestPorts<
|
|
|
323
374
|
const cache = createMemoryCache();
|
|
324
375
|
const clock = options.clock ?? createFrozenClock();
|
|
325
376
|
const { bus: eventBus, events } = createRecordingEventBus();
|
|
377
|
+
const errorReporter = createMemoryErrorReporter();
|
|
378
|
+
const flags = createMemoryFlags();
|
|
326
379
|
const { jobs, dispatchedJobs } = createRecordingJobDispatcher();
|
|
327
380
|
const idempotency = createMemoryIdempotencyStore();
|
|
328
381
|
const ids = options.ids ?? createUuidIdGenerator();
|
|
329
382
|
const logger = createNoopLogger();
|
|
383
|
+
const locks = createMemoryLocks({
|
|
384
|
+
now: () => clock.now(),
|
|
385
|
+
sleep: createClockAwareSleep(clock),
|
|
386
|
+
});
|
|
330
387
|
const mailer = createMemoryMailer();
|
|
331
388
|
const notifications = createMemoryNotificationPort();
|
|
332
389
|
const outbox = createMemoryOutbox();
|
|
390
|
+
const payments = createMemoryPayments();
|
|
333
391
|
const rateLimit = createMemoryRateLimiter();
|
|
392
|
+
const search = createMemorySearch();
|
|
334
393
|
const storage = createMemoryStorage();
|
|
335
394
|
|
|
336
395
|
const defaultPorts = {
|
|
@@ -338,15 +397,20 @@ export function createTestPorts<
|
|
|
338
397
|
cache,
|
|
339
398
|
clock,
|
|
340
399
|
eventBus,
|
|
400
|
+
errorReporter,
|
|
401
|
+
flags,
|
|
341
402
|
gate: createGate({ policies: [] }),
|
|
342
403
|
idempotency,
|
|
343
404
|
ids,
|
|
344
405
|
jobs,
|
|
345
406
|
logger,
|
|
407
|
+
locks,
|
|
346
408
|
mailer,
|
|
347
409
|
notifications,
|
|
348
410
|
outbox,
|
|
411
|
+
payments,
|
|
349
412
|
rateLimit,
|
|
413
|
+
search,
|
|
350
414
|
storage,
|
|
351
415
|
};
|
|
352
416
|
const overrides = completeTestPortOverrides(options.overrides);
|
|
@@ -409,16 +473,21 @@ export function createTestPorts<
|
|
|
409
473
|
audit,
|
|
410
474
|
eventBus,
|
|
411
475
|
events,
|
|
476
|
+
errorReporter,
|
|
412
477
|
jobs,
|
|
413
478
|
dispatchedJobs,
|
|
414
479
|
mailer,
|
|
415
480
|
notifications,
|
|
416
481
|
outbox,
|
|
482
|
+
payments,
|
|
417
483
|
storage,
|
|
418
484
|
idempotency,
|
|
419
485
|
cache,
|
|
486
|
+
flags,
|
|
420
487
|
rateLimit,
|
|
421
488
|
logger,
|
|
489
|
+
locks,
|
|
490
|
+
search,
|
|
422
491
|
clock,
|
|
423
492
|
ids,
|
|
424
493
|
uow,
|
|
@@ -432,6 +501,19 @@ function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
|
432
501
|
return proto === Object.prototype || proto === null;
|
|
433
502
|
}
|
|
434
503
|
|
|
504
|
+
function createClockAwareSleep(
|
|
505
|
+
clock: ClockPort,
|
|
506
|
+
): (ms: number) => Promise<void> {
|
|
507
|
+
const maybeFrozenClock = clock as Partial<{ advance(ms: number): void }>;
|
|
508
|
+
if (typeof maybeFrozenClock.advance === "function") {
|
|
509
|
+
return async (ms) => {
|
|
510
|
+
maybeFrozenClock.advance?.(ms);
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
515
|
+
}
|
|
516
|
+
|
|
435
517
|
function createMissingTestPortMember(
|
|
436
518
|
portKey: string,
|
|
437
519
|
member: string,
|
|
@@ -799,7 +881,7 @@ export interface TestContextFixture<Ctx, Ports extends AnyPorts>
|
|
|
799
881
|
|
|
800
882
|
/**
|
|
801
883
|
* Create a one-call test context fixture for jobs, listeners, schedules,
|
|
802
|
-
* notifications, tasks, and use-case tests.
|
|
884
|
+
* notifications, payments, tasks, and use-case tests.
|
|
803
885
|
*
|
|
804
886
|
* The fixture builds common memory ports through `createTestPorts(...)`,
|
|
805
887
|
* assembles an app context with actor, tenant, request ID, trace ID, auth,
|