@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.
Files changed (243) hide show
  1. package/ChronicleClient.ts +43 -35
  2. package/ChronicleOptions.ts +27 -1
  3. package/EventStore.ts +22 -12
  4. package/Metrics.ts +70 -96
  5. package/Tracing.ts +4 -2
  6. package/WellKnownTelemetryNames.ts +67 -0
  7. package/artifacts/validateArtifactSchemas.spec.ts +1 -1
  8. package/artifacts/withActivatedArtifact.ts +7 -5
  9. package/connection/ChronicleConnection.ts +18 -6
  10. package/connection/CompatibilityPreflight.ts +2 -1
  11. package/connection/KernelKeepAlive.ts +13 -7
  12. package/connection/TokenProvider.ts +3 -1
  13. package/connection/TraceContextMiddleware.ts +30 -0
  14. package/connection/for_ChronicleConnection/when_creating_clients/with_correlated_multi_host_callers.spec.ts +73 -0
  15. package/connection/for_OAuthTokenProvider/when_acquiring_tokens/with_http_instrumentation.spec.ts +102 -0
  16. package/connection/for_TraceContextMiddleware/when_calling/with_active_context.spec.ts +235 -0
  17. package/connection/for_TraceContextMiddleware/when_calling/with_grpc_instrumentation.spec.ts +102 -0
  18. package/correlation/CorrelationIdManager.ts +10 -0
  19. package/dist/ChronicleClient.d.ts.map +1 -1
  20. package/dist/ChronicleClient.js +45 -35
  21. package/dist/ChronicleClient.js.map +1 -1
  22. package/dist/ChronicleOptions.d.ts +8 -0
  23. package/dist/ChronicleOptions.d.ts.map +1 -1
  24. package/dist/ChronicleOptions.js +19 -1
  25. package/dist/ChronicleOptions.js.map +1 -1
  26. package/dist/EventStore.d.ts +3 -1
  27. package/dist/EventStore.d.ts.map +1 -1
  28. package/dist/EventStore.js +22 -12
  29. package/dist/EventStore.js.map +1 -1
  30. package/dist/Metrics.d.ts +23 -64
  31. package/dist/Metrics.d.ts.map +1 -1
  32. package/dist/Metrics.js +62 -92
  33. package/dist/Metrics.js.map +1 -1
  34. package/dist/Tracing.d.ts +1 -1
  35. package/dist/Tracing.d.ts.map +1 -1
  36. package/dist/Tracing.js +4 -2
  37. package/dist/Tracing.js.map +1 -1
  38. package/dist/WellKnownTelemetryNames.d.ts +65 -0
  39. package/dist/WellKnownTelemetryNames.d.ts.map +1 -0
  40. package/dist/WellKnownTelemetryNames.js +67 -0
  41. package/dist/WellKnownTelemetryNames.js.map +1 -0
  42. package/dist/artifacts/withActivatedArtifact.d.ts +2 -1
  43. package/dist/artifacts/withActivatedArtifact.d.ts.map +1 -1
  44. package/dist/artifacts/withActivatedArtifact.js +5 -4
  45. package/dist/artifacts/withActivatedArtifact.js.map +1 -1
  46. package/dist/connection/ChronicleConnection.d.ts +5 -0
  47. package/dist/connection/ChronicleConnection.d.ts.map +1 -1
  48. package/dist/connection/ChronicleConnection.js +13 -6
  49. package/dist/connection/ChronicleConnection.js.map +1 -1
  50. package/dist/connection/CompatibilityPreflight.d.ts.map +1 -1
  51. package/dist/connection/CompatibilityPreflight.js +2 -1
  52. package/dist/connection/CompatibilityPreflight.js.map +1 -1
  53. package/dist/connection/KernelKeepAlive.d.ts +3 -1
  54. package/dist/connection/KernelKeepAlive.d.ts.map +1 -1
  55. package/dist/connection/KernelKeepAlive.js +10 -7
  56. package/dist/connection/KernelKeepAlive.js.map +1 -1
  57. package/dist/connection/TokenProvider.d.ts.map +1 -1
  58. package/dist/connection/TokenProvider.js +3 -1
  59. package/dist/connection/TokenProvider.js.map +1 -1
  60. package/dist/connection/TraceContextMiddleware.d.ts +4 -0
  61. package/dist/connection/TraceContextMiddleware.d.ts.map +1 -0
  62. package/dist/connection/TraceContextMiddleware.js +30 -0
  63. package/dist/connection/TraceContextMiddleware.js.map +1 -0
  64. package/dist/correlation/CorrelationIdManager.d.ts +4 -0
  65. package/dist/correlation/CorrelationIdManager.d.ts.map +1 -1
  66. package/dist/correlation/CorrelationIdManager.js +8 -0
  67. package/dist/correlation/CorrelationIdManager.js.map +1 -1
  68. package/dist/eventSequences/EventLog.d.ts +2 -1
  69. package/dist/eventSequences/EventLog.d.ts.map +1 -1
  70. package/dist/eventSequences/EventLog.js +2 -2
  71. package/dist/eventSequences/EventLog.js.map +1 -1
  72. package/dist/eventSequences/EventSequence.d.ts +4 -1
  73. package/dist/eventSequences/EventSequence.d.ts.map +1 -1
  74. package/dist/eventSequences/EventSequence.js +63 -80
  75. package/dist/eventSequences/EventSequence.js.map +1 -1
  76. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.d.ts.map +1 -1
  77. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js +3 -4
  78. package/dist/eventStoreSubscriptions/EventStoreSubscriptions.js.map +1 -1
  79. package/dist/events/handles.d.ts +12 -0
  80. package/dist/events/handles.d.ts.map +1 -0
  81. package/dist/events/handles.js +33 -0
  82. package/dist/events/handles.js.map +1 -0
  83. package/dist/events/index.d.ts +1 -0
  84. package/dist/events/index.d.ts.map +1 -1
  85. package/dist/events/index.js +1 -0
  86. package/dist/events/index.js.map +1 -1
  87. package/dist/index.d.ts +7 -1
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +4 -1
  90. package/dist/index.js.map +1 -1
  91. package/dist/logging/ChronicleLogEntry.d.ts +11 -0
  92. package/dist/logging/ChronicleLogEntry.d.ts.map +1 -0
  93. package/dist/logging/ChronicleLogEntry.js +4 -0
  94. package/dist/logging/ChronicleLogEntry.js.map +1 -0
  95. package/dist/logging/ChronicleLogLevel.d.ts +9 -0
  96. package/dist/logging/ChronicleLogLevel.d.ts.map +1 -0
  97. package/dist/logging/ChronicleLogLevel.js +12 -0
  98. package/dist/logging/ChronicleLogLevel.js.map +1 -0
  99. package/dist/logging/DiagChronicleLogger.d.ts +7 -0
  100. package/dist/logging/DiagChronicleLogger.d.ts.map +1 -0
  101. package/dist/logging/DiagChronicleLogger.js +10 -0
  102. package/dist/logging/DiagChronicleLogger.js.map +1 -0
  103. package/dist/logging/IChronicleLogger.d.ts +7 -0
  104. package/dist/logging/IChronicleLogger.d.ts.map +1 -0
  105. package/dist/logging/IChronicleLogger.js +4 -0
  106. package/dist/logging/IChronicleLogger.js.map +1 -0
  107. package/dist/logging/createLogger.d.ts +10 -0
  108. package/dist/logging/createLogger.d.ts.map +1 -0
  109. package/dist/logging/createLogger.js +55 -0
  110. package/dist/logging/createLogger.js.map +1 -0
  111. package/dist/observation/EventHandler.d.ts +7 -0
  112. package/dist/observation/EventHandler.d.ts.map +1 -0
  113. package/dist/observation/EventHandler.js +4 -0
  114. package/dist/observation/EventHandler.js.map +1 -0
  115. package/dist/observation/createHandlerInstanceValidator.d.ts +4 -0
  116. package/dist/observation/createHandlerInstanceValidator.d.ts.map +1 -0
  117. package/dist/observation/createHandlerInstanceValidator.js +40 -0
  118. package/dist/observation/createHandlerInstanceValidator.js.map +1 -0
  119. package/dist/observation/getEventHandlers.d.ts +5 -0
  120. package/dist/observation/getEventHandlers.d.ts.map +1 -0
  121. package/dist/observation/getEventHandlers.js +40 -0
  122. package/dist/observation/getEventHandlers.js.map +1 -0
  123. package/dist/observation/getInstanceMethods.d.ts +4 -0
  124. package/dist/observation/getInstanceMethods.d.ts.map +1 -0
  125. package/dist/observation/getInstanceMethods.js +29 -0
  126. package/dist/observation/getInstanceMethods.js.map +1 -0
  127. package/dist/projections/Projections.d.ts.map +1 -1
  128. package/dist/projections/Projections.js +4 -3
  129. package/dist/projections/Projections.js.map +1 -1
  130. package/dist/reactors/ReactorDispatcher.d.ts.map +1 -1
  131. package/dist/reactors/ReactorDispatcher.js +27 -32
  132. package/dist/reactors/ReactorDispatcher.js.map +1 -1
  133. package/dist/reactors/Reactors.d.ts +1 -0
  134. package/dist/reactors/Reactors.d.ts.map +1 -1
  135. package/dist/reactors/Reactors.js +49 -34
  136. package/dist/reactors/Reactors.js.map +1 -1
  137. package/dist/reactors/index.d.ts +1 -0
  138. package/dist/reactors/index.d.ts.map +1 -1
  139. package/dist/reactors/index.js +1 -0
  140. package/dist/reactors/index.js.map +1 -1
  141. package/dist/reducers/ReducerEventDispatcher.d.ts +1 -0
  142. package/dist/reducers/ReducerEventDispatcher.d.ts.map +1 -1
  143. package/dist/reducers/ReducerEventDispatcher.js +5 -17
  144. package/dist/reducers/ReducerEventDispatcher.js.map +1 -1
  145. package/dist/reducers/ReducerEventHandler.d.ts +3 -6
  146. package/dist/reducers/ReducerEventHandler.d.ts.map +1 -1
  147. package/dist/reducers/Reducers.d.ts +1 -0
  148. package/dist/reducers/Reducers.d.ts.map +1 -1
  149. package/dist/reducers/Reducers.js +47 -32
  150. package/dist/reducers/Reducers.js.map +1 -1
  151. package/dist/reducers/index.d.ts +1 -0
  152. package/dist/reducers/index.d.ts.map +1 -1
  153. package/dist/reducers/index.js +1 -0
  154. package/dist/reducers/index.js.map +1 -1
  155. package/dist/seeding/EventSeeding.d.ts.map +1 -1
  156. package/dist/seeding/EventSeeding.js +4 -3
  157. package/dist/seeding/EventSeeding.js.map +1 -1
  158. package/dist/telemetry/ChronicleTelemetryOptions.d.ts +11 -0
  159. package/dist/telemetry/ChronicleTelemetryOptions.d.ts.map +1 -0
  160. package/dist/telemetry/ChronicleTelemetryOptions.js +4 -0
  161. package/dist/telemetry/ChronicleTelemetryOptions.js.map +1 -0
  162. package/dist/telemetry/observeOperation.d.ts +6 -0
  163. package/dist/telemetry/observeOperation.d.ts.map +1 -0
  164. package/dist/telemetry/observeOperation.js +23 -0
  165. package/dist/telemetry/observeOperation.js.map +1 -0
  166. package/dist/telemetry/runInBackgroundContext.d.ts +3 -0
  167. package/dist/telemetry/runInBackgroundContext.d.ts.map +1 -0
  168. package/dist/telemetry/runInBackgroundContext.js +9 -0
  169. package/dist/telemetry/runInBackgroundContext.js.map +1 -0
  170. package/dist/telemetry/spanAttributes.d.ts +14 -0
  171. package/dist/telemetry/spanAttributes.d.ts.map +1 -0
  172. package/dist/telemetry/spanAttributes.js +47 -0
  173. package/dist/telemetry/spanAttributes.js.map +1 -0
  174. package/dist/testing/ReactorScenario.d.ts +1 -0
  175. package/dist/testing/ReactorScenario.d.ts.map +1 -1
  176. package/dist/testing/ReactorScenario.js +9 -1
  177. package/dist/testing/ReactorScenario.js.map +1 -1
  178. package/dist/testing/ReducerReadModelProcessor.d.ts.map +1 -1
  179. package/dist/testing/ReducerReadModelProcessor.js +1 -0
  180. package/dist/testing/ReducerReadModelProcessor.js.map +1 -1
  181. package/dist/tsconfig.tsbuildinfo +1 -1
  182. package/eventSequences/EventLog.ts +4 -2
  183. package/eventSequences/EventSequence.ts +64 -81
  184. package/eventStoreSubscriptions/EventStoreSubscriptions.ts +5 -5
  185. package/events/for_handles/when_decorating_a_method.legacy.spec.ts +91 -0
  186. package/events/for_handles/when_decorating_a_method.standard.spec.ts +102 -0
  187. package/events/handles.ts +37 -0
  188. package/events/index.ts +1 -0
  189. package/for_ChronicleClient/when_registering_artifacts/with_invalid_handler_mappings.spec.ts +142 -0
  190. package/for_ChronicleClient/when_starting_background_work/with_a_correlated_caller.spec.ts +150 -0
  191. package/for_ChronicleOptions/when_configuring_telemetry/with_event_source_identifiers.spec.ts +44 -0
  192. package/index.ts +7 -1
  193. package/logging/ChronicleLogEntry.ts +14 -0
  194. package/logging/ChronicleLogLevel.ts +11 -0
  195. package/logging/DiagChronicleLogger.ts +13 -0
  196. package/logging/IChronicleLogger.ts +10 -0
  197. package/logging/createLogger.ts +50 -0
  198. package/logging/for_ChronicleLogger/when_logging/with_client_owned_sinks.spec.ts +96 -0
  199. package/observation/EventHandler.ts +9 -0
  200. package/observation/createHandlerInstanceValidator.ts +42 -0
  201. package/observation/for_getEventHandlers/when_discovering_handlers.spec.ts +137 -0
  202. package/observation/for_getEventHandlers/when_reobserving_explicit_handlers.spec.ts +73 -0
  203. package/observation/getEventHandlers.ts +43 -0
  204. package/observation/getInstanceMethods.ts +29 -0
  205. package/package.json +8 -2
  206. package/projections/Projections.ts +4 -3
  207. package/reactors/ReactorDispatcher.ts +26 -30
  208. package/reactors/Reactors.ts +55 -37
  209. package/reactors/decorators.standard.spec.ts +8 -0
  210. package/reactors/for_ReactorDispatcher/when_selecting_explicit_handlers.spec.ts +64 -0
  211. package/reactors/for_Reactors/when_activating_a_field_override.spec.ts +62 -0
  212. package/reactors/for_Reactors/when_delivering/with_activation.spec.ts +14 -14
  213. package/reactors/for_Reactors/when_delivering_to_instance_handlers.spec.ts +108 -0
  214. package/reactors/for_Reactors/when_observing_explicit_handlers.legacy.spec.ts +77 -0
  215. package/reactors/for_Reactors/when_registering_explicit_handlers.spec.ts +53 -0
  216. package/reactors/index.ts +1 -0
  217. package/reducers/ReducerEventDispatcher.ts +5 -16
  218. package/reducers/ReducerEventHandler.ts +4 -6
  219. package/reducers/Reducers.ts +50 -35
  220. package/reducers/for_ReducerEventDispatcher/when_invoking_an_explicit_handler.spec.ts +51 -0
  221. package/reducers/for_ReducerEventDispatcher/when_invoking_instance_handlers.spec.ts +69 -0
  222. package/reducers/for_Reducers/when_activating_a_field_override.spec.ts +63 -0
  223. package/reducers/for_Reducers/when_observing_explicit_handlers.legacy.spec.ts +73 -0
  224. package/reducers/for_Reducers/when_registering_explicit_handlers.spec.ts +57 -0
  225. package/reducers/index.ts +1 -0
  226. package/seeding/EventSeeding.ts +6 -4
  227. package/telemetry/ChronicleTelemetryOptions.ts +8 -0
  228. package/telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.ts +36 -0
  229. package/telemetry/for_ChronicleTelemetry/given/an_event_sequence.fixture.ts +29 -0
  230. package/telemetry/for_ChronicleTelemetry/when_appending/with_shared_and_legacy_attributes.spec.ts +58 -0
  231. package/telemetry/for_ChronicleTelemetry/when_appending/without_an_sdk.spec.ts +15 -0
  232. package/telemetry/for_ChronicleTelemetry/when_documenting/with_public_names.spec.ts +15 -0
  233. package/telemetry/for_ChronicleTelemetry/when_recording_failures/with_sensitive_exception_details.spec.ts +34 -0
  234. package/telemetry/for_ChronicleTelemetry/when_recording_identifiers/with_per_client_privacy.spec.ts +92 -0
  235. package/telemetry/for_ChronicleTelemetry/when_recording_metrics/with_legacy_overlap.spec.ts +40 -0
  236. package/telemetry/for_ChronicleTelemetry/when_recording_sequence_numbers/with_integer_precision.spec.ts +23 -0
  237. package/telemetry/observeOperation.ts +25 -0
  238. package/telemetry/runInBackgroundContext.ts +10 -0
  239. package/telemetry/spanAttributes.ts +49 -0
  240. package/testing/ReactorScenario.ts +9 -1
  241. package/testing/ReducerReadModelProcessor.ts +1 -0
  242. package/testing/for_ReactorScenario/when_delivering_to_an_explicit_handler.spec.ts +100 -0
  243. 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 { ChronicleTracer } from '../Tracing.js';
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.append', async span => {
80
- span.setAttribute('chronicle.event_store', this._eventStoreName);
81
- span.setAttribute('chronicle.namespace', this._namespace);
82
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
83
- span.setAttribute('chronicle.event_source_id', eventSourceId);
84
- span.setAttribute('chronicle.event_type_id', eventType.id.value);
85
- span.setAttribute('chronicle.event_type_generation', eventType.generation.value);
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 = Date.now() - startTime;
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.setAttribute('chronicle.sequence_number', result.sequenceNumber.value.toString());
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.append_many', async span => {
185
- span.setAttribute('chronicle.event_store', this._eventStoreName);
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.setAttribute('chronicle.event_source_id', distinctEventSourceIds[0]);
189
+ setEventSourceId(span, distinctEventSourceIds[0], this._telemetry);
190
190
  }
