@cratis/chronicle 6.33.0 → 6.35.0
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/ChronicleClient.ts +43 -35
- package/ChronicleOptions.ts +27 -1
- package/EventStore.ts +22 -12
- package/Metrics.ts +70 -96
- package/Tracing.ts +4 -2
- package/WellKnownTelemetryNames.ts +67 -0
- package/artifacts/validateArtifactSchemas.spec.ts +1 -1
- package/artifacts/withActivatedArtifact.ts +7 -5
- package/connection/ChronicleConnection.ts +18 -6
- package/connection/CompatibilityPreflight.ts +2 -1
- package/connection/KernelKeepAlive.ts +13 -7
- package/connection/TokenProvider.ts +3 -1
- package/connection/TraceContextMiddleware.ts +30 -0
- package/connection/for_ChronicleConnection/when_creating_clients/with_correlated_multi_host_callers.spec.ts +73 -0
- package/connection/for_OAuthTokenProvider/when_acquiring_tokens/with_http_instrumentation.spec.ts +102 -0
- package/connection/for_TraceContextMiddleware/when_calling/with_active_context.spec.ts +235 -0
- package/connection/for_TraceContextMiddleware/when_calling/with_grpc_instrumentation.spec.ts +102 -0
- package/correlation/CorrelationIdManager.ts +10 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +45 -35
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/ChronicleOptions.d.ts +8 -0
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +19 -1
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/EventStore.d.ts +3 -1
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +22 -12
- package/dist/EventStore.js.map +1 -1
- package/dist/Metrics.d.ts +23 -64
- package/dist/Metrics.d.ts.map +1 -1
- package/dist/Metrics.js +62 -92
- package/dist/Metrics.js.map +1 -1
- package/dist/Tracing.d.ts +1 -1
- package/dist/Tracing.d.ts.map +1 -1
- package/dist/Tracing.js +4 -2
- package/dist/Tracing.js.map +1 -1
- package/dist/WellKnownTelemetryNames.d.ts +65 -0
- package/dist/WellKnownTelemetryNames.d.ts.map +1 -0
- package/dist/WellKnownTelemetryNames.js +67 -0
- package/dist/WellKnownTelemetryNames.js.map +1 -0
- package/dist/artifacts/withActivatedArtifact.d.ts +2 -1
- package/dist/artifacts/withActivatedArtifact.d.ts.map +1 -1
- package/dist/artifacts/withActivatedArtifact.js +5 -4
- package/dist/artifacts/withActivatedArtifact.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +5 -0
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +13 -6
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/CompatibilityPreflight.d.ts.map +1 -1
- package/dist/connection/CompatibilityPreflight.js +2 -1
- package/dist/connection/CompatibilityPreflight.js.map +1 -1
- package/dist/connection/KernelKeepAlive.d.ts +3 -1
- package/dist/connection/KernelKeepAlive.d.ts.map +1 -1
- package/dist/connection/KernelKeepAlive.js +10 -7
- package/dist/connection/KernelKeepAlive.js.map +1 -1
- package/dist/connection/TokenProvider.d.ts.map +1 -1
- package/dist/connection/TokenProvider.js +3 -1
- package/dist/connection/TokenProvider.js.map +1 -1
- package/dist/connection/TraceContextMiddleware.d.ts +4 -0
- package/dist/connection/TraceContextMiddleware.d.ts.map +1 -0
- package/dist/connection/TraceContextMiddleware.js +30 -0
- package/dist/connection/TraceContextMiddleware.js.map +1 -0
- package/dist/correlation/CorrelationIdManager.d.ts +4 -0
- package/dist/correlation/CorrelationIdManager.d.ts.map +1 -1
- package/dist/correlation/CorrelationIdManager.js +8 -0
- package/dist/correlation/CorrelationIdManager.js.map +1 -1
- package/dist/eventSequences/EventLog.d.ts +2 -1
- package/dist/eventSequences/EventLog.d.ts.map +1 -1
- package/dist/eventSequences/EventLog.js +2 -2
- package/dist/eventSequences/EventLog.js.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts +4 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +63 -80
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts.map +1 -1
- package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js +3 -4
- package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js.map +1 -1
- package/dist/events/handles.d.ts +12 -0
- package/dist/events/handles.d.ts.map +1 -0
- package/dist/events/handles.js +33 -0
- package/dist/events/handles.js.map +1 -0
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +1 -0
- package/dist/events/index.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/ChronicleLogEntry.d.ts +11 -0
- package/dist/logging/ChronicleLogEntry.d.ts.map +1 -0
- package/dist/logging/ChronicleLogEntry.js +4 -0
- package/dist/logging/ChronicleLogEntry.js.map +1 -0
- package/dist/logging/ChronicleLogLevel.d.ts +9 -0
- package/dist/logging/ChronicleLogLevel.d.ts.map +1 -0
- package/dist/logging/ChronicleLogLevel.js +12 -0
- package/dist/logging/ChronicleLogLevel.js.map +1 -0
- package/dist/logging/DiagChronicleLogger.d.ts +7 -0
- package/dist/logging/DiagChronicleLogger.d.ts.map +1 -0
- package/dist/logging/DiagChronicleLogger.js +10 -0
- package/dist/logging/DiagChronicleLogger.js.map +1 -0
- package/dist/logging/IChronicleLogger.d.ts +7 -0
- package/dist/logging/IChronicleLogger.d.ts.map +1 -0
- package/dist/logging/IChronicleLogger.js +4 -0
- package/dist/logging/IChronicleLogger.js.map +1 -0
- package/dist/logging/createLogger.d.ts +10 -0
- package/dist/logging/createLogger.d.ts.map +1 -0
- package/dist/logging/createLogger.js +55 -0
- package/dist/logging/createLogger.js.map +1 -0
- package/dist/observation/EventHandler.d.ts +7 -0
- package/dist/observation/EventHandler.d.ts.map +1 -0
- package/dist/observation/EventHandler.js +4 -0
- package/dist/observation/EventHandler.js.map +1 -0
- package/dist/observation/createHandlerInstanceValidator.d.ts +4 -0
- package/dist/observation/createHandlerInstanceValidator.d.ts.map +1 -0
- package/dist/observation/createHandlerInstanceValidator.js +40 -0
- package/dist/observation/createHandlerInstanceValidator.js.map +1 -0
- package/dist/observation/getEventHandlers.d.ts +5 -0
- package/dist/observation/getEventHandlers.d.ts.map +1 -0
- package/dist/observation/getEventHandlers.js +40 -0
- package/dist/observation/getEventHandlers.js.map +1 -0
- package/dist/observation/getInstanceMethods.d.ts +4 -0
- package/dist/observation/getInstanceMethods.d.ts.map +1 -0
- package/dist/observation/getInstanceMethods.js +29 -0
- package/dist/observation/getInstanceMethods.js.map +1 -0
- package/dist/projections/Projections.d.ts.map +1 -1
- package/dist/projections/Projections.js +4 -3
- package/dist/projections/Projections.js.map +1 -1
- package/dist/reactors/ReactorDispatcher.d.ts.map +1 -1
- package/dist/reactors/ReactorDispatcher.js +27 -32
- package/dist/reactors/ReactorDispatcher.js.map +1 -1
- package/dist/reactors/Reactors.d.ts +1 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +49 -34
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/reactors/index.d.ts +1 -0
- package/dist/reactors/index.d.ts.map +1 -1
- package/dist/reactors/index.js +1 -0
- package/dist/reactors/index.js.map +1 -1
- package/dist/reducers/ReducerEventDispatcher.d.ts +1 -0
- package/dist/reducers/ReducerEventDispatcher.d.ts.map +1 -1
- package/dist/reducers/ReducerEventDispatcher.js +5 -17
- package/dist/reducers/ReducerEventDispatcher.js.map +1 -1
- package/dist/reducers/ReducerEventHandler.d.ts +3 -6
- package/dist/reducers/ReducerEventHandler.d.ts.map +1 -1
- package/dist/reducers/Reducers.d.ts +1 -0
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +47 -32
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/reducers/index.d.ts +1 -0
- package/dist/reducers/index.d.ts.map +1 -1
- package/dist/reducers/index.js +1 -0
- package/dist/reducers/index.js.map +1 -1
- package/dist/seeding/EventSeeding.d.ts.map +1 -1
- package/dist/seeding/EventSeeding.js +4 -3
- package/dist/seeding/EventSeeding.js.map +1 -1
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts +11 -0
- package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -0
- package/dist/telemetry/ChronicleTelemetryOptions.js +4 -0
- package/dist/telemetry/ChronicleTelemetryOptions.js.map +1 -0
- package/dist/telemetry/observeOperation.d.ts +6 -0
- package/dist/telemetry/observeOperation.d.ts.map +1 -0
- package/dist/telemetry/observeOperation.js +23 -0
- package/dist/telemetry/observeOperation.js.map +1 -0
- package/dist/telemetry/runInBackgroundContext.d.ts +3 -0
- package/dist/telemetry/runInBackgroundContext.d.ts.map +1 -0
- package/dist/telemetry/runInBackgroundContext.js +9 -0
- package/dist/telemetry/runInBackgroundContext.js.map +1 -0
- package/dist/telemetry/spanAttributes.d.ts +14 -0
- package/dist/telemetry/spanAttributes.d.ts.map +1 -0
- package/dist/telemetry/spanAttributes.js +47 -0
- package/dist/telemetry/spanAttributes.js.map +1 -0
- package/dist/testing/ReactorScenario.d.ts +1 -0
- package/dist/testing/ReactorScenario.d.ts.map +1 -1
- package/dist/testing/ReactorScenario.js +9 -1
- package/dist/testing/ReactorScenario.js.map +1 -1
- package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -1
- package/dist/testing/ReducerReadModelProcessor.js +1 -0
- package/dist/testing/ReducerReadModelProcessor.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/eventSequences/EventLog.ts +4 -2
- package/eventSequences/EventSequence.ts +64 -81
- package/eventStoreSubscriptions/EventStoreSubscriptions.ts +5 -5
- package/events/for_handles/when_decorating_a_method.legacy.spec.ts +91 -0
- package/events/for_handles/when_decorating_a_method.standard.spec.ts +102 -0
- package/events/handles.ts +37 -0
- package/events/index.ts +1 -0
- package/for_ChronicleClient/when_registering_artifacts/with_invalid_handler_mappings.spec.ts +142 -0
- package/for_ChronicleClient/when_starting_background_work/with_a_correlated_caller.spec.ts +150 -0
- package/for_ChronicleOptions/when_configuring_telemetry/with_event_source_identifiers.spec.ts +44 -0
- package/index.ts +7 -1
- package/logging/ChronicleLogEntry.ts +14 -0
- package/logging/ChronicleLogLevel.ts +11 -0
- package/logging/DiagChronicleLogger.ts +13 -0
- package/logging/IChronicleLogger.ts +10 -0
- package/logging/createLogger.ts +50 -0
- package/logging/for_ChronicleLogger/when_logging/with_client_owned_sinks.spec.ts +96 -0
- package/observation/EventHandler.ts +9 -0
- package/observation/createHandlerInstanceValidator.ts +42 -0
- package/observation/for_getEventHandlers/when_discovering_handlers.spec.ts +137 -0
- package/observation/for_getEventHandlers/when_reobserving_explicit_handlers.spec.ts +73 -0
- package/observation/getEventHandlers.ts +43 -0
- package/observation/getInstanceMethods.ts +29 -0
- package/package.json +8 -2
- package/projections/Projections.ts +4 -3
- package/reactors/ReactorDispatcher.ts +26 -30
- package/reactors/Reactors.ts +55 -37
- package/reactors/decorators.standard.spec.ts +8 -0
- package/reactors/for_ReactorDispatcher/when_selecting_explicit_handlers.spec.ts +64 -0
- package/reactors/for_Reactors/when_activating_a_field_override.spec.ts +62 -0
- package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +14 -14
- package/reactors/for_Reactors/when_delivering_to_instance_handlers.spec.ts +108 -0
- package/reactors/for_Reactors/when_observing_explicit_handlers.legacy.spec.ts +77 -0
- package/reactors/for_Reactors/when_registering_explicit_handlers.spec.ts +53 -0
- package/reactors/index.ts +1 -0
- package/reducers/ReducerEventDispatcher.ts +5 -16
- package/reducers/ReducerEventHandler.ts +4 -6
- package/reducers/Reducers.ts +50 -35
- package/reducers/for_ReducerEventDispatcher/when_invoking_an_explicit_handler.spec.ts +51 -0
- package/reducers/for_ReducerEventDispatcher/when_invoking_instance_handlers.spec.ts +69 -0
- package/reducers/for_Reducers/when_activating_a_field_override.spec.ts +63 -0
- package/reducers/for_Reducers/when_observing_explicit_handlers.legacy.spec.ts +73 -0
- package/reducers/for_Reducers/when_registering_explicit_handlers.spec.ts +57 -0
- package/reducers/index.ts +1 -0
- package/seeding/EventSeeding.ts +6 -4
- package/telemetry/ChronicleTelemetryOptions.ts +8 -0
- package/telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.ts +36 -0
- package/telemetry/for_ChronicleTelemetry/given/an_event_sequence.fixture.ts +29 -0
- package/telemetry/for_ChronicleTelemetry/when_appending/with_shared_and_legacy_attributes.spec.ts +58 -0
- package/telemetry/for_ChronicleTelemetry/when_appending/without_an_sdk.spec.ts +15 -0
- package/telemetry/for_ChronicleTelemetry/when_documenting/with_public_names.spec.ts +15 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_failures/with_sensitive_exception_details.spec.ts +34 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +92 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +40 -0
- package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +23 -0
- package/telemetry/observeOperation.ts +25 -0
- package/telemetry/runInBackgroundContext.ts +10 -0
- package/telemetry/spanAttributes.ts +49 -0
- package/testing/ReactorScenario.ts +9 -1
- package/testing/ReducerReadModelProcessor.ts +1 -0
- package/testing/for_ReactorScenario/when_delivering_to_an_explicit_handler.spec.ts +100 -0
- package/testing/for_ReducerReadModelProcessor/when_an_instance_field_hides_a_handler.spec.ts +29 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { ChronicleConnection } from '../connection/index.js';
|
|
5
|
-
import { SpanStatusCode } from '@opentelemetry/api';
|
|
5
|
+
import { SpanStatusCode, type Span } from '@opentelemetry/api';
|
|
6
6
|
import type {
|
|
7
7
|
AppendedEventResponse as ContractsAppendedEvent,
|
|
8
8
|
AppendManyResponse as ContractsAppendManyResponse,
|
|
@@ -37,7 +37,10 @@ import type { WaitForCompletionOptions } from './WaitForCompletionOptions.js';
|
|
|
37
37
|
|
|
38
38
|
/** Default timeout for {@link AppendResult.waitForCompletion}, matching the C# client's default. */
|
|
39
39
|
const DEFAULT_WAIT_FOR_COMPLETION_TIMEOUT_MS = 5000;
|
|
40
|
-
import {
|
|
40
|
+
import { observeOperation } from '../telemetry/observeOperation.js';
|
|
41
|
+
import { setTelemetryAttribute, setEventSourceId, setSequenceNumber, recordSafeException } from '../telemetry/spanAttributes.js';
|
|
42
|
+
import type { ChronicleTelemetryOptions } from '../telemetry/ChronicleTelemetryOptions.js';
|
|
43
|
+
import { WellKnownTelemetryNames as names } from '../WellKnownTelemetryNames.js';
|
|
41
44
|
import { ChronicleMetrics } from '../Metrics.js';
|
|
42
45
|
import { identityProvider, Identity } from '../identity/index.js';
|
|
43
46
|
import { causationManager, CausationType } from '../auditing/index.js';
|
|
@@ -60,7 +63,8 @@ export class EventSequence implements IEventSequence {
|
|
|
60
63
|
private readonly _namespace: string,
|
|
61
64
|
private readonly _connection: ChronicleConnection,
|
|
62
65
|
private readonly _unitOfWorkManager: IUnitOfWorkManager,
|
|
63
|
-
private readonly _resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation
|
|
66
|
+
private readonly _resolveConstraintMessage?: (violation: ConstraintViolation) => ConstraintViolation,
|
|
67
|
+
private readonly _telemetry?: ChronicleTelemetryOptions
|
|
64
68
|
) {
|
|
65
69
|
this.transactional = new TransactionalEventSequence(this, this._unitOfWorkManager);
|
|
66
70
|
}
|
|
@@ -76,14 +80,13 @@ export class EventSequence implements IEventSequence {
|
|
|
76
80
|
'chronicle.event_type_id': eventType.id.value
|
|
77
81
|
};
|
|
78
82
|
|
|
79
|
-
return
|
|
80
|
-
|
|
81
|
-
span
|
|
82
|
-
span
|
|
83
|
-
span
|
|
84
|
-
span.setAttribute(
|
|
85
|
-
|
|
86
|
-
const startTime = Date.now();
|
|
83
|
+
return observeOperation(names.spans.append, async span => {
|
|
84
|
+
this.setSequenceAttributes(span);
|
|
85
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
86
|
+
setTelemetryAttribute(span, 'eventTypeId', eventType.id.value);
|
|
87
|
+
setTelemetryAttribute(span, 'eventTypeGeneration', eventType.generation.value);
|
|
88
|
+
if (options?.sourceType) span.setAttribute(names.attributes.eventSourceType, options.sourceType);
|
|
89
|
+
const startTime = performance.now();
|
|
87
90
|
try {
|
|
88
91
|
const response = await this._connection.eventSequences.append({
|
|
89
92
|
EventStore: this._eventStoreName,
|
|
@@ -113,14 +116,14 @@ export class EventSequence implements IEventSequence {
|
|
|
113
116
|
});
|
|
114
117
|
|
|
115
118
|
const appendResponse = ensureCommandResponse('append event', response);
|
|
116
|
-
const duration =
|
|
119
|
+
const duration = performance.now() - startTime;
|
|
117
120
|
const result = this.mapAppendResponse(
|
|
118
121
|
appendResponse.SequenceNumber,
|
|
119
122
|
appendResponse.ConstraintViolations ?? [],
|
|
120
123
|
appendResponse.Errors ?? [],
|
|
121
124
|
appendResponse.ConcurrencyViolation
|
|
122
125
|
);
|
|
123
|
-
span
|
|
126
|
+
setSequenceNumber(span, result.sequenceNumber.value);
|
|
124
127
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
125
128
|
|
|
126
129
|
ChronicleMetrics.eventsAppended.add(1, metricAttributes);
|
|
@@ -147,8 +150,7 @@ export class EventSequence implements IEventSequence {
|
|
|
147
150
|
|
|
148
151
|
return result;
|
|
149
152
|
} catch (error) {
|
|
150
|
-
span
|
|
151
|
-
span.recordException(error as Error);
|
|
153
|
+
recordSafeException(span, error);
|
|
152
154
|
ChronicleMetrics.appendErrors.add(1, {
|
|
153
155
|
'chronicle.event_store': this._eventStoreName,
|
|
154
156
|
'chronicle.namespace': this._namespace,
|
|
@@ -158,7 +160,7 @@ export class EventSequence implements IEventSequence {
|
|
|
158
160
|
} finally {
|
|
159
161
|
span.end();
|
|
160
162
|
}
|
|
161
|
-
});
|
|
163
|
+
}, correlationId);
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
/** @inheritdoc */
|
|
@@ -181,15 +183,13 @@ export class EventSequence implements IEventSequence {
|
|
|
181
183
|
'chronicle.events_count': eventsForEventSourceIds.length
|
|
182
184
|
};
|
|
183
185
|
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
span.setAttribute('chronicle.namespace', this._namespace);
|
|
187
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
186
|
+
return observeOperation(names.spans.appendMany, async span => {
|
|
187
|
+
this.setSequenceAttributes(span);
|
|
188
188
|
if (distinctEventSourceIds.length === 1) {
|
|
189
|
-
span
|
|
189
|
+
setEventSourceId(span, distinctEventSourceIds[0], this._telemetry);
|
|
190
190
|
}
|
|
191
|
-
span
|
|
192
|
-
const startTime =
|
|
191
|
+
setTelemetryAttribute(span, 'eventCount', eventsForEventSourceIds.length);
|
|
192
|
+
const startTime = performance.now();
|
|
193
193
|
try {
|
|
194
194
|
const response = await this._connection.eventSequences.appendManyForEventSources({
|
|
195
195
|
EventStore: this._eventStoreName,
|
|
@@ -210,7 +210,7 @@ export class EventSequence implements IEventSequence {
|
|
|
210
210
|
});
|
|
211
211
|
|
|
212
212
|
const appendManyResponse = ensureCommandResponse('append many events', response);
|
|
213
|
-
const duration =
|
|
213
|
+
const duration = performance.now() - startTime;
|
|
214
214
|
// Mirrors the C# client: every per-event AppendResult in a batch carries all
|
|
215
215
|
// constraint violations and the first concurrency violation of the whole batch —
|
|
216
216
|
// the wire response doesn't correlate either back to a specific event index.
|
|
@@ -269,8 +269,7 @@ export class EventSequence implements IEventSequence {
|
|
|
269
269
|
|
|
270
270
|
return result;
|
|
271
271
|
} catch (error) {
|
|
272
|
-
span
|
|
273
|
-
span.recordException(error as Error);
|
|
272
|
+
recordSafeException(span, error);
|
|
274
273
|
ChronicleMetrics.appendErrors.add(1, {
|
|
275
274
|
'chronicle.event_store': this._eventStoreName,
|
|
276
275
|
'chronicle.namespace': this._namespace,
|
|
@@ -280,7 +279,7 @@ export class EventSequence implements IEventSequence {
|
|
|
280
279
|
} finally {
|
|
281
280
|
span.end();
|
|
282
281
|
}
|
|
283
|
-
});
|
|
282
|
+
}, correlationId);
|
|
284
283
|
}
|
|
285
284
|
|
|
286
285
|
/** @inheritdoc */
|
|
@@ -300,13 +299,9 @@ export class EventSequence implements IEventSequence {
|
|
|
300
299
|
eventStreamId?: string,
|
|
301
300
|
filterEventTypes?: Constructor[]
|
|
302
301
|
): Promise<EventSequenceNumber> {
|
|
303
|
-
return
|
|
304
|
-
|
|
305
|
-
span
|
|
306
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
307
|
-
if (eventSourceId !== undefined) {
|
|
308
|
-
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
309
|
-
}
|
|
302
|
+
return observeOperation(names.spans.getTailSequenceNumber, async span => {
|
|
303
|
+
this.setSequenceAttributes(span);
|
|
304
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
310
305
|
try {
|
|
311
306
|
const response = await this._connection.eventSequences.tailSequenceNumber({
|
|
312
307
|
EventStore: this._eventStoreName,
|
|
@@ -324,12 +319,11 @@ export class EventSequence implements IEventSequence {
|
|
|
324
319
|
|
|
325
320
|
const data = ensureQuerySuccess('get tail sequence number', response);
|
|
326
321
|
const result = new EventSequenceNumber(data?.SequenceNumber ?? 0n);
|
|
327
|
-
span
|
|
322
|
+
setSequenceNumber(span, result.value);
|
|
328
323
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
329
324
|
return result;
|
|
330
325
|
} catch (error) {
|
|
331
|
-
span
|
|
332
|
-
span.recordException(error as Error);
|
|
326
|
+
recordSafeException(span, error);
|
|
333
327
|
throw error;
|
|
334
328
|
} finally {
|
|
335
329
|
span.end();
|
|
@@ -345,11 +339,9 @@ export class EventSequence implements IEventSequence {
|
|
|
345
339
|
|
|
346
340
|
/** @inheritdoc */
|
|
347
341
|
async hasEventsFor(eventSourceId: string): Promise<boolean> {
|
|
348
|
-
return
|
|
349
|
-
|
|
350
|
-
span
|
|
351
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
352
|
-
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
342
|
+
return observeOperation(names.spans.hasEventsFor, async span => {
|
|
343
|
+
this.setSequenceAttributes(span);
|
|
344
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
353
345
|
try {
|
|
354
346
|
const response = await this._connection.eventSequences.hasEventsForEventSourceId({
|
|
355
347
|
EventStore: this._eventStoreName,
|
|
@@ -359,12 +351,11 @@ export class EventSequence implements IEventSequence {
|
|
|
359
351
|
});
|
|
360
352
|
|
|
361
353
|
const result = ensureQuerySuccess('has events for event source', response)?.HasEvents ?? false;
|
|
362
|
-
span
|
|
354
|
+
setTelemetryAttribute(span, 'hasEvents', result);
|
|
363
355
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
364
356
|
return result;
|
|
365
357
|
} catch (error) {
|
|
366
|
-
span
|
|
367
|
-
span.recordException(error as Error);
|
|
358
|
+
recordSafeException(span, error);
|
|
368
359
|
throw error;
|
|
369
360
|
} finally {
|
|
370
361
|
span.end();
|
|
@@ -380,11 +371,9 @@ export class EventSequence implements IEventSequence {
|
|
|
380
371
|
eventStreamId?: string,
|
|
381
372
|
eventSourceType?: string
|
|
382
373
|
): Promise<AppendedEvent[]> {
|
|
383
|
-
return
|
|
384
|
-
|
|
385
|
-
span
|
|
386
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
387
|
-
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
374
|
+
return observeOperation(names.spans.getForEventSourceIdAndEventTypes, async span => {
|
|
375
|
+
this.setSequenceAttributes(span);
|
|
376
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
388
377
|
try {
|
|
389
378
|
const response = await this._connection.eventSequences.forEventSourceIdAndEventTypes({
|
|
390
379
|
EventStore: this._eventStoreName,
|
|
@@ -403,8 +392,7 @@ export class EventSequence implements IEventSequence {
|
|
|
403
392
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
404
393
|
return result;
|
|
405
394
|
} catch (error) {
|
|
406
|
-
span
|
|
407
|
-
span.recordException(error as Error);
|
|
395
|
+
recordSafeException(span, error);
|
|
408
396
|
throw error;
|
|
409
397
|
} finally {
|
|
410
398
|
span.end();
|
|
@@ -418,11 +406,10 @@ export class EventSequence implements IEventSequence {
|
|
|
418
406
|
eventSourceId?: string,
|
|
419
407
|
filterEventTypes?: Constructor[]
|
|
420
408
|
): Promise<AppendedEvent[]> {
|
|
421
|
-
return
|
|
422
|
-
|
|
423
|
-
span
|
|
424
|
-
span
|
|
425
|
-
span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
|
|
409
|
+
return observeOperation(names.spans.getFromSequenceNumber, async span => {
|
|
410
|
+
this.setSequenceAttributes(span);
|
|
411
|
+
setSequenceNumber(span, sequenceNumber.value);
|
|
412
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
426
413
|
try {
|
|
427
414
|
const response = await this._connection.eventSequences.fromSequenceNumber({
|
|
428
415
|
EventStore: this._eventStoreName,
|
|
@@ -437,8 +424,7 @@ export class EventSequence implements IEventSequence {
|
|
|
437
424
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
438
425
|
return result;
|
|
439
426
|
} catch (error) {
|
|
440
|
-
span
|
|
441
|
-
span.recordException(error as Error);
|
|
427
|
+
recordSafeException(span, error);
|
|
442
428
|
throw error;
|
|
443
429
|
} finally {
|
|
444
430
|
span.end();
|
|
@@ -452,11 +438,9 @@ export class EventSequence implements IEventSequence {
|
|
|
452
438
|
const causationChain = causationManager.getCurrentChain();
|
|
453
439
|
const identity = identityProvider.getCurrent();
|
|
454
440
|
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
span
|
|
458
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
459
|
-
span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
|
|
441
|
+
return observeOperation(names.spans.redact, async span => {
|
|
442
|
+
this.setSequenceAttributes(span);
|
|
443
|
+
setSequenceNumber(span, sequenceNumber.value);
|
|
460
444
|
try {
|
|
461
445
|
ensureCommandSuccess('redact event', await this._connection.eventSequences.redact({
|
|
462
446
|
EventStore: this._eventStoreName,
|
|
@@ -473,8 +457,7 @@ export class EventSequence implements IEventSequence {
|
|
|
473
457
|
}));
|
|
474
458
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
475
459
|
} catch (error) {
|
|
476
|
-
span
|
|
477
|
-
span.recordException(error as Error);
|
|
460
|
+
recordSafeException(span, error);
|
|
478
461
|
throw error;
|
|
479
462
|
} finally {
|
|
480
463
|
span.end();
|
|
@@ -489,11 +472,9 @@ export class EventSequence implements IEventSequence {
|
|
|
489
472
|
const identity = identityProvider.getCurrent();
|
|
490
473
|
const wireEventTypeIds = (eventTypes ?? []).map(constructor => getEventTypeFor(constructor as unknown as Function).id.value);
|
|
491
474
|
|
|
492
|
-
return
|
|
493
|
-
|
|
494
|
-
span
|
|
495
|
-
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
496
|
-
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
475
|
+
return observeOperation(names.spans.redactForEventSource, async span => {
|
|
476
|
+
this.setSequenceAttributes(span);
|
|
477
|
+
setEventSourceId(span, eventSourceId, this._telemetry);
|
|
497
478
|
try {
|
|
498
479
|
ensureCommandSuccess('redact event source', await this._connection.eventSequences.redactForEventSource({
|
|
499
480
|
EventStore: this._eventStoreName,
|
|
@@ -511,8 +492,7 @@ export class EventSequence implements IEventSequence {
|
|
|
511
492
|
}));
|
|
512
493
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
513
494
|
} catch (error) {
|
|
514
|
-
span
|
|
515
|
-
span.recordException(error as Error);
|
|
495
|
+
recordSafeException(span, error);
|
|
516
496
|
throw error;
|
|
517
497
|
} finally {
|
|
518
498
|
span.end();
|
|
@@ -538,12 +518,10 @@ export class EventSequence implements IEventSequence {
|
|
|
538
518
|
|
|
539
519
|
/** @inheritdoc */
|
|
540
520
|
async completeStream(eventStreamType: string, eventStreamId: string): Promise<CompleteStreamResult> {
|
|
541
|
-
return
|
|
542
|
-
|
|
543
|
-
span
|
|
544
|
-
span
|
|
545
|
-
span.setAttribute('chronicle.event_stream_type', eventStreamType);
|
|
546
|
-
span.setAttribute('chronicle.event_stream_id', eventStreamId);
|
|
521
|
+
return observeOperation(names.spans.completeStream, async span => {
|
|
522
|
+
this.setSequenceAttributes(span);
|
|
523
|
+
setTelemetryAttribute(span, 'eventStreamType', eventStreamType);
|
|
524
|
+
setTelemetryAttribute(span, 'eventStreamId', eventStreamId);
|
|
547
525
|
try {
|
|
548
526
|
const response = await this._connection.eventSequences.completeStream({
|
|
549
527
|
EventStore: this._eventStoreName,
|
|
@@ -561,8 +539,7 @@ export class EventSequence implements IEventSequence {
|
|
|
561
539
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
562
540
|
return result;
|
|
563
541
|
} catch (error) {
|
|
564
|
-
span
|
|
565
|
-
span.recordException(error as Error);
|
|
542
|
+
recordSafeException(span, error);
|
|
566
543
|
throw error;
|
|
567
544
|
} finally {
|
|
568
545
|
span.end();
|
|
@@ -570,6 +547,12 @@ export class EventSequence implements IEventSequence {
|
|
|
570
547
|
});
|
|
571
548
|
}
|
|
572
549
|
|
|
550
|
+
private setSequenceAttributes(span: Span): void {
|
|
551
|
+
setTelemetryAttribute(span, 'eventStore', this._eventStoreName);
|
|
552
|
+
setTelemetryAttribute(span, 'namespace', this._namespace);
|
|
553
|
+
setTelemetryAttribute(span, 'eventSequenceId', this.id.value);
|
|
554
|
+
}
|
|
555
|
+
|
|
573
556
|
private toClientCompleteStreamError(error: number): CompleteStreamError {
|
|
574
557
|
// Mirrors the C# client's switch: any wire value other than DefaultStreamCannotBeCompleted
|
|
575
558
|
// (including UNRECOGNIZED) is treated as AlreadyCompleted.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { createLogger } from '../logging/createLogger.js';
|
|
5
5
|
import { ChronicleConnection } from '../connection/index.js';
|
|
6
6
|
import { EventStoreName } from '../EventStoreName.js';
|
|
7
7
|
import { EventType, EventTypeGeneration, EventTypeId, IEventTypes } from '../events/index.js';
|
|
@@ -15,15 +15,15 @@ import { IEventStoreSubscriptions } from './IEventStoreSubscriptions.js';
|
|
|
15
15
|
* Represents an implementation of {@link IEventStoreSubscriptions}.
|
|
16
16
|
*/
|
|
17
17
|
export class EventStoreSubscriptions implements IEventStoreSubscriptions {
|
|
18
|
-
private readonly _logger
|
|
19
|
-
namespace: '@cratis/chronicle/EventStoreSubscriptions'
|
|
20
|
-
});
|
|
18
|
+
private readonly _logger: ReturnType<typeof createLogger>;
|
|
21
19
|
|
|
22
20
|
constructor(
|
|
23
21
|
private readonly _eventTypes: IEventTypes,
|
|
24
22
|
private readonly _targetEventStore: string,
|
|
25
23
|
private readonly _connection: ChronicleConnection
|
|
26
|
-
) {
|
|
24
|
+
) {
|
|
25
|
+
this._logger = createLogger('@cratis/chronicle/EventStoreSubscriptions', _connection.logger);
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
/** @inheritdoc */
|
|
29
29
|
async subscribe(
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import { handles, eventType } from '../../index.js';
|
|
6
|
+
import { getHandledEventType } from '../handles.js';
|
|
7
|
+
import { onceOnly } from '../../reactors/onceOnly.js';
|
|
8
|
+
import { getReactorEventTypes, selectReactorHandler } from '../../reactors/ReactorDispatcher.js';
|
|
9
|
+
import { EventObservationState } from '@cratis/chronicle.contracts';
|
|
10
|
+
|
|
11
|
+
chai.should();
|
|
12
|
+
|
|
13
|
+
@eventType('legacy-explicit-handler')
|
|
14
|
+
class AuthorRegistered {}
|
|
15
|
+
|
|
16
|
+
class NamedReactor {
|
|
17
|
+
@onceOnly()
|
|
18
|
+
@handles(AuthorRegistered)
|
|
19
|
+
notify() { return 'called'; }
|
|
20
|
+
|
|
21
|
+
helper() {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('when decorating a legacy reactor method', () => {
|
|
25
|
+
let handlers: ReturnType<typeof getReactorEventTypes>;
|
|
26
|
+
beforeEach(() => { handlers = getReactorEventTypes(NamedReactor, [AuthorRegistered]); });
|
|
27
|
+
|
|
28
|
+
it('should store the event constructor on the method', () => {
|
|
29
|
+
(getHandledEventType(NamedReactor.prototype.notify) === AuthorRegistered).should.be.true;
|
|
30
|
+
});
|
|
31
|
+
it('should leave an undecorated helper unmarked', () => {
|
|
32
|
+
(getHandledEventType(NamedReactor.prototype.helper) === undefined).should.be.true;
|
|
33
|
+
});
|
|
34
|
+
it('should discover the named handler', () => {
|
|
35
|
+
handlers.should.deep.equal([{ id: 'legacy-explicit-handler', generation: 1, methodName: 'notify' }]);
|
|
36
|
+
});
|
|
37
|
+
it('should keep the original method behavior', () => {
|
|
38
|
+
new NamedReactor().notify().should.equal('called');
|
|
39
|
+
});
|
|
40
|
+
it('should honor onceOnly during replay', () => {
|
|
41
|
+
selectReactorHandler(handlers, NamedReactor, undefined, 'legacy-explicit-handler', EventObservationState.Replay)!
|
|
42
|
+
.skipReplay.should.be.true;
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('when decorating a static legacy method', () => {
|
|
47
|
+
it('should reject a method that cannot receive an instance delivery', () => {
|
|
48
|
+
(() => {
|
|
49
|
+
class Invalid {
|
|
50
|
+
@handles(AuthorRegistered)
|
|
51
|
+
static notify() {}
|
|
52
|
+
}
|
|
53
|
+
return Invalid;
|
|
54
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('when decorating an unsupported legacy member', () => {
|
|
59
|
+
it('should reject a getter without evaluating it', () => {
|
|
60
|
+
(() => {
|
|
61
|
+
class Invalid {
|
|
62
|
+
@handles(AuthorRegistered)
|
|
63
|
+
get notify() { throw new Error('The getter must not run'); }
|
|
64
|
+
}
|
|
65
|
+
return Invalid;
|
|
66
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
67
|
+
});
|
|
68
|
+
it('should reject a function-valued field', () => {
|
|
69
|
+
(() => {
|
|
70
|
+
class Invalid {
|
|
71
|
+
// @ts-expect-error Fields are not event handlers.
|
|
72
|
+
@handles(AuthorRegistered)
|
|
73
|
+
notify = () => {};
|
|
74
|
+
}
|
|
75
|
+
return Invalid;
|
|
76
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('when declaring handles twice on a legacy method', () => {
|
|
81
|
+
it('should reject metadata that would silently replace an event type', () => {
|
|
82
|
+
(() => {
|
|
83
|
+
class Invalid {
|
|
84
|
+
@handles(AuthorRegistered)
|
|
85
|
+
@handles(AuthorRegistered)
|
|
86
|
+
notify() {}
|
|
87
|
+
}
|
|
88
|
+
return Invalid;
|
|
89
|
+
}).should.throw(TypeError, "Handler 'notify' can only declare @handles once.");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import { beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import { handles } from '../../index.js';
|
|
6
|
+
import { getHandledEventType } from '../handles.js';
|
|
7
|
+
import { isOnceOnly, onceOnly } from '../../reactors/onceOnly.js';
|
|
8
|
+
|
|
9
|
+
// Chai installs this property at runtime; the standalone type-check needs its declaration too.
|
|
10
|
+
declare global {
|
|
11
|
+
interface Object { should: Chai.Assertion; }
|
|
12
|
+
}
|
|
13
|
+
chai.should();
|
|
14
|
+
|
|
15
|
+
class AuthorRegistered {}
|
|
16
|
+
class NamedReactor {
|
|
17
|
+
@handles(AuthorRegistered)
|
|
18
|
+
@onceOnly()
|
|
19
|
+
notify() { return 'called'; }
|
|
20
|
+
|
|
21
|
+
@onceOnly()
|
|
22
|
+
@handles(AuthorRegistered)
|
|
23
|
+
otherOrder() {}
|
|
24
|
+
|
|
25
|
+
helper() {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe('when decorating a standard reactor method', () => {
|
|
29
|
+
let eventType: ReturnType<typeof getHandledEventType>;
|
|
30
|
+
beforeEach(() => { eventType = getHandledEventType(NamedReactor.prototype.notify); });
|
|
31
|
+
|
|
32
|
+
it('should store the constructor on the method before instantiation', () => {
|
|
33
|
+
(eventType === AuthorRegistered).should.be.true;
|
|
34
|
+
});
|
|
35
|
+
it('should keep onceOnly metadata in either decorator order', () => {
|
|
36
|
+
isOnceOnly(NamedReactor.prototype.notify).should.be.true;
|
|
37
|
+
isOnceOnly(NamedReactor.prototype.otherOrder).should.be.true;
|
|
38
|
+
});
|
|
39
|
+
it('should not mark another method', () => {
|
|
40
|
+
(getHandledEventType(NamedReactor.prototype.helper) === undefined).should.be.true;
|
|
41
|
+
});
|
|
42
|
+
it('should keep the original method behavior', () => {
|
|
43
|
+
new NamedReactor().notify().should.equal('called');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('when decorating an unsupported standard member', () => {
|
|
48
|
+
it('should reject a static method', () => {
|
|
49
|
+
(() => {
|
|
50
|
+
class Invalid { @handles(AuthorRegistered) static notify() {} }
|
|
51
|
+
return Invalid;
|
|
52
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
53
|
+
});
|
|
54
|
+
it('should reject a private method', () => {
|
|
55
|
+
(() => {
|
|
56
|
+
class Invalid { @handles(AuthorRegistered) #notify() {} }
|
|
57
|
+
return Invalid;
|
|
58
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
59
|
+
});
|
|
60
|
+
it('should reject a symbol method', () => {
|
|
61
|
+
(() => {
|
|
62
|
+
class Invalid { @handles(AuthorRegistered) [Symbol.iterator]() {} }
|
|
63
|
+
return Invalid;
|
|
64
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
65
|
+
});
|
|
66
|
+
it('should reject a getter', () => {
|
|
67
|
+
(() => {
|
|
68
|
+
class Invalid {
|
|
69
|
+
// @ts-expect-error Getters are not event handlers.
|
|
70
|
+
@handles(AuthorRegistered)
|
|
71
|
+
get notify() { return () => {}; }
|
|
72
|
+
}
|
|
73
|
+
return Invalid;
|
|
74
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
75
|
+
});
|
|
76
|
+
it('should reject a function-valued field', () => {
|
|
77
|
+
(() => {
|
|
78
|
+
class Invalid {
|
|
79
|
+
// @ts-expect-error Fields are not event handlers.
|
|
80
|
+
@handles(AuthorRegistered)
|
|
81
|
+
notify = () => {};
|
|
82
|
+
}
|
|
83
|
+
return Invalid;
|
|
84
|
+
}).should.throw(TypeError, 'Handles requires a public, string-named instance method.');
|
|
85
|
+
});
|
|
86
|
+
it('should reject two event declarations on one method', () => {
|
|
87
|
+
(() => {
|
|
88
|
+
class Invalid { @handles(AuthorRegistered) @handles(AuthorRegistered) notify() {} }
|
|
89
|
+
return Invalid;
|
|
90
|
+
}).should.throw(TypeError, "Handler 'notify' can only declare @handles once.");
|
|
91
|
+
});
|
|
92
|
+
it('should reject an absent event constructor', () => {
|
|
93
|
+
(() => {
|
|
94
|
+
class Invalid {
|
|
95
|
+
// @ts-expect-error An event constructor is required.
|
|
96
|
+
@handles(undefined)
|
|
97
|
+
notify() {}
|
|
98
|
+
}
|
|
99
|
+
return Invalid;
|
|
100
|
+
}).should.throw(TypeError, 'Handles requires an event constructor.');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Copyright (c) Cratis. All rights reserved.
|
|
2
|
+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
|
+
|
|
4
|
+
import 'reflect-metadata';
|
|
5
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
6
|
+
|
|
7
|
+
const METADATA_KEY = 'chronicle:events:handles';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Selects the event type handled by a reactor or reducer method instead of its camelCase name.
|
|
11
|
+
* The event type must be registered with the owning event store.
|
|
12
|
+
* @param eventType - The event constructor handled by this method.
|
|
13
|
+
* @returns A method decorator for legacy and standard TypeScript decorators.
|
|
14
|
+
*/
|
|
15
|
+
export function handles(eventType: Constructor): MethodDecorator & ((value: Function, context: ClassMethodDecoratorContext) => void) {
|
|
16
|
+
return (target: object, propertyOrContext: string | symbol | ClassMethodDecoratorContext, descriptor?: PropertyDescriptor) => {
|
|
17
|
+
const isStandard = typeof propertyOrContext === 'object';
|
|
18
|
+
const method = isStandard ? target : descriptor?.value;
|
|
19
|
+
const name = isStandard ? propertyOrContext.name : propertyOrContext;
|
|
20
|
+
if ((isStandard && (propertyOrContext.kind !== 'method' || propertyOrContext.static || propertyOrContext.private)) ||
|
|
21
|
+
(!isStandard && typeof target === 'function') || typeof name !== 'string' || typeof method !== 'function') {
|
|
22
|
+
throw new TypeError('Handles requires a public, string-named instance method.');
|
|
23
|
+
}
|
|
24
|
+
if (typeof eventType !== 'function') {
|
|
25
|
+
throw new TypeError('Handles requires an event constructor.');
|
|
26
|
+
}
|
|
27
|
+
if (Reflect.hasOwnMetadata(METADATA_KEY, method)) {
|
|
28
|
+
throw new TypeError(`Handler '${name}' can only declare @handles once.`);
|
|
29
|
+
}
|
|
30
|
+
Reflect.defineMetadata(METADATA_KEY, eventType, method);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Gets the explicit event constructor for a handler, without inheriting metadata. */
|
|
35
|
+
export function getHandledEventType(method: Function): Constructor | undefined {
|
|
36
|
+
return Reflect.getOwnMetadata(METADATA_KEY, method) as Constructor | undefined;
|
|
37
|
+
}
|
package/events/index.ts
CHANGED