@beignet/core 0.0.31 → 0.0.33
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 +115 -0
- package/README.md +346 -21
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +63 -47
- package/dist/application/index.js.map +1 -1
- package/dist/domain/entity.d.ts +37 -6
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +9 -7
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +4 -4
- package/dist/domain/index.js.map +1 -1
- package/dist/events/index.d.ts +6 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +20 -10
- package/dist/events/index.js.map +1 -1
- package/dist/flags/index.d.ts +5 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +10 -9
- package/dist/flags/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +11 -1
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js +4 -3
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +449 -9
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +472 -12
- package/dist/jobs/index.js.map +1 -1
- package/dist/outbox/index.d.ts +170 -3
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +192 -8
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/index.d.ts +6 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +1 -0
- package/dist/ports/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +4 -0
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/provider.d.ts +10 -2
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +1 -1
- package/dist/schedules/index.d.ts +8 -7
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +47 -16
- package/dist/schedules/index.js.map +1 -1
- package/dist/search/index.d.ts +2 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/index.js.map +1 -1
- 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/hooks/rate-limit.d.ts +26 -13
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +28 -34
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks/security.d.ts +179 -0
- package/dist/server/hooks/security.d.ts.map +1 -0
- package/dist/server/hooks/security.js +225 -0
- package/dist/server/hooks/security.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +22 -6
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +15 -2
- package/dist/server/providers/loadProviderConfig.js.map +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -0
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/runtime-integrity.d.ts +84 -0
- package/dist/server/runtime-integrity.d.ts.map +1 -0
- package/dist/server/runtime-integrity.js +160 -0
- package/dist/server/runtime-integrity.js.map +1 -0
- package/dist/server/server.d.ts +10 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +55 -1
- package/dist/server/server.js.map +1 -1
- package/dist/server/trusted-proxy.d.ts +77 -0
- package/dist/server/trusted-proxy.d.ts.map +1 -0
- package/dist/server/trusted-proxy.js +129 -0
- package/dist/server/trusted-proxy.js.map +1 -0
- package/dist/tasks/index.d.ts +6 -7
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +22 -5
- package/dist/tasks/index.js.map +1 -1
- package/dist/tenancy/index.d.ts +41 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +34 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +12 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/execution.d.ts +14 -0
- package/dist/tracing/execution.d.ts.map +1 -0
- package/dist/tracing/execution.js +17 -0
- package/dist/tracing/execution.js.map +1 -0
- package/dist/tracing/index.d.ts +49 -0
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js +59 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/uploads/client.d.ts.map +1 -1
- package/dist/uploads/client.js +44 -6
- package/dist/uploads/client.js.map +1 -1
- package/dist/uploads/index.d.ts +182 -2
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +468 -76
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +56 -4
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +107 -2
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/app-architecture/SKILL.md +23 -2
- package/src/application/index.ts +88 -56
- package/src/domain/entity.ts +61 -13
- package/src/domain/index.ts +8 -7
- package/src/events/index.ts +27 -14
- package/src/flags/index.ts +23 -19
- package/src/idempotency/index.ts +17 -3
- package/src/jobs/index.ts +1022 -19
- package/src/outbox/index.ts +409 -9
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +17 -0
- package/src/providers/instrumentation.ts +4 -0
- package/src/providers/provider.ts +11 -2
- package/src/schedules/index.ts +60 -26
- package/src/search/index.ts +3 -1
- package/src/server/hooks/index.ts +10 -0
- package/src/server/hooks/rate-limit.ts +52 -46
- package/src/server/hooks/security.ts +503 -0
- package/src/server/index.ts +8 -0
- package/src/server/instrumentation.ts +25 -5
- package/src/server/providers/loadProviderConfig.ts +19 -2
- package/src/server/request-context.ts +7 -0
- package/src/server/runtime-integrity.ts +322 -0
- package/src/server/server.ts +86 -1
- package/src/server/trusted-proxy.ts +249 -0
- package/src/tasks/index.ts +29 -12
- package/src/tenancy/index.ts +55 -0
- package/src/testing/index.ts +19 -3
- package/src/tracing/execution.ts +37 -0
- package/src/tracing/index.ts +137 -0
- package/src/uploads/client.ts +65 -6
- package/src/uploads/index.ts +713 -76
- package/src/webhooks/index.ts +240 -9
- package/src/domain/events.ts +0 -59
package/src/application/index.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
} from "../providers/instrumentation.js";
|
|
6
6
|
import {
|
|
7
7
|
createChildTraceContext,
|
|
8
|
+
resolveTracingPort,
|
|
9
|
+
runWithTracing,
|
|
8
10
|
type TraceContextInput,
|
|
9
11
|
} from "../tracing/index.js";
|
|
10
12
|
|
|
@@ -482,6 +484,7 @@ function getInstrumentationTrace(ctx: unknown): TraceContextInput | undefined {
|
|
|
482
484
|
spanId: context.spanId,
|
|
483
485
|
parentSpanId: context.parentSpanId,
|
|
484
486
|
traceparent: context.traceparent,
|
|
487
|
+
tracestate: context.tracestate,
|
|
485
488
|
};
|
|
486
489
|
}
|
|
487
490
|
|
|
@@ -517,9 +520,9 @@ function startUseCaseRunInstrumentation(args: {
|
|
|
517
520
|
if (!useCasesEnabled && !errorsEnabled) return undefined;
|
|
518
521
|
|
|
519
522
|
const requestId = getInstrumentationRequestId(args.ctx);
|
|
520
|
-
const trace =
|
|
521
|
-
|
|
522
|
-
|
|
523
|
+
const trace =
|
|
524
|
+
resolveTracingPort(ports)?.current() ??
|
|
525
|
+
createChildTraceContext(getInstrumentationTrace(args.ctx) ?? {});
|
|
523
526
|
|
|
524
527
|
const record = (event: ProviderInstrumentationEventInput) => {
|
|
525
528
|
try {
|
|
@@ -542,6 +545,7 @@ function startUseCaseRunInstrumentation(args: {
|
|
|
542
545
|
spanId: trace.spanId,
|
|
543
546
|
parentSpanId: trace.parentSpanId,
|
|
544
547
|
traceparent: trace.traceparent,
|
|
548
|
+
tracestate: trace.tracestate,
|
|
545
549
|
name: args.name,
|
|
546
550
|
kind: args.kind,
|
|
547
551
|
phase,
|
|
@@ -558,6 +562,7 @@ function startUseCaseRunInstrumentation(args: {
|
|
|
558
562
|
spanId: trace.spanId,
|
|
559
563
|
parentSpanId: trace.parentSpanId,
|
|
560
564
|
traceparent: trace.traceparent,
|
|
565
|
+
tracestate: trace.tracestate,
|
|
561
566
|
message: getRunErrorMessage(error),
|
|
562
567
|
stack: error instanceof Error ? error.stack : undefined,
|
|
563
568
|
useCaseName: args.name,
|
|
@@ -725,61 +730,88 @@ class UseCaseBuilder<
|
|
|
725
730
|
: never,
|
|
726
731
|
parseInput: boolean,
|
|
727
732
|
) => {
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
733
|
+
const traceAttributes = {
|
|
734
|
+
"beignet.use_case.name": useCaseName,
|
|
735
|
+
"beignet.use_case.kind": useCaseKind,
|
|
736
|
+
} as const;
|
|
737
|
+
|
|
738
|
+
return await runWithTracing(
|
|
739
|
+
args.ctx,
|
|
740
|
+
{
|
|
741
|
+
name: `beignet.use_case ${useCaseName}`,
|
|
742
|
+
type: "useCase",
|
|
743
|
+
kind: "internal",
|
|
744
|
+
attributes: traceAttributes,
|
|
745
|
+
metricAttributes: traceAttributes,
|
|
746
|
+
},
|
|
747
|
+
async () => {
|
|
748
|
+
const startedAt = Date.now();
|
|
749
|
+
const instrumentation = instrumented
|
|
750
|
+
? startUseCaseRunInstrumentation({
|
|
751
|
+
ctx: args.ctx,
|
|
752
|
+
name: useCaseName,
|
|
753
|
+
kind: useCaseKind,
|
|
754
|
+
})
|
|
755
|
+
: undefined;
|
|
756
|
+
onRun?.({
|
|
732
757
|
name: useCaseName,
|
|
733
758
|
kind: useCaseKind,
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
759
|
+
phase: "start",
|
|
760
|
+
ctx: args.ctx,
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
try {
|
|
764
|
+
const parsedInput =
|
|
765
|
+
parseInput && validation.input
|
|
766
|
+
? await parseSchema(
|
|
767
|
+
inputSchema,
|
|
768
|
+
args.input,
|
|
769
|
+
useCaseName,
|
|
770
|
+
"input",
|
|
771
|
+
)
|
|
772
|
+
: (args.input as SchemaOutput<InputSchema>);
|
|
773
|
+
|
|
774
|
+
const rawResult = await fn({
|
|
775
|
+
ctx: args.ctx,
|
|
776
|
+
input: parsedInput,
|
|
777
|
+
events: eventHelpers,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
const result = validation.output
|
|
781
|
+
? await parseSchema(
|
|
782
|
+
outputSchema,
|
|
783
|
+
rawResult,
|
|
784
|
+
useCaseName,
|
|
785
|
+
"output",
|
|
786
|
+
)
|
|
787
|
+
: (rawResult as SchemaOutput<OutputSchema>);
|
|
788
|
+
|
|
789
|
+
const durationMs = Date.now() - startedAt;
|
|
790
|
+
instrumentation?.end(durationMs);
|
|
791
|
+
onRun?.({
|
|
792
|
+
name: useCaseName,
|
|
793
|
+
kind: useCaseKind,
|
|
794
|
+
phase: "end",
|
|
795
|
+
durationMs,
|
|
796
|
+
ctx: args.ctx,
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
return result;
|
|
800
|
+
} catch (err) {
|
|
801
|
+
const durationMs = Date.now() - startedAt;
|
|
802
|
+
instrumentation?.error(durationMs, err);
|
|
803
|
+
onRun?.({
|
|
804
|
+
name: useCaseName,
|
|
805
|
+
kind: useCaseKind,
|
|
806
|
+
phase: "error",
|
|
807
|
+
durationMs,
|
|
808
|
+
error: err,
|
|
809
|
+
ctx: args.ctx,
|
|
810
|
+
});
|
|
811
|
+
throw err;
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
);
|
|
783
815
|
};
|
|
784
816
|
|
|
785
817
|
type RunArgs = Parameters<typeof execute>[0];
|
package/src/domain/entity.ts
CHANGED
|
@@ -66,9 +66,47 @@ function validateSchemaSync<T>(
|
|
|
66
66
|
// biome-ignore lint/suspicious/noExplicitAny: Base methods type for flexibility
|
|
67
67
|
type AnyMethods = Record<string, any>;
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Base entity instance passed to the `.methods(...)` builder: validated props
|
|
71
|
+
* plus the `with` and `toJSON` helpers, before domain methods attach.
|
|
72
|
+
*
|
|
73
|
+
* `with(...)` is typed as returning the base instance here because the final
|
|
74
|
+
* methods object is still being defined while the builder runs. At runtime
|
|
75
|
+
* the returned instance carries the entity's methods, and `EntityInstance`
|
|
76
|
+
* maps base-instance returns back to the full instance type so consumers keep
|
|
77
|
+
* typed method chaining.
|
|
78
|
+
*/
|
|
79
|
+
export type EntityBaseInstance<Schema extends StandardSchema> =
|
|
80
|
+
InferOutput<Schema> & {
|
|
81
|
+
/**
|
|
82
|
+
* Create a new validated entity instance with patched properties.
|
|
83
|
+
*/
|
|
84
|
+
with(
|
|
85
|
+
patch: Partial<InferOutput<Schema>>,
|
|
86
|
+
): EntityBaseInstance<Schema> | Promise<EntityBaseInstance<Schema>>;
|
|
87
|
+
/**
|
|
88
|
+
* Convert the entity to a plain JSON object for persistence.
|
|
89
|
+
*/
|
|
90
|
+
toJSON(): InferOutput<Schema>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Replace base-instance returns with the full entity instance type, unwrapping
|
|
95
|
+
* promises, so methods that return `self` or `self.with(...)` chain with the
|
|
96
|
+
* entity's methods attached.
|
|
97
|
+
*/
|
|
98
|
+
type WithEntityInstance<R, Instance, Schema extends StandardSchema> =
|
|
99
|
+
R extends Promise<infer P>
|
|
100
|
+
? Promise<WithEntityInstance<P, Instance, Schema>>
|
|
101
|
+
: R extends EntityBaseInstance<Schema>
|
|
102
|
+
? Instance
|
|
103
|
+
: R;
|
|
104
|
+
|
|
69
105
|
/**
|
|
70
106
|
* Entity instance with validated props, attached methods, and immutable update
|
|
71
|
-
* helpers.
|
|
107
|
+
* helpers. Method returns that are typed as the base instance (`self` or
|
|
108
|
+
* `self.with(...)`) are mapped to the full instance type so chaining keeps
|
|
109
|
+
* the entity's methods.
|
|
72
110
|
*/
|
|
73
111
|
export type EntityInstance<
|
|
74
112
|
Name extends string,
|
|
@@ -87,7 +125,13 @@ export type EntityInstance<
|
|
|
87
125
|
* Convert the entity to a plain JSON object for persistence.
|
|
88
126
|
*/
|
|
89
127
|
toJSON(): InferOutput<Schema>;
|
|
90
|
-
} &
|
|
128
|
+
} & {
|
|
129
|
+
[K in keyof Methods]: Methods[K] extends (...args: infer A) => infer R
|
|
130
|
+
? (
|
|
131
|
+
...args: A
|
|
132
|
+
) => WithEntityInstance<R, EntityInstance<Name, Schema, Methods>, Schema>
|
|
133
|
+
: Methods[K];
|
|
134
|
+
};
|
|
91
135
|
|
|
92
136
|
/**
|
|
93
137
|
* Entity definition returned by `defineEntity(...).build()`.
|
|
@@ -135,8 +179,7 @@ class EntityBuilder<
|
|
|
135
179
|
private readonly cfg: {
|
|
136
180
|
name: Name;
|
|
137
181
|
schema?: Schema;
|
|
138
|
-
|
|
139
|
-
methods?: (self: any) => Methods;
|
|
182
|
+
methods?: (self: EntityBaseInstance<Schema>) => Methods;
|
|
140
183
|
},
|
|
141
184
|
) {}
|
|
142
185
|
|
|
@@ -147,17 +190,22 @@ class EntityBuilder<
|
|
|
147
190
|
return new EntityBuilder<Name, S, Methods>({
|
|
148
191
|
name: this.cfg.name,
|
|
149
192
|
schema,
|
|
150
|
-
|
|
193
|
+
// Methods declared before props were typed against the previous schema;
|
|
194
|
+
// canonical builder order is props-then-methods, so this widening only
|
|
195
|
+
// affects the discouraged reverse order.
|
|
196
|
+
methods: this.cfg.methods as unknown as
|
|
197
|
+
| ((self: EntityBaseInstance<S>) => Methods)
|
|
198
|
+
| undefined,
|
|
151
199
|
});
|
|
152
200
|
}
|
|
153
201
|
|
|
154
202
|
/**
|
|
155
203
|
* Define methods to attach to entity instances.
|
|
156
|
-
* The method builder receives the base instance
|
|
204
|
+
* The method builder receives the typed base instance: validated props plus
|
|
205
|
+
* `with` and `toJSON`.
|
|
157
206
|
*/
|
|
158
207
|
methods<M extends AnyMethods>(
|
|
159
|
-
|
|
160
|
-
build: (self: any) => M,
|
|
208
|
+
build: (self: EntityBaseInstance<Schema>) => M,
|
|
161
209
|
): EntityBuilder<Name, Schema, M> {
|
|
162
210
|
return new EntityBuilder<Name, Schema, M>({
|
|
163
211
|
name: this.cfg.name,
|
|
@@ -190,8 +238,7 @@ class EntityBuilder<
|
|
|
190
238
|
// Cast parsed to a plain object for spreading
|
|
191
239
|
const parsedObj = parsed as Record<string, unknown>;
|
|
192
240
|
|
|
193
|
-
|
|
194
|
-
const base: any = {
|
|
241
|
+
const base = {
|
|
195
242
|
...parsedObj,
|
|
196
243
|
// Immutable update - creates a new validated instance
|
|
197
244
|
// Note: Re-validates the merged props for data integrity
|
|
@@ -205,7 +252,7 @@ class EntityBuilder<
|
|
|
205
252
|
toJSON() {
|
|
206
253
|
return parsed;
|
|
207
254
|
},
|
|
208
|
-
}
|
|
255
|
+
} as EntityBaseInstance<Schema>;
|
|
209
256
|
|
|
210
257
|
const methods = methodsBuilder?.(base) ?? {};
|
|
211
258
|
const instance = Object.freeze({
|
|
@@ -262,6 +309,7 @@ class EntityBuilder<
|
|
|
262
309
|
* ```
|
|
263
310
|
*/
|
|
264
311
|
export function defineEntity<Name extends string>(name: Name) {
|
|
265
|
-
|
|
266
|
-
|
|
312
|
+
return new EntityBuilder<Name, StandardSchema, Record<string, never>>({
|
|
313
|
+
name,
|
|
314
|
+
});
|
|
267
315
|
}
|
package/src/domain/index.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @beignet/core/domain
|
|
3
3
|
*
|
|
4
|
-
* Domain modeling helpers for Beignet - value objects
|
|
4
|
+
* Domain modeling helpers for Beignet - value objects and entities.
|
|
5
5
|
*
|
|
6
6
|
* This package provides small, framework-agnostic helpers for domain modeling:
|
|
7
7
|
* - A Value Object builder (defineValueObject)
|
|
8
8
|
* - An Entity/Aggregate builder (defineEntity)
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Domain events are declared with `defineEvent` from `@beignet/core/events`.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
export { defineEntity, type EntityDef, type EntityInstance } from "./entity.js";
|
|
13
13
|
export {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
|
|
14
|
+
defineEntity,
|
|
15
|
+
type EntityBaseInstance,
|
|
16
|
+
type EntityDef,
|
|
17
|
+
type EntityInstance,
|
|
18
|
+
} from "./entity.js";
|
|
18
19
|
export { defineValueObject, type ValueObjectDef } from "./value-object.js";
|
package/src/events/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { runWithResolvedTracingContext } from "../tracing/execution.js";
|
|
3
|
+
import type { TracingPort } from "../tracing/index.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Any Standard Schema compatible validator.
|
|
@@ -160,6 +162,11 @@ export interface RegisterListenersOptions<Ctx> {
|
|
|
160
162
|
* Static listener context or factory evaluated for each delivered event.
|
|
161
163
|
*/
|
|
162
164
|
ctx?: Ctx | (() => MaybePromise<Ctx>);
|
|
165
|
+
/**
|
|
166
|
+
* Runtime tracing port used to start the listener span before a lazy context
|
|
167
|
+
* factory runs.
|
|
168
|
+
*/
|
|
169
|
+
tracing?: TracingPort;
|
|
163
170
|
/**
|
|
164
171
|
* Called when a listener fails. When omitted, listener errors are rethrown to
|
|
165
172
|
* the event bus subscription callback.
|
|
@@ -243,16 +250,6 @@ async function parsePayload<Schema extends StandardSchemaV1>(
|
|
|
243
250
|
throw new Error("Invalid Standard Schema result: missing value");
|
|
244
251
|
}
|
|
245
252
|
|
|
246
|
-
async function resolveCtx<Ctx>(
|
|
247
|
-
ctx: Ctx | (() => MaybePromise<Ctx>) | undefined,
|
|
248
|
-
): Promise<Ctx> {
|
|
249
|
-
if (typeof ctx === "function") {
|
|
250
|
-
return (ctx as () => MaybePromise<Ctx>)();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return ctx as Ctx;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
253
|
/**
|
|
257
254
|
* Define a typed event.
|
|
258
255
|
*
|
|
@@ -326,10 +323,26 @@ export function registerListeners<Ctx>(
|
|
|
326
323
|
eventBus.subscribe(listener.event, async (rawPayload) => {
|
|
327
324
|
try {
|
|
328
325
|
const payload = await parseEventPayload(listener.event, rawPayload);
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
326
|
+
const traceAttributes = {
|
|
327
|
+
"beignet.listener.name": listener.name,
|
|
328
|
+
"beignet.event.name": listener.event.name,
|
|
329
|
+
} as const;
|
|
330
|
+
await runWithResolvedTracingContext({
|
|
331
|
+
tracing: options.tracing,
|
|
332
|
+
ctx: options.ctx as Ctx | (() => MaybePromise<Ctx>),
|
|
333
|
+
operation: {
|
|
334
|
+
name: `beignet.listener ${listener.name}`,
|
|
335
|
+
type: "listener",
|
|
336
|
+
kind: "consumer",
|
|
337
|
+
attributes: traceAttributes,
|
|
338
|
+
metricAttributes: traceAttributes,
|
|
339
|
+
},
|
|
340
|
+
run: (ctx) =>
|
|
341
|
+
listener.handle({
|
|
342
|
+
event: listener.event,
|
|
343
|
+
payload,
|
|
344
|
+
ctx,
|
|
345
|
+
}),
|
|
333
346
|
});
|
|
334
347
|
} catch (error) {
|
|
335
348
|
options.onError?.(error, listener);
|
package/src/flags/index.ts
CHANGED
|
@@ -38,7 +38,7 @@ export type FlagKey = string;
|
|
|
38
38
|
/**
|
|
39
39
|
* Feature flag value kind.
|
|
40
40
|
*/
|
|
41
|
-
export type
|
|
41
|
+
export type FlagValueKind = "boolean" | "string" | "number" | "object";
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Subject used for provider-neutral flag targeting.
|
|
@@ -104,8 +104,9 @@ export type FlagEvaluationContext = {
|
|
|
104
104
|
* Flag definition registered through `defineFlags(...)`.
|
|
105
105
|
*/
|
|
106
106
|
export type FlagDef<TValue extends FlagValue = FlagValue> = {
|
|
107
|
+
kind: "flag";
|
|
107
108
|
key: FlagKey;
|
|
108
|
-
|
|
109
|
+
valueKind: FlagValueKindForValue<TValue>;
|
|
109
110
|
defaultValue: TValue;
|
|
110
111
|
description?: string;
|
|
111
112
|
metadata?: Record<string, unknown>;
|
|
@@ -120,13 +121,14 @@ export type InferFlagValue<TFlag> =
|
|
|
120
121
|
/**
|
|
121
122
|
* Map a value type to its runtime flag kind.
|
|
122
123
|
*/
|
|
123
|
-
export type
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
124
|
+
export type FlagValueKindForValue<TValue extends FlagValue> =
|
|
125
|
+
TValue extends boolean
|
|
126
|
+
? "boolean"
|
|
127
|
+
: TValue extends string
|
|
128
|
+
? "string"
|
|
129
|
+
: TValue extends number
|
|
130
|
+
? "number"
|
|
131
|
+
: "object";
|
|
130
132
|
|
|
131
133
|
/**
|
|
132
134
|
* Options accepted when declaring a flag.
|
|
@@ -184,7 +186,7 @@ export type FlagEvaluationDetails<TValue extends FlagValue = FlagValue> = {
|
|
|
184
186
|
key: string;
|
|
185
187
|
value: TValue;
|
|
186
188
|
defaultValue: TValue;
|
|
187
|
-
|
|
189
|
+
valueKind: FlagValueKindForValue<TValue>;
|
|
188
190
|
reason: FlagEvaluationReason;
|
|
189
191
|
defaulted: boolean;
|
|
190
192
|
variant?: string;
|
|
@@ -320,20 +322,21 @@ export type MemoryFlagsPort = FlagsPort & {
|
|
|
320
322
|
};
|
|
321
323
|
|
|
322
324
|
function createFlag<TValue extends FlagValue>(
|
|
323
|
-
|
|
325
|
+
valueKind: FlagValueKind,
|
|
324
326
|
key: string,
|
|
325
327
|
options: DefineFlagOptions<TValue>,
|
|
326
328
|
): FlagDef<TValue> {
|
|
327
329
|
const defaultValue = options.default;
|
|
328
|
-
if (!
|
|
330
|
+
if (!matchesValueKind(defaultValue, valueKind)) {
|
|
329
331
|
throw new Error(
|
|
330
|
-
`Flag "${key}" default value does not match "${
|
|
332
|
+
`Flag "${key}" default value does not match "${valueKind}" flag value kind.`,
|
|
331
333
|
);
|
|
332
334
|
}
|
|
333
335
|
|
|
334
336
|
return {
|
|
337
|
+
kind: "flag",
|
|
335
338
|
key,
|
|
336
|
-
|
|
339
|
+
valueKind: valueKind as FlagValueKindForValue<TValue>,
|
|
337
340
|
defaultValue,
|
|
338
341
|
description: options.description,
|
|
339
342
|
metadata: options.metadata,
|
|
@@ -454,9 +457,9 @@ export function createMemoryFlags(
|
|
|
454
457
|
...port,
|
|
455
458
|
values,
|
|
456
459
|
set(flag, value) {
|
|
457
|
-
if (!
|
|
460
|
+
if (!matchesValueKind(value, flag.valueKind)) {
|
|
458
461
|
throw new Error(
|
|
459
|
-
`Flag "${flag.key}" value does not match "${flag.
|
|
462
|
+
`Flag "${flag.key}" value does not match "${flag.valueKind}" flag value kind.`,
|
|
460
463
|
);
|
|
461
464
|
}
|
|
462
465
|
values.set(flag.key, value);
|
|
@@ -485,13 +488,14 @@ function createFlagsFromStore(
|
|
|
485
488
|
const values = readValues();
|
|
486
489
|
const stored = values[flag.key];
|
|
487
490
|
const hasStored = Object.hasOwn(values, flag.key);
|
|
488
|
-
const usedStoredValue =
|
|
491
|
+
const usedStoredValue =
|
|
492
|
+
hasStored && matchesValueKind(stored, flag.valueKind);
|
|
489
493
|
const value = usedStoredValue
|
|
490
494
|
? (stored as typeof defaultValue)
|
|
491
495
|
: defaultValue;
|
|
492
496
|
const details: FlagEvaluationDetails<typeof defaultValue> = {
|
|
493
497
|
key: flag.key,
|
|
494
|
-
|
|
498
|
+
valueKind: flag.valueKind as FlagValueKindForValue<typeof defaultValue>,
|
|
495
499
|
value,
|
|
496
500
|
defaultValue,
|
|
497
501
|
reason: usedStoredValue ? "static" : "default",
|
|
@@ -551,7 +555,7 @@ function createFlagsFromStore(
|
|
|
551
555
|
return flags;
|
|
552
556
|
}
|
|
553
557
|
|
|
554
|
-
function
|
|
558
|
+
function matchesValueKind(value: unknown, kind: FlagValueKind): boolean {
|
|
555
559
|
switch (kind) {
|
|
556
560
|
case "boolean":
|
|
557
561
|
return typeof value === "boolean";
|
package/src/idempotency/index.ts
CHANGED
|
@@ -507,13 +507,27 @@ function reservationFromRecord(
|
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Options for `createMemoryIdempotencyStore(...)`.
|
|
512
|
+
*/
|
|
513
|
+
export interface MemoryIdempotencyStoreOptions {
|
|
514
|
+
/**
|
|
515
|
+
* Clock used for reservation and completion timestamps. Defaults to the
|
|
516
|
+
* system clock.
|
|
517
|
+
*/
|
|
518
|
+
now?: () => Date;
|
|
519
|
+
}
|
|
520
|
+
|
|
510
521
|
/**
|
|
511
522
|
* Create an in-memory idempotency store for tests and local examples.
|
|
512
523
|
*
|
|
513
524
|
* The memory store is process-local and not suitable for multi-process
|
|
514
525
|
* production deployments.
|
|
515
526
|
*/
|
|
516
|
-
export function createMemoryIdempotencyStore(
|
|
527
|
+
export function createMemoryIdempotencyStore(
|
|
528
|
+
options: MemoryIdempotencyStoreOptions = {},
|
|
529
|
+
): MemoryIdempotencyStore {
|
|
530
|
+
const storeNow = options.now ?? (() => new Date());
|
|
517
531
|
const records = new Map<string, MemoryRecord>();
|
|
518
532
|
|
|
519
533
|
return {
|
|
@@ -527,7 +541,7 @@ export function createMemoryIdempotencyStore(): MemoryIdempotencyStore {
|
|
|
527
541
|
assertNonEmptyString("fingerprint", input.fingerprint);
|
|
528
542
|
assertTtl(input.ttlSec);
|
|
529
543
|
|
|
530
|
-
const now =
|
|
544
|
+
const now = storeNow();
|
|
531
545
|
const storageKey = createIdempotencyStorageKey(input);
|
|
532
546
|
const existing = records.get(storageKey);
|
|
533
547
|
|
|
@@ -573,7 +587,7 @@ export function createMemoryIdempotencyStore(): MemoryIdempotencyStore {
|
|
|
573
587
|
|
|
574
588
|
existing.status = "completed";
|
|
575
589
|
existing.result = input.result;
|
|
576
|
-
existing.completedAt =
|
|
590
|
+
existing.completedAt = storeNow();
|
|
577
591
|
},
|
|
578
592
|
|
|
579
593
|
async fail(input) {
|