191
- span.setAttribute('chronicle.events_count', eventsForEventSourceIds.length);
192
- const startTime = Date.now();
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 = Date.now() - startTime;
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_tail_sequence_number', async span => {
304
- span.setAttribute('chronicle.event_store', this._eventStoreName);
305
- span.setAttribute('chronicle.namespace', this._namespace);
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.setAttribute('chronicle.sequence_number', result.value.toString());
322
+ setSequenceNumber(span, result.value);
328
323
  span.setStatus({ code: SpanStatusCode.OK });
329
324
  return result;
330
325
  } catch (error) {
331
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.has_events_for', async span => {
349
- span.setAttribute('chronicle.event_store', this._eventStoreName);
350
- span.setAttribute('chronicle.namespace', this._namespace);
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.setAttribute('chronicle.has_events', result);
354
+ setTelemetryAttribute(span, 'hasEvents', result);
363
355
  span.setStatus({ code: SpanStatusCode.OK });
364
356
  return result;
365
357
  } catch (error) {
366
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_for_event_source_id_and_event_types', async span => {
384
- span.setAttribute('chronicle.event_store', this._eventStoreName);
385
- span.setAttribute('chronicle.namespace', this._namespace);
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_from_sequence_number', async span => {
422
- span.setAttribute('chronicle.event_store', this._eventStoreName);
423
- span.setAttribute('chronicle.namespace', this._namespace);
424
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact', async span => {
456
- span.setAttribute('chronicle.event_store', this._eventStoreName);
457
- span.setAttribute('chronicle.namespace', this._namespace);
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact_for_event_source', async span => {
493
- span.setAttribute('chronicle.event_store', this._eventStoreName);
494
- span.setAttribute('chronicle.namespace', this._namespace);
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 ChronicleTracer.startActiveSpan('chronicle.event_sequences.complete_stream', async span => {
542
- span.setAttribute('chronicle.event_store', this._eventStoreName);
543
- span.setAttribute('chronicle.namespace', this._namespace);
544
- span.setAttribute('chronicle.event_sequence_id', this.id.value);
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.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
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 { diag } from '@opentelemetry/api';
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 = diag.createComponentLogger({
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
@@ -3,6 +3,7 @@
3
3
 
4
4
  import '../types/standardDecoratorMetadata.js';
5
5
 
6
+ export { handles } from './handles.js';
6
7
  export { EventType } from './EventType.js';
7
8
  export { EventTypeId } from './EventTypeId.js';
8
9
  export { EventTypeGeneration } from './EventTypeGeneration.js';