@beignet/core 0.0.3 → 0.0.5
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 +159 -0
- package/README.md +792 -50
- package/dist/application/index.d.ts +28 -2
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +140 -12
- package/dist/application/index.js.map +1 -1
- package/dist/client/client.d.ts +2 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +136 -48
- package/dist/client/client.js.map +1 -1
- package/dist/client/error-messages.d.ts +14 -0
- package/dist/client/error-messages.d.ts.map +1 -0
- package/dist/client/error-messages.js +23 -0
- package/dist/client/error-messages.js.map +1 -0
- package/dist/client/index.d.ts +8 -4
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +6 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/types.d.ts +35 -5
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client-only.d.ts +8 -0
- package/dist/client-only.d.ts.map +1 -0
- package/dist/client-only.js +8 -0
- package/dist/client-only.js.map +1 -0
- package/dist/config/index.d.ts +5 -5
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +2 -2
- package/dist/config/index.js.map +1 -1
- package/dist/contracts/catalog-errors.d.ts +27 -0
- package/dist/contracts/catalog-errors.d.ts.map +1 -0
- package/dist/contracts/catalog-errors.js +69 -0
- package/dist/contracts/catalog-errors.js.map +1 -0
- package/dist/contracts/contract-builder.d.ts +15 -12
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +15 -41
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/contracts/contract-group.d.ts +11 -8
- package/dist/contracts/contract-group.d.ts.map +1 -1
- package/dist/contracts/contract-group.js +13 -40
- package/dist/contracts/contract-group.js.map +1 -1
- package/dist/contracts/contract-like.d.ts +1 -1
- package/dist/contracts/contract-like.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +13 -9
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/contracts/index.js +9 -5
- package/dist/contracts/index.js.map +1 -1
- package/dist/contracts/openapi-meta.d.ts +48 -0
- package/dist/contracts/openapi-meta.d.ts.map +1 -1
- package/dist/contracts/openapi-meta.js +3 -0
- package/dist/contracts/openapi-meta.js.map +1 -1
- package/dist/contracts/path-template.d.ts +1 -1
- package/dist/contracts/path-template.js +2 -2
- package/dist/contracts/path-template.js.map +1 -1
- package/dist/contracts/schema-shape.d.ts +37 -0
- package/dist/contracts/schema-shape.d.ts.map +1 -0
- package/dist/contracts/schema-shape.js +61 -0
- package/dist/contracts/schema-shape.js.map +1 -0
- package/dist/contracts/success-status.d.ts +32 -0
- package/dist/contracts/success-status.d.ts.map +1 -0
- package/dist/contracts/success-status.js +18 -0
- package/dist/contracts/success-status.js.map +1 -0
- package/dist/contracts/types.d.ts +25 -5
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/contracts/types.js.map +1 -1
- package/dist/contracts/utils.d.ts +1 -1
- package/dist/contracts/utils.d.ts.map +1 -1
- package/dist/contracts/utils.js +1 -1
- package/dist/contracts/utils.js.map +1 -1
- package/dist/domain/events.d.ts +1 -1
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/index.d.ts +3 -3
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +3 -3
- package/dist/domain/index.js.map +1 -1
- package/dist/errors/catalog.d.ts +9 -1
- package/dist/errors/catalog.d.ts.map +1 -1
- package/dist/errors/catalog.js +7 -1
- package/dist/errors/catalog.js.map +1 -1
- package/dist/errors/http.d.ts +10 -0
- package/dist/errors/http.d.ts.map +1 -1
- package/dist/errors/http.js +11 -1
- package/dist/errors/http.js.map +1 -1
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +4 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/errors/response.d.ts +4 -1
- package/dist/errors/response.d.ts.map +1 -1
- package/dist/errors/response.js.map +1 -1
- package/dist/events/index.d.ts +10 -12
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +10 -10
- package/dist/events/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +5 -3
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +12 -14
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +13 -13
- package/dist/jobs/index.js.map +1 -1
- package/dist/notifications/index.d.ts +14 -16
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +14 -14
- package/dist/notifications/index.js.map +1 -1
- package/dist/openapi/index.d.ts +8 -3
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +41 -29
- package/dist/openapi/index.js.map +1 -1
- package/dist/openapi/schema-introspector.d.ts +37 -0
- package/dist/openapi/schema-introspector.d.ts.map +1 -1
- package/dist/openapi/schema-introspector.js +23 -17
- package/dist/openapi/schema-introspector.js.map +1 -1
- package/dist/outbox/index.d.ts +15 -6
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +60 -16
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/audit.d.ts +56 -10
- package/dist/ports/audit.d.ts.map +1 -1
- package/dist/ports/audit.js +71 -3
- package/dist/ports/audit.js.map +1 -1
- package/dist/ports/auth.d.ts +92 -0
- package/dist/ports/auth.d.ts.map +1 -1
- package/dist/ports/auth.js +92 -0
- package/dist/ports/auth.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/events.d.ts.map +1 -1
- package/dist/ports/index.d.ts +62 -33
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +28 -34
- package/dist/ports/index.js.map +1 -1
- package/dist/ports/policy.d.ts +32 -3
- package/dist/ports/policy.d.ts.map +1 -1
- package/dist/ports/policy.js +13 -2
- package/dist/ports/policy.js.map +1 -1
- package/dist/ports/testing.d.ts +1030 -2
- package/dist/ports/testing.d.ts.map +1 -1
- package/dist/ports/testing.js +1031 -1
- package/dist/ports/testing.js.map +1 -1
- package/dist/ports/unbound.d.ts +21 -0
- package/dist/ports/unbound.d.ts.map +1 -0
- package/dist/ports/unbound.js +57 -0
- package/dist/ports/unbound.js.map +1 -0
- package/dist/ports/unit-of-work.d.ts +1 -1
- package/dist/ports/unit-of-work.d.ts.map +1 -1
- package/dist/ports/unit-of-work.js +1 -1
- package/dist/ports/unit-of-work.js.map +1 -1
- package/dist/providers/index.d.ts +3 -2
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +3 -2
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +45 -4
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js +25 -6
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/metadata.d.ts +39 -0
- package/dist/providers/metadata.d.ts.map +1 -0
- package/dist/providers/metadata.js +169 -0
- package/dist/providers/metadata.js.map +1 -0
- package/dist/providers/provider.d.ts +114 -9
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js +3 -20
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +94 -13
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +66 -12
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/audit-context.d.ts +29 -0
- package/dist/server/audit-context.d.ts.map +1 -0
- package/dist/server/audit-context.js +44 -0
- package/dist/server/audit-context.js.map +1 -0
- package/dist/server/context.d.ts +141 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +39 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/contract-like.d.ts +1 -1
- package/dist/server/contract-like.d.ts.map +1 -1
- package/dist/server/contract-like.js +1 -1
- package/dist/server/contract-like.js.map +1 -1
- package/dist/server/health.d.ts +2 -2
- package/dist/server/health.d.ts.map +1 -1
- package/dist/server/hooks/auth.d.ts +49 -10
- package/dist/server/hooks/auth.d.ts.map +1 -1
- package/dist/server/hooks/auth.js +77 -37
- package/dist/server/hooks/auth.js.map +1 -1
- package/dist/server/hooks/cors.d.ts +1 -1
- package/dist/server/hooks/cors.d.ts.map +1 -1
- package/dist/server/hooks/errors.d.ts +2 -2
- package/dist/server/hooks/errors.d.ts.map +1 -1
- package/dist/server/hooks/errors.js +2 -2
- package/dist/server/hooks/errors.js.map +1 -1
- package/dist/server/hooks/idempotency.d.ts +78 -0
- package/dist/server/hooks/idempotency.d.ts.map +1 -0
- package/dist/server/hooks/idempotency.js +154 -0
- package/dist/server/hooks/idempotency.js.map +1 -0
- package/dist/server/hooks/index.d.ts +8 -7
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +6 -5
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/logging.d.ts +2 -2
- package/dist/server/hooks/logging.d.ts.map +1 -1
- package/dist/server/hooks/logging.js +1 -1
- package/dist/server/hooks/logging.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +25 -7
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +47 -12
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks.d.ts +1 -1
- package/dist/server/hooks.d.ts.map +1 -1
- package/dist/server/hooks.js +1 -1
- package/dist/server/hooks.js.map +1 -1
- package/dist/server/http.d.ts +61 -35
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +1 -20
- package/dist/server/http.js.map +1 -1
- package/dist/server/index.d.ts +36 -12
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +24 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts +108 -0
- package/dist/server/instrumentation.d.ts.map +1 -0
- package/dist/server/instrumentation.js +297 -0
- package/dist/server/instrumentation.js.map +1 -0
- package/dist/server/openapi.d.ts +3 -3
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/openapi.js +1 -1
- package/dist/server/openapi.js.map +1 -1
- package/dist/server/providers/index.d.ts +3 -3
- package/dist/server/providers/index.d.ts.map +1 -1
- package/dist/server/providers/index.js +3 -3
- package/dist/server/providers/index.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts +2 -2
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +2 -2
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/request-context.d.ts +67 -0
- package/dist/server/request-context.d.ts.map +1 -0
- package/dist/server/request-context.js +79 -0
- package/dist/server/request-context.js.map +1 -0
- package/dist/server/server-context.d.ts +38 -0
- package/dist/server/server-context.d.ts.map +1 -0
- package/dist/server/server-context.js +38 -0
- package/dist/server/server-context.js.map +1 -0
- package/dist/server/server.d.ts +105 -33
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +434 -118
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +2 -2
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +2 -2
- package/dist/server/types.js.map +1 -1
- package/dist/server/use-case-route.d.ts +263 -0
- package/dist/server/use-case-route.d.ts.map +1 -0
- package/dist/server/use-case-route.js +77 -0
- package/dist/server/use-case-route.js.map +1 -0
- package/dist/server-only.d.ts +8 -0
- package/dist/server-only.d.ts.map +1 -0
- package/dist/server-only.js +8 -0
- package/dist/server-only.js.map +1 -0
- package/dist/tasks/index.d.ts +139 -0
- package/dist/tasks/index.d.ts.map +1 -0
- package/dist/tasks/index.js +98 -0
- package/dist/tasks/index.js.map +1 -0
- package/dist/testing/index.d.ts +607 -5
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +426 -4
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/index.d.ts +89 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +101 -0
- package/dist/tracing/index.js.map +1 -0
- package/dist/uploads/client.d.ts +1 -1
- package/dist/uploads/client.d.ts.map +1 -1
- package/dist/uploads/index.d.ts +2 -2
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +1 -1
- package/dist/uploads/index.js.map +1 -1
- package/package.json +24 -2
- package/src/application/index.ts +193 -10
- package/src/client/client.ts +148 -150
- package/src/client/error-messages.ts +35 -0
- package/src/client/index.ts +12 -4
- package/src/client/types.ts +44 -5
- package/src/client-only.ts +7 -0
- package/src/config/index.ts +6 -6
- package/src/contracts/catalog-errors.ts +115 -0
- package/src/contracts/contract-builder.ts +39 -76
- package/src/contracts/contract-group.ts +33 -68
- package/src/contracts/contract-like.ts +1 -1
- package/src/contracts/index.ts +24 -11
- package/src/contracts/openapi-meta.ts +55 -0
- package/src/contracts/path-template.ts +2 -2
- package/src/contracts/schema-shape.ts +75 -0
- package/src/contracts/success-status.ts +68 -0
- package/src/contracts/types.ts +32 -5
- package/src/contracts/utils.ts +5 -2
- package/src/domain/events.ts +6 -2
- package/src/domain/index.ts +3 -3
- package/src/errors/catalog.ts +9 -1
- package/src/errors/http.ts +11 -1
- package/src/errors/index.ts +4 -4
- package/src/errors/response.ts +4 -1
- package/src/events/index.ts +12 -26
- package/src/idempotency/index.ts +5 -3
- package/src/jobs/index.ts +14 -24
- package/src/notifications/index.ts +17 -27
- package/src/openapi/index.ts +73 -38
- package/src/openapi/schema-introspector.ts +68 -17
- package/src/outbox/index.ts +84 -19
- package/src/ports/audit.ts +120 -11
- package/src/ports/auth.ts +132 -0
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +104 -35
- package/src/ports/policy.ts +50 -3
- package/src/ports/testing.ts +2220 -33
- package/src/ports/unbound.ts +64 -0
- package/src/ports/unit-of-work.ts +6 -2
- package/src/providers/index.ts +16 -3
- package/src/providers/instrumentation.ts +86 -7
- package/src/providers/metadata.ts +234 -0
- package/src/providers/provider.ts +168 -9
- package/src/schedules/index.ts +173 -23
- package/src/server/audit-context.ts +45 -0
- package/src/server/context.ts +224 -0
- package/src/server/contract-like.ts +1 -1
- package/src/server/health.ts +2 -2
- package/src/server/hooks/auth.ts +141 -51
- package/src/server/hooks/cors.ts +1 -1
- package/src/server/hooks/errors.ts +7 -4
- package/src/server/hooks/idempotency.ts +263 -0
- package/src/server/hooks/index.ts +14 -7
- package/src/server/hooks/logging.ts +3 -3
- package/src/server/hooks/rate-limit.ts +85 -17
- package/src/server/hooks.ts +1 -1
- package/src/server/http.ts +78 -51
- package/src/server/index.ts +62 -12
- package/src/server/instrumentation.ts +470 -0
- package/src/server/openapi.ts +4 -4
- package/src/server/providers/index.ts +6 -3
- package/src/server/providers/loadProviderConfig.ts +4 -4
- package/src/server/request-context.ts +116 -0
- package/src/server/server-context.ts +44 -0
- package/src/server/server.ts +886 -238
- package/src/server/types.ts +2 -2
- package/src/server/use-case-route.ts +430 -0
- package/src/server-only.ts +7 -0
- package/src/tasks/index.ts +275 -0
- package/src/testing/index.ts +1142 -6
- package/src/tracing/index.ts +176 -0
- package/src/uploads/client.ts +1 -1
- package/src/uploads/index.ts +7 -3
- package/dist/ports/mailer.d.ts +0 -6
- package/dist/ports/mailer.d.ts.map +0 -1
- package/dist/ports/mailer.js +0 -2
- package/dist/ports/mailer.js.map +0 -1
- package/dist/ports/schedules.d.ts +0 -9
- package/dist/ports/schedules.d.ts.map +0 -1
- package/dist/ports/schedules.js +0 -2
- package/dist/ports/schedules.js.map +0 -1
package/src/openapi/index.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* OpenAPI 3.1 generation from Beignet contracts
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { type ZodTypeAny, z } from "zod";
|
|
8
7
|
import {
|
|
9
8
|
type AnyContract,
|
|
10
9
|
type ContractLike,
|
|
@@ -13,17 +12,26 @@ import {
|
|
|
13
12
|
parsePathTemplate,
|
|
14
13
|
resolveContract,
|
|
15
14
|
STANDARD_ERROR_RESPONSE_SCHEMA,
|
|
16
|
-
} from "../contracts";
|
|
15
|
+
} from "../contracts/index.js";
|
|
16
|
+
import {
|
|
17
|
+
comparePathParamsToTemplate,
|
|
18
|
+
formatPathParamsMismatch,
|
|
19
|
+
} from "../contracts/schema-shape.js";
|
|
17
20
|
import {
|
|
18
21
|
createZodIntrospector,
|
|
22
|
+
createZodSchemaConverter,
|
|
23
|
+
type SchemaConverter,
|
|
19
24
|
type SchemaIntrospector,
|
|
20
|
-
|
|
25
|
+
type SchemaIO,
|
|
26
|
+
} from "./schema-introspector.js";
|
|
21
27
|
|
|
22
28
|
// Re-export the introspector types for consumers who want custom implementations
|
|
23
29
|
export {
|
|
24
30
|
createZodIntrospector,
|
|
31
|
+
createZodSchemaConverter,
|
|
32
|
+
type SchemaConverter,
|
|
25
33
|
type SchemaIntrospector,
|
|
26
|
-
} from "./schema-introspector";
|
|
34
|
+
} from "./schema-introspector.js";
|
|
27
35
|
|
|
28
36
|
/**
|
|
29
37
|
* OpenAPI 3.1 info object.
|
|
@@ -338,6 +346,11 @@ export interface OpenAPIGeneratorOptions {
|
|
|
338
346
|
* to support other schema libraries.
|
|
339
347
|
*/
|
|
340
348
|
schemaIntrospector?: SchemaIntrospector;
|
|
349
|
+
/**
|
|
350
|
+
* Schema converters used to turn contract schemas into OpenAPI schemas.
|
|
351
|
+
* Custom converters run before Beignet's default Zod converter.
|
|
352
|
+
*/
|
|
353
|
+
schemaConverters?: readonly SchemaConverter[];
|
|
341
354
|
}
|
|
342
355
|
|
|
343
356
|
/**
|
|
@@ -347,10 +360,9 @@ type GeneratorState = {
|
|
|
347
360
|
components: ComponentsObject;
|
|
348
361
|
jsonMediaType: string;
|
|
349
362
|
introspector: SchemaIntrospector;
|
|
363
|
+
schemaConverters: readonly SchemaConverter[];
|
|
350
364
|
};
|
|
351
365
|
|
|
352
|
-
type SchemaIO = "input" | "output";
|
|
353
|
-
|
|
354
366
|
/**
|
|
355
367
|
* Contract input accepted by the OpenAPI generator.
|
|
356
368
|
*/
|
|
@@ -392,6 +404,10 @@ export function contractsToOpenAPI(
|
|
|
392
404
|
components,
|
|
393
405
|
jsonMediaType: options.jsonMediaType ?? "application/json",
|
|
394
406
|
introspector: options.schemaIntrospector ?? createZodIntrospector(),
|
|
407
|
+
schemaConverters: [
|
|
408
|
+
...(options.schemaConverters ?? []),
|
|
409
|
+
createZodSchemaConverter(),
|
|
410
|
+
],
|
|
395
411
|
};
|
|
396
412
|
|
|
397
413
|
for (const contract of contracts) {
|
|
@@ -456,6 +472,7 @@ function addContractToPaths(
|
|
|
456
472
|
addHeaderParams(contract, operation, state);
|
|
457
473
|
addRequestBody(contract, operation, state);
|
|
458
474
|
addResponses(contract, operation, state);
|
|
475
|
+
applyOpenAPIOverrides(operation, meta);
|
|
459
476
|
|
|
460
477
|
// Clean up empty parameters array
|
|
461
478
|
if (operation.parameters?.length === 0) {
|
|
@@ -474,6 +491,25 @@ function addContractToPaths(
|
|
|
474
491
|
pathItem[methodKey] = operation;
|
|
475
492
|
}
|
|
476
493
|
|
|
494
|
+
function applyOpenAPIOverrides(
|
|
495
|
+
operation: OperationObject,
|
|
496
|
+
meta: AnyContract["metadata"]["openapi"] | undefined,
|
|
497
|
+
): void {
|
|
498
|
+
if (!meta) return;
|
|
499
|
+
|
|
500
|
+
for (const parameter of meta.parameters ?? []) {
|
|
501
|
+
addParameter(operation, parameter);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (meta.requestBody) {
|
|
505
|
+
operation.requestBody = meta.requestBody;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
for (const [status, response] of Object.entries(meta.responses ?? {})) {
|
|
509
|
+
operation.responses[status] = response;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
477
513
|
function addParameter(
|
|
478
514
|
operation: OperationObject,
|
|
479
515
|
parameter: ParameterObject,
|
|
@@ -525,20 +561,12 @@ function addPathParams(
|
|
|
525
561
|
return;
|
|
526
562
|
}
|
|
527
563
|
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
564
|
+
const { missingKeys, extraKeys } = comparePathParamsToTemplate({
|
|
565
|
+
pathKeys,
|
|
566
|
+
shapeKeys: Object.keys(shape),
|
|
567
|
+
});
|
|
531
568
|
if (missingKeys.length > 0 || extraKeys.length > 0) {
|
|
532
|
-
const details =
|
|
533
|
-
missingKeys.length > 0
|
|
534
|
-
? `missing pathParams keys: ${missingKeys.join(", ")}`
|
|
535
|
-
: undefined,
|
|
536
|
-
extraKeys.length > 0
|
|
537
|
-
? `extra pathParams keys: ${extraKeys.join(", ")}`
|
|
538
|
-
: undefined,
|
|
539
|
-
]
|
|
540
|
-
.filter(Boolean)
|
|
541
|
-
.join("; ");
|
|
569
|
+
const details = formatPathParamsMismatch({ missingKeys, extraKeys });
|
|
542
570
|
throw new Error(
|
|
543
571
|
`Path parameters for contract "${contract.name}" must match "${contract.path}" (${details}).`,
|
|
544
572
|
);
|
|
@@ -546,8 +574,8 @@ function addPathParams(
|
|
|
546
574
|
|
|
547
575
|
for (const key of pathKeys) {
|
|
548
576
|
const field = shape[key];
|
|
549
|
-
const paramSchemaRef =
|
|
550
|
-
field
|
|
577
|
+
const paramSchemaRef = schemaToConvertedSchemaRef(
|
|
578
|
+
field,
|
|
551
579
|
`${contract.name}_path_${key}`,
|
|
552
580
|
state,
|
|
553
581
|
"input",
|
|
@@ -593,8 +621,8 @@ function addQueryParams(
|
|
|
593
621
|
description = state.introspector.getDescription(field);
|
|
594
622
|
}
|
|
595
623
|
|
|
596
|
-
const paramSchemaRef =
|
|
597
|
-
field
|
|
624
|
+
const paramSchemaRef = schemaToConvertedSchemaRef(
|
|
625
|
+
field,
|
|
598
626
|
`${contract.name}_query_${key}`,
|
|
599
627
|
state,
|
|
600
628
|
"input",
|
|
@@ -630,8 +658,8 @@ function addHeaderParams(
|
|
|
630
658
|
const optional = state.introspector.isOptional(originalField);
|
|
631
659
|
const description = state.introspector.getDescription(originalField);
|
|
632
660
|
|
|
633
|
-
const paramSchemaRef =
|
|
634
|
-
originalField
|
|
661
|
+
const paramSchemaRef = schemaToConvertedSchemaRef(
|
|
662
|
+
originalField,
|
|
635
663
|
`${contract.name}_header_${key}`,
|
|
636
664
|
state,
|
|
637
665
|
"input",
|
|
@@ -663,8 +691,8 @@ function addRequestBody(
|
|
|
663
691
|
);
|
|
664
692
|
}
|
|
665
693
|
|
|
666
|
-
const schemaRef =
|
|
667
|
-
contract.body
|
|
694
|
+
const schemaRef = schemaToConvertedSchemaRef(
|
|
695
|
+
contract.body,
|
|
668
696
|
`${contract.name}_body`,
|
|
669
697
|
state,
|
|
670
698
|
"input",
|
|
@@ -889,10 +917,10 @@ function normalizeExampleKey(key: string): string {
|
|
|
889
917
|
}
|
|
890
918
|
|
|
891
919
|
/**
|
|
892
|
-
* Convert a
|
|
920
|
+
* Convert a validation schema to a JSON Schema reference, registering it in components.
|
|
893
921
|
*/
|
|
894
|
-
function
|
|
895
|
-
schema:
|
|
922
|
+
function schemaToConvertedSchemaRef(
|
|
923
|
+
schema: unknown,
|
|
896
924
|
nameHint: string,
|
|
897
925
|
state: GeneratorState,
|
|
898
926
|
io: SchemaIO,
|
|
@@ -905,14 +933,21 @@ function zodToSchemaRef(
|
|
|
905
933
|
const schemaName = normalizeSchemaName(nameHint, state);
|
|
906
934
|
|
|
907
935
|
if (!state.components.schemas[schemaName]) {
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
936
|
+
const converter = state.schemaConverters.find((candidate) =>
|
|
937
|
+
candidate.canConvert(schema),
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (!converter) {
|
|
941
|
+
throw new Error(
|
|
942
|
+
`Unable to convert schema "${nameHint}" to OpenAPI. ` +
|
|
943
|
+
"Pass a schemaConverters entry to contractsToOpenAPI for this schema library.",
|
|
944
|
+
);
|
|
945
|
+
}
|
|
913
946
|
|
|
914
|
-
|
|
915
|
-
|
|
947
|
+
const jsonSchema = converter.toJSONSchema(schema, {
|
|
948
|
+
nameHint,
|
|
949
|
+
io,
|
|
950
|
+
});
|
|
916
951
|
|
|
917
952
|
state.components.schemas[schemaName] = jsonSchema;
|
|
918
953
|
}
|
|
@@ -930,7 +965,7 @@ function schemaToSchemaRef(
|
|
|
930
965
|
return standardErrorResponseSchemaRef(nameHint, state);
|
|
931
966
|
}
|
|
932
967
|
|
|
933
|
-
return
|
|
968
|
+
return schemaToConvertedSchemaRef(schema, nameHint, state, io);
|
|
934
969
|
}
|
|
935
970
|
|
|
936
971
|
function standardErrorResponseSchemaRef(
|
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
import { type ZodTypeAny, z } from "zod";
|
|
2
|
+
import { getObjectSchemaShape } from "../contracts/schema-shape.js";
|
|
3
|
+
|
|
4
|
+
export type SchemaIO = "input" | "output";
|
|
5
|
+
|
|
6
|
+
type ConvertedSchemaObject = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Context passed to an OpenAPI schema converter.
|
|
10
|
+
*/
|
|
11
|
+
export type SchemaConverterContext = {
|
|
12
|
+
/**
|
|
13
|
+
* Whether the schema is documenting request input or response output.
|
|
14
|
+
*/
|
|
15
|
+
io: SchemaIO;
|
|
16
|
+
/**
|
|
17
|
+
* Component name hint Beignet will use when registering the converted schema.
|
|
18
|
+
*/
|
|
19
|
+
nameHint: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Converts a validation schema into an OpenAPI-compatible JSON Schema object.
|
|
24
|
+
*/
|
|
25
|
+
export interface SchemaConverter {
|
|
26
|
+
/**
|
|
27
|
+
* Human-readable converter name used in diagnostics.
|
|
28
|
+
*/
|
|
29
|
+
name: string;
|
|
30
|
+
/**
|
|
31
|
+
* Return true when this converter owns the schema value.
|
|
32
|
+
*/
|
|
33
|
+
canConvert(schema: unknown): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Convert the schema into an OpenAPI-compatible JSON Schema object.
|
|
36
|
+
*/
|
|
37
|
+
toJSONSchema(
|
|
38
|
+
schema: unknown,
|
|
39
|
+
context: SchemaConverterContext,
|
|
40
|
+
): ConvertedSchemaObject;
|
|
41
|
+
}
|
|
42
|
+
|
|
1
43
|
/**
|
|
2
44
|
* Schema introspection adapter.
|
|
3
45
|
*
|
|
@@ -31,6 +73,31 @@ export interface SchemaIntrospector {
|
|
|
31
73
|
unwrapOptional(schema: unknown): unknown;
|
|
32
74
|
}
|
|
33
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Create the default schema converter for Zod schemas.
|
|
78
|
+
*/
|
|
79
|
+
export function createZodSchemaConverter(): SchemaConverter {
|
|
80
|
+
return {
|
|
81
|
+
name: "zod",
|
|
82
|
+
canConvert(schema: unknown): boolean {
|
|
83
|
+
return schema instanceof z.ZodType;
|
|
84
|
+
},
|
|
85
|
+
toJSONSchema(
|
|
86
|
+
schema: unknown,
|
|
87
|
+
context: SchemaConverterContext,
|
|
88
|
+
): ConvertedSchemaObject {
|
|
89
|
+
const jsonSchema = z.toJSONSchema(schema as ZodTypeAny, {
|
|
90
|
+
target: "draft-2020-12",
|
|
91
|
+
unrepresentable: "any",
|
|
92
|
+
io: context.io,
|
|
93
|
+
}) as ConvertedSchemaObject;
|
|
94
|
+
|
|
95
|
+
delete jsonSchema.$schema;
|
|
96
|
+
return jsonSchema;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
34
101
|
/**
|
|
35
102
|
* Create a schema introspector for Zod schemas.
|
|
36
103
|
*
|
|
@@ -41,23 +108,7 @@ export interface SchemaIntrospector {
|
|
|
41
108
|
export function createZodIntrospector(): SchemaIntrospector {
|
|
42
109
|
return {
|
|
43
110
|
getShape(schema: unknown): Record<string, unknown> | undefined {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const schemaDef = (schema as Record<string, unknown>)?._def;
|
|
47
|
-
if (!schemaDef || typeof schemaDef !== "object") return undefined;
|
|
48
|
-
|
|
49
|
-
if (!("shape" in schemaDef)) return undefined;
|
|
50
|
-
|
|
51
|
-
let shape: unknown;
|
|
52
|
-
if (typeof schemaDef.shape === "function") {
|
|
53
|
-
shape = schemaDef.shape();
|
|
54
|
-
} else if (typeof schemaDef.shape === "object") {
|
|
55
|
-
shape = schemaDef.shape;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!shape || typeof shape !== "object") return undefined;
|
|
59
|
-
|
|
60
|
-
return shape as Record<string, unknown>;
|
|
111
|
+
return getObjectSchemaShape(schema);
|
|
61
112
|
},
|
|
62
113
|
|
|
63
114
|
getDescription(schema: unknown): string | undefined {
|
package/src/outbox/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
type EventPayloadDef,
|
|
10
10
|
type InferEventPayload,
|
|
11
11
|
parseEventPayload,
|
|
12
|
-
} from "../events";
|
|
12
|
+
} from "../events/index.js";
|
|
13
13
|
import {
|
|
14
14
|
getJobRetryDelayMs,
|
|
15
15
|
getJobRetryMaxAttempts,
|
|
@@ -17,16 +17,17 @@ import {
|
|
|
17
17
|
type JobDef,
|
|
18
18
|
parseJobPayload,
|
|
19
19
|
shouldRetryJob,
|
|
20
|
-
} from "../jobs";
|
|
21
|
-
import type { JobDispatcherPort } from "../ports/events";
|
|
20
|
+
} from "../jobs/index.js";
|
|
21
|
+
import type { JobDispatcherPort } from "../ports/events.js";
|
|
22
22
|
import type {
|
|
23
23
|
BufferedDomainEventRecorder,
|
|
24
24
|
DomainEventRecorderPort,
|
|
25
|
-
} from "../ports/unit-of-work";
|
|
25
|
+
} from "../ports/unit-of-work.js";
|
|
26
26
|
import {
|
|
27
|
+
type BaseProviderInstrumentationEvent,
|
|
27
28
|
createProviderInstrumentation,
|
|
28
29
|
type ProviderInstrumentationTarget,
|
|
29
|
-
} from "../providers";
|
|
30
|
+
} from "../providers/index.js";
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* Value or promise of that value.
|
|
@@ -365,6 +366,14 @@ export interface DefineOutboxRegistryInput {
|
|
|
365
366
|
jobs?: readonly JobDef[];
|
|
366
367
|
}
|
|
367
368
|
|
|
369
|
+
/**
|
|
370
|
+
* Correlation fields attached to outbox instrumentation events.
|
|
371
|
+
*/
|
|
372
|
+
export type OutboxInstrumentationContext = Pick<
|
|
373
|
+
BaseProviderInstrumentationEvent,
|
|
374
|
+
"requestId" | "traceId" | "spanId" | "parentSpanId" | "traceparent"
|
|
375
|
+
>;
|
|
376
|
+
|
|
368
377
|
/**
|
|
369
378
|
* Options for draining one outbox batch.
|
|
370
379
|
*/
|
|
@@ -413,9 +422,14 @@ export interface DrainOutboxOptions {
|
|
|
413
422
|
now: Date;
|
|
414
423
|
}) => number);
|
|
415
424
|
/**
|
|
416
|
-
* Optional instrumentation target for retry and dead-letter
|
|
425
|
+
* Optional instrumentation target for delivery, retry, and dead-letter
|
|
426
|
+
* visibility.
|
|
417
427
|
*/
|
|
418
428
|
instrumentation?: ProviderInstrumentationTarget;
|
|
429
|
+
/**
|
|
430
|
+
* Optional correlation fields attached to outbox instrumentation events.
|
|
431
|
+
*/
|
|
432
|
+
instrumentationContext?: OutboxInstrumentationContext;
|
|
419
433
|
/**
|
|
420
434
|
* Observer called when delivery fails. Observer failures are ignored so the
|
|
421
435
|
* original delivery failure still controls retry/dead-letter behavior.
|
|
@@ -953,6 +967,20 @@ function shouldRetryOutboxMessage(
|
|
|
953
967
|
});
|
|
954
968
|
}
|
|
955
969
|
|
|
970
|
+
function outboxInstrumentationDetails(
|
|
971
|
+
message: ClaimedOutboxMessage,
|
|
972
|
+
details?: Record<string, unknown>,
|
|
973
|
+
): Record<string, unknown> {
|
|
974
|
+
return {
|
|
975
|
+
attempt: message.attempts,
|
|
976
|
+
maxAttempts: message.maxAttempts,
|
|
977
|
+
messageId: message.id,
|
|
978
|
+
messageKind: message.kind,
|
|
979
|
+
messageName: message.name,
|
|
980
|
+
...details,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
|
|
956
984
|
async function deliverOutboxMessage(
|
|
957
985
|
options: DrainOutboxOptions,
|
|
958
986
|
message: ClaimedOutboxMessage,
|
|
@@ -1007,6 +1035,13 @@ export async function drainOutbox(
|
|
|
1007
1035
|
const batchSize = options.batchSize ?? 100;
|
|
1008
1036
|
assertPositiveInteger("batchSize", batchSize);
|
|
1009
1037
|
const instrumentation = createProviderInstrumentation(
|
|
1038
|
+
options.instrumentation,
|
|
1039
|
+
{
|
|
1040
|
+
providerName: "outbox",
|
|
1041
|
+
watcher: "outbox",
|
|
1042
|
+
},
|
|
1043
|
+
);
|
|
1044
|
+
const jobInstrumentation = createProviderInstrumentation(
|
|
1010
1045
|
options.instrumentation,
|
|
1011
1046
|
{
|
|
1012
1047
|
providerName: "outbox",
|
|
@@ -1035,6 +1070,15 @@ export async function drainOutbox(
|
|
|
1035
1070
|
claimToken: message.claimToken,
|
|
1036
1071
|
now,
|
|
1037
1072
|
});
|
|
1073
|
+
instrumentation.record({
|
|
1074
|
+
type: "outbox",
|
|
1075
|
+
...options.instrumentationContext,
|
|
1076
|
+
messageId: message.id,
|
|
1077
|
+
messageKind: message.kind,
|
|
1078
|
+
messageName: message.name,
|
|
1079
|
+
status: "delivered",
|
|
1080
|
+
details: outboxInstrumentationDetails(message),
|
|
1081
|
+
});
|
|
1038
1082
|
result.delivered += 1;
|
|
1039
1083
|
} catch (error) {
|
|
1040
1084
|
try {
|
|
@@ -1060,34 +1104,55 @@ export async function drainOutbox(
|
|
|
1060
1104
|
});
|
|
1061
1105
|
|
|
1062
1106
|
if (deadLetter) {
|
|
1107
|
+
instrumentation.record({
|
|
1108
|
+
type: "outbox",
|
|
1109
|
+
...options.instrumentationContext,
|
|
1110
|
+
messageId: message.id,
|
|
1111
|
+
messageKind: message.kind,
|
|
1112
|
+
messageName: message.name,
|
|
1113
|
+
status: "deadLettered",
|
|
1114
|
+
details: outboxInstrumentationDetails(message, {
|
|
1115
|
+
error: serializeOutboxError(error),
|
|
1116
|
+
}),
|
|
1117
|
+
});
|
|
1063
1118
|
if (message.kind === "job") {
|
|
1064
|
-
|
|
1119
|
+
jobInstrumentation.record({
|
|
1065
1120
|
type: "job",
|
|
1121
|
+
...options.instrumentationContext,
|
|
1066
1122
|
jobName: message.name,
|
|
1067
1123
|
status: "deadLettered",
|
|
1068
|
-
details: {
|
|
1069
|
-
attempt: message.attempts,
|
|
1070
|
-
maxAttempts: message.maxAttempts,
|
|
1071
|
-
messageId: message.id,
|
|
1124
|
+
details: outboxInstrumentationDetails(message, {
|
|
1072
1125
|
error: serializeOutboxError(error),
|
|
1073
|
-
},
|
|
1126
|
+
}),
|
|
1074
1127
|
});
|
|
1075
1128
|
}
|
|
1076
1129
|
result.deadLettered += 1;
|
|
1077
1130
|
} else {
|
|
1131
|
+
const retryAt = new Date(now.getTime() + retryDelayMs).toISOString();
|
|
1132
|
+
instrumentation.record({
|
|
1133
|
+
type: "outbox",
|
|
1134
|
+
...options.instrumentationContext,
|
|
1135
|
+
messageId: message.id,
|
|
1136
|
+
messageKind: message.kind,
|
|
1137
|
+
messageName: message.name,
|
|
1138
|
+
status: "retryScheduled",
|
|
1139
|
+
details: outboxInstrumentationDetails(message, {
|
|
1140
|
+
retryDelayMs,
|
|
1141
|
+
retryAt,
|
|
1142
|
+
error: serializeOutboxError(error),
|
|
1143
|
+
}),
|
|
1144
|
+
});
|
|
1078
1145
|
if (message.kind === "job") {
|
|
1079
|
-
|
|
1146
|
+
jobInstrumentation.record({
|
|
1080
1147
|
type: "job",
|
|
1148
|
+
...options.instrumentationContext,
|
|
1081
1149
|
jobName: message.name,
|
|
1082
1150
|
status: "retryScheduled",
|
|
1083
|
-
details: {
|
|
1084
|
-
attempt: message.attempts,
|
|
1085
|
-
maxAttempts: message.maxAttempts,
|
|
1086
|
-
messageId: message.id,
|
|
1151
|
+
details: outboxInstrumentationDetails(message, {
|
|
1087
1152
|
retryDelayMs,
|
|
1088
|
-
retryAt
|
|
1153
|
+
retryAt,
|
|
1089
1154
|
error: serializeOutboxError(error),
|
|
1090
|
-
},
|
|
1155
|
+
}),
|
|
1091
1156
|
});
|
|
1092
1157
|
}
|
|
1093
1158
|
result.retried += 1;
|
package/src/ports/audit.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ProviderInstrumentationTarget,
|
|
3
|
+
resolveProviderInstrumentationPort,
|
|
4
|
+
} from "../providers/instrumentation.js";
|
|
5
|
+
import { redactValue } from "./redaction.js";
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
8
|
* The normalized category of actor that caused application activity.
|
|
@@ -111,10 +115,11 @@ export interface ActivityResource {
|
|
|
111
115
|
/**
|
|
112
116
|
* A normalized audit/activity log entry.
|
|
113
117
|
*
|
|
114
|
-
* Application code usually
|
|
115
|
-
* `
|
|
116
|
-
*
|
|
117
|
-
* `AuditLogPort`
|
|
118
|
+
* Application code usually records entries through an audit port wrapped with
|
|
119
|
+
* `createAmbientAuditLog(...)` from `@beignet/core/server`, which fills
|
|
120
|
+
* missing actor, tenant, request ID, and trace ID fields from the ambient
|
|
121
|
+
* request context at record time. Durability depends on the `AuditLogPort`
|
|
122
|
+
* implementation.
|
|
118
123
|
*/
|
|
119
124
|
export interface AuditLogEntry {
|
|
120
125
|
/**
|
|
@@ -165,14 +170,18 @@ export interface AuditLogEntry {
|
|
|
165
170
|
/**
|
|
166
171
|
* Input accepted by `AuditLogPort.record(...)`.
|
|
167
172
|
*
|
|
168
|
-
* `occurredAt
|
|
169
|
-
*
|
|
170
|
-
*
|
|
173
|
+
* `actor`, `occurredAt`, and `outcome` are optional at call sites. Wrappers
|
|
174
|
+
* such as `createAmbientAuditLog(...)` fill a missing actor from the ambient
|
|
175
|
+
* request context. Adapters that store audit entries should call
|
|
176
|
+
* `normalizeAuditLogEntry(...)` before persistence or otherwise apply
|
|
177
|
+
* equivalent defaults; entries without an actor normalize to an anonymous
|
|
178
|
+
* actor.
|
|
171
179
|
*/
|
|
172
180
|
export type AuditLogEntryInput = Omit<
|
|
173
181
|
AuditLogEntry,
|
|
174
|
-
"occurredAt" | "outcome"
|
|
182
|
+
"actor" | "occurredAt" | "outcome"
|
|
175
183
|
> & {
|
|
184
|
+
actor?: ActivityActor;
|
|
176
185
|
occurredAt?: Date;
|
|
177
186
|
outcome?: AuditOutcome;
|
|
178
187
|
};
|
|
@@ -259,7 +268,7 @@ export function createServiceActor(
|
|
|
259
268
|
/**
|
|
260
269
|
* Create a system actor descriptor for framework or app-owned background work.
|
|
261
270
|
*
|
|
262
|
-
* Use this for
|
|
271
|
+
* Use this for schedules, scripts, maintenance jobs, and other work that
|
|
263
272
|
* is not directly caused by a user or external service.
|
|
264
273
|
*
|
|
265
274
|
* @example
|
|
@@ -334,13 +343,15 @@ export function createTenant(
|
|
|
334
343
|
* Fill default audit fields for an input entry.
|
|
335
344
|
*
|
|
336
345
|
* @param entry - Partial audit entry accepted by `AuditLogPort.record(...)`.
|
|
337
|
-
* @returns A complete audit entry with `occurredAt
|
|
346
|
+
* @returns A complete audit entry with `actor`, `occurredAt`, and `outcome`
|
|
347
|
+
* populated. Entries without an actor default to an anonymous actor.
|
|
338
348
|
*/
|
|
339
349
|
export function normalizeAuditLogEntry(
|
|
340
350
|
entry: AuditLogEntryInput,
|
|
341
351
|
): AuditLogEntry {
|
|
342
352
|
return {
|
|
343
353
|
...entry,
|
|
354
|
+
actor: entry.actor ?? createAnonymousActor(),
|
|
344
355
|
occurredAt: entry.occurredAt ?? new Date(),
|
|
345
356
|
outcome: entry.outcome ?? "success",
|
|
346
357
|
};
|
|
@@ -409,6 +420,104 @@ export function createRedactedAuditLog(
|
|
|
409
420
|
};
|
|
410
421
|
}
|
|
411
422
|
|
|
423
|
+
/**
|
|
424
|
+
* Options for wrapping an audit log with instrumentation emission.
|
|
425
|
+
*/
|
|
426
|
+
export interface InstrumentedAuditLogOptions {
|
|
427
|
+
/**
|
|
428
|
+
* Durable audit log to write first.
|
|
429
|
+
*/
|
|
430
|
+
audit: AuditLogPort;
|
|
431
|
+
/**
|
|
432
|
+
* Instrumentation sink, port, or ports object. Pass the app ports object so
|
|
433
|
+
* the sink (`ports.instrumentation`, then `ports.devtools`) is resolved
|
|
434
|
+
* lazily on each write and observes provider startup order.
|
|
435
|
+
*/
|
|
436
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
437
|
+
/**
|
|
438
|
+
* Whether to emit instrumentation events. Defaults to true.
|
|
439
|
+
*/
|
|
440
|
+
emit?: boolean;
|
|
441
|
+
/**
|
|
442
|
+
* Optional app-owned redactor applied after Beignet's audit redaction.
|
|
443
|
+
*/
|
|
444
|
+
redact?: (entry: AuditLogEntry) => AuditLogEntry;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function prepareInstrumentedEntry(
|
|
448
|
+
input: AuditLogEntryInput,
|
|
449
|
+
redact?: (entry: AuditLogEntry) => AuditLogEntry,
|
|
450
|
+
): AuditLogEntry {
|
|
451
|
+
const redacted = redactAuditLogEntry(normalizeAuditLogEntry(input));
|
|
452
|
+
return redact ? redact(redacted) : redacted;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function auditSummary(entry: AuditLogEntry): string {
|
|
456
|
+
const resource = entry.resource?.id
|
|
457
|
+
? `${entry.resource.type}:${entry.resource.id}`
|
|
458
|
+
: entry.resource?.type;
|
|
459
|
+
const outcome = entry.outcome === "failure" ? "failed" : "succeeded";
|
|
460
|
+
return resource
|
|
461
|
+
? `${entry.action} ${outcome} for ${resource}`
|
|
462
|
+
: `${entry.action} ${outcome}`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Wrap an audit log so durable audit writes also appear in instrumentation
|
|
467
|
+
* sinks such as devtools.
|
|
468
|
+
*
|
|
469
|
+
* Instrumentation failures are ignored so audit persistence remains the
|
|
470
|
+
* source of truth.
|
|
471
|
+
*
|
|
472
|
+
* @example
|
|
473
|
+
* ```ts
|
|
474
|
+
* const audit = createInstrumentedAuditLog({
|
|
475
|
+
* audit: createDrizzleAuditLog(db),
|
|
476
|
+
* instrumentation: ports,
|
|
477
|
+
* });
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
export function createInstrumentedAuditLog(
|
|
481
|
+
options: InstrumentedAuditLogOptions,
|
|
482
|
+
): AuditLogPort {
|
|
483
|
+
return {
|
|
484
|
+
async record(input) {
|
|
485
|
+
const entry = prepareInstrumentedEntry(input, options.redact);
|
|
486
|
+
|
|
487
|
+
await options.audit.record(entry);
|
|
488
|
+
|
|
489
|
+
if (options.emit === false) return;
|
|
490
|
+
|
|
491
|
+
const port = resolveProviderInstrumentationPort(options.instrumentation);
|
|
492
|
+
if (!port) return;
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
port.record({
|
|
496
|
+
type: "custom",
|
|
497
|
+
watcher: "audit",
|
|
498
|
+
name: entry.action,
|
|
499
|
+
label: "Audit",
|
|
500
|
+
summary: auditSummary(entry),
|
|
501
|
+
requestId: entry.requestId,
|
|
502
|
+
traceId: entry.traceId,
|
|
503
|
+
details: {
|
|
504
|
+
action: entry.action,
|
|
505
|
+
actor: entry.actor,
|
|
506
|
+
tenant: entry.tenant,
|
|
507
|
+
resource: entry.resource,
|
|
508
|
+
outcome: entry.outcome,
|
|
509
|
+
message: entry.message,
|
|
510
|
+
metadata: entry.metadata,
|
|
511
|
+
occurredAt: entry.occurredAt.toISOString(),
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
} catch {
|
|
515
|
+
// Instrumentation is an observer; durable audit writes must not depend on it.
|
|
516
|
+
}
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
412
521
|
/**
|
|
413
522
|
* Create an in-memory audit log for tests and local examples.
|
|
414
523
|
*
|