@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
@@ -0,0 +1,137 @@
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 { eventType } from '../../events/eventTypeDecorator.js';
6
+ import { handles } from '../../events/handles.js';
7
+ import { getReactorEventTypes } from '../../reactors/ReactorDispatcher.js';
8
+ import { ReducerEventDispatcher } from '../../reducers/ReducerEventDispatcher.js';
9
+
10
+ chai.should();
11
+
12
+ @eventType('explicit-author', 2)
13
+ class AuthorRegistered {}
14
+ @eventType('conventional-book')
15
+ class BookBorrowed {}
16
+
17
+ const registeredTypes = [AuthorRegistered, BookBorrowed];
18
+ const discoverers = [
19
+ { name: 'reactor', discover: getReactorEventTypes },
20
+ { name: 'reducer', discover: (type: new () => object, types: readonly (new () => object)[]) => new ReducerEventDispatcher(type, types).handlers }
21
+ ];
22
+
23
+ for (const { name, discover } of discoverers) {
24
+ describe(`when discovering explicit and conventional ${name} handlers`, () => {
25
+ class Observer {
26
+ @handles(AuthorRegistered)
27
+ notify() {}
28
+ bookBorrowed() {}
29
+ helper() {}
30
+ get unrelated() { throw new Error('A getter must not run during discovery'); }
31
+ }
32
+ let entries: ReturnType<typeof getReactorEventTypes>;
33
+ beforeEach(() => { entries = discover(Observer, registeredTypes); });
34
+ it('should include both event types without treating helpers as handlers', () => {
35
+ entries.should.deep.equal([
36
+ { id: 'explicit-author', generation: 2, methodName: 'notify' },
37
+ { id: 'conventional-book', generation: 1, methodName: 'bookBorrowed' }
38
+ ]);
39
+ });
40
+ });
41
+
42
+ describe(`when an explicit ${name} method has another event's conventional name`, () => {
43
+ class Observer { @handles(AuthorRegistered) bookBorrowed() {} }
44
+ it('should use only the explicit event type', () => {
45
+ discover(Observer, registeredTypes).should.deep.equal([{ id: 'explicit-author', generation: 2, methodName: 'bookBorrowed' }]);
46
+ });
47
+ });
48
+
49
+ describe(`when an explicit ${name} method has its own conventional name`, () => {
50
+ class Observer { @handles(AuthorRegistered) authorRegistered() {} }
51
+ it('should register it only once', () => {
52
+ discover(Observer, registeredTypes).should.have.lengthOf(1);
53
+ });
54
+ });
55
+
56
+ describe(`when inheriting an explicit ${name} handler`, () => {
57
+ class Base { @handles(AuthorRegistered) notify() {} }
58
+ class Inherited extends Base {}
59
+ class Overridden extends Base { notify() {} }
60
+ class Remapped extends Base { @handles(BookBorrowed) notify() {} }
61
+ it('should discover the inherited handler', () => {
62
+ discover(Inherited, registeredTypes)[0].methodName!.should.equal('notify');
63
+ });
64
+ it('should reject an override that hides the base declaration', () => {
65
+ (() => discover(Overridden, registeredTypes)).should.throw(
66
+ "Override 'notify' on 'Overridden' hides @handles(AuthorRegistered) declared on 'Base'; redecorate the override.");
67
+ });
68
+ it('should reject an inherited undecorated override across multiple levels', () => {
69
+ class Derived extends Overridden {}
70
+ (() => discover(Derived, registeredTypes)).should.throw(
71
+ "Override 'notify' on 'Overridden' hides @handles(AuthorRegistered) declared on 'Base'; redecorate the override.");
72
+ });
73
+ it('should allow an override explicitly keeping the same event type', () => {
74
+ class Reaffirmed extends Base { @handles(AuthorRegistered) notify() {} }
75
+ discover(Reaffirmed, registeredTypes).should.deep.equal([{ id: 'explicit-author', generation: 2, methodName: 'notify' }]);
76
+ });
77
+ it('should use the override event type instead of the base declaration', () => {
78
+ discover(Remapped, registeredTypes).should.deep.equal([{ id: 'conventional-book', generation: 1, methodName: 'notify' }]);
79
+ });
80
+ });
81
+
82
+ describe(`when explicit ${name} event classes share a name`, () => {
83
+ const OtherAuthorRegistered = class AuthorRegistered {};
84
+ eventType('another-author')(OtherAuthorRegistered);
85
+ class Observer {
86
+ @handles(AuthorRegistered) notify() {}
87
+ @handles(OtherAuthorRegistered) archive() {}
88
+ }
89
+ it('should resolve event constructors rather than class names', () => {
90
+ discover(Observer, [AuthorRegistered, OtherAuthorRegistered]).map(entry => entry.id)
91
+ .should.deep.equal(['explicit-author', 'another-author']);
92
+ });
93
+ });
94
+
95
+ describe(`when an explicit ${name} event type is not registered`, () => {
96
+ class Observer { @handles(AuthorRegistered) notify() {} }
97
+ it('should name the observer method and missing event constructor', () => {
98
+ (() => discover(Observer, [BookBorrowed])).should.throw(/notify.*Observer.*AuthorRegistered.*no registered event type/);
99
+ });
100
+ it('should not accept a different constructor with the same event id', () => {
101
+ @eventType('explicit-author', 2)
102
+ class OtherAuthor {}
103
+ (() => discover(Observer, [OtherAuthor])).should.throw(/notify.*no registered event type/);
104
+ });
105
+ it('should not accept a listed class without event metadata', () => {
106
+ class Unknown {}
107
+ class Invalid { @handles(Unknown) notify() {} }
108
+ (() => discover(Invalid, [Unknown])).should.throw(/notify.*Unknown.*no registered event type/);
109
+ });
110
+ });
111
+
112
+ describe(`when two ${name} methods handle one event type`, () => {
113
+ class Explicit {
114
+ @handles(AuthorRegistered) notify() {}
115
+ @handles(AuthorRegistered) archive() {}
116
+ }
117
+ class Mixed {
118
+ @handles(AuthorRegistered) notify() {}
119
+ authorRegistered() {}
120
+ }
121
+ class Base { @handles(AuthorRegistered) notify() {} }
122
+ class Inherited extends Base { @handles(AuthorRegistered) archive() {} }
123
+ class Conventional extends Base { authorRegistered() {} }
124
+ it('should reject two explicit handlers', () => {
125
+ (() => discover(Explicit, registeredTypes)).should.throw(/multiple handlers.*explicit-author.*notify.*archive/);
126
+ });
127
+ it('should reject an explicit handler and a conventional handler', () => {
128
+ (() => discover(Mixed, registeredTypes)).should.throw(/multiple handlers.*explicit-author.*notify.*authorRegistered/);
129
+ });
130
+ it('should include inherited methods in duplicate detection', () => {
131
+ (() => discover(Inherited, registeredTypes)).should.throw(/multiple handlers.*explicit-author.*archive.*notify/);
132
+ });
133
+ it('should reject a conventional handler duplicating an inherited explicit handler', () => {
134
+ (() => discover(Conventional, registeredTypes)).should.throw(/multiple handlers.*explicit-author.*notify.*authorRegistered/);
135
+ });
136
+ });
137
+ }
@@ -0,0 +1,73 @@
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 type { ReactorMessage, ReducerMessage } from '@cratis/chronicle.contracts';
5
+ import { afterEach, beforeEach, chai, describe, it, vi } from 'vitest';
6
+ import type { IClientArtifactsProvider } from '../../artifacts/index.js';
7
+ import type { ChronicleConnection } from '../../connection/index.js';
8
+ import { ConnectionLifecycle } from '../../connection/ConnectionLifecycle.js';
9
+ import type { IEventLog } from '../../eventSequences/IEventLog.js';
10
+ import { eventType } from '../../events/eventTypeDecorator.js';
11
+ import { handles } from '../../events/handles.js';
12
+ import { onceOnly, reactor, Reactors } from '../../reactors/index.js';
13
+ import { reducer, Reducers } from '../../reducers/index.js';
14
+
15
+ chai.should();
16
+
17
+ @eventType('reobserved-event')
18
+ class AuthorRegistered {}
19
+
20
+ @onceOnly()
21
+ @reactor('reobserved-reactor')
22
+ class AuthorReactor { @handles(AuthorRegistered) notify() {} }
23
+
24
+ @reducer('reobserved-reducer')
25
+ class AuthorReducer { @handles(AuthorRegistered) update() {} }
26
+
27
+ for (const kind of ['reactor', 'reducer'] as const) {
28
+ describe(`when reobserving an explicit ${kind} handler`, () => {
29
+ const registrations: (ReactorMessage | ReducerMessage)[] = [];
30
+ let observations: Reactors | Reducers;
31
+ beforeEach(async () => {
32
+ vi.useFakeTimers();
33
+ registrations.length = 0;
34
+ const lifecycle = new ConnectionLifecycle();
35
+ async function* stream(queue: AsyncIterable<ReactorMessage | ReducerMessage>) {
36
+ registrations.push((await queue[Symbol.asyncIterator]().next()).value!);
37
+ yield { Events: [], Partition: 'author', ReplayState: 0, InitialState: '' };
38
+ }
39
+ const connection = {
40
+ reactors: { observe: stream }, reducers: { observe: stream },
41
+ readModels: { registerMany: async () => ({}) }
42
+ } as unknown as ChronicleConnection;
43
+ const artifacts = {
44
+ reactors: [AuthorReactor], reducers: [AuthorReducer], eventTypes: [AuthorRegistered], readModels: []
45
+ } as unknown as IClientArtifactsProvider;
46
+ observations = kind === 'reactor'
47
+ ? new Reactors(artifacts, connection, 'store', 'tenant', lifecycle, {} as IEventLog)
48
+ : new Reducers(artifacts, connection, 'store', 'tenant', lifecycle, 'sink');
49
+ await observations.register();
50
+ await vi.advanceTimersByTimeAsync(0);
51
+ // Changing the catalog must not invalidate an already validated observation on its timer callback.
52
+ artifacts.eventTypes.splice(0);
53
+ await vi.advanceTimersByTimeAsync(2000);
54
+ });
55
+ afterEach(() => {
56
+ observations.dispose();
57
+ vi.clearAllTimers();
58
+ vi.useRealTimers();
59
+ });
60
+ it('should reuse the registered handler mapping when the stream ends', () => {
61
+ registrations.should.have.lengthOf(2);
62
+ registrations[1].should.deep.equal(registrations[0]);
63
+ const registration = registrations[1].Content!.Value0!;
64
+ const observer = 'Reactor' in registration ? registration.Reactor : registration.Reducer;
65
+ observer!.EventTypes.map(entry => entry.EventType!.Id).should.deep.equal(['reobserved-event']);
66
+ });
67
+ if (kind === 'reactor') {
68
+ it('should register a class-level onceOnly reactor with an explicit handler as non-replayable', () => {
69
+ (registrations[0] as ReactorMessage).Content!.Value0!.Reactor!.IsReplayable.should.be.false;
70
+ });
71
+ }
72
+ });
73
+ }
@@ -0,0 +1,43 @@
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 type { Constructor } from '@cratis/fundamentals';
5
+ import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
6
+ import { getHandledEventType } from '../events/handles.js';
7
+ import type { EventHandler } from './EventHandler.js';
8
+ import { getInstanceMethods } from './getInstanceMethods.js';
9
+
10
+ /** Resolves explicit event constructors first, with camelCase discovery for undecorated methods. */
11
+ export function getEventHandlers(type: Constructor, registeredTypes: readonly Constructor[], methods = getInstanceMethods(type)): EventHandler[] {
12
+ const eventTypes = new Map(registeredTypes.flatMap(eventType => {
13
+ const metadata = getEventTypeMetadata(eventType);
14
+ return metadata ? [[eventType, metadata.eventType] as const] : [];
15
+ }));
16
+ const handlers = new Map<string, EventHandler>();
17
+ const addHandler = (eventType: Constructor, methodName: string) => {
18
+ const metadata = eventTypes.get(eventType)!;
19
+ const id = metadata.id.value;
20
+ const previous = handlers.get(id);
21
+ if (previous && previous.methodName !== methodName) {
22
+ throw new Error(`'${(type as Function).name}' has multiple handlers for event type '${id}': '${previous.methodName}' and '${methodName}'.`);
23
+ }
24
+ handlers.set(id, { id, generation: metadata.generation.value, methodName });
25
+ };
26
+
27
+ for (const [name, method] of methods) {
28
+ const eventType = getHandledEventType(method);
29
+ if (eventType === undefined) continue;
30
+ if (!eventTypes.has(eventType)) {
31
+ throw new Error(`Handler '${name}' on '${(type as Function).name}' names event type '${(eventType as Function).name}', which has no registered event type. Register that event constructor with the event store.`);
32
+ }
33
+ addHandler(eventType, name);
34
+ }
35
+
36
+ for (const eventType of eventTypes.keys()) {
37
+ const className = (eventType as Function).name;
38
+ const name = className.charAt(0).toLowerCase() + className.slice(1);
39
+ const method = methods.get(name);
40
+ if (method && getHandledEventType(method) === undefined) addHandler(eventType, name);
41
+ }
42
+ return [...handlers.values()];
43
+ }
@@ -0,0 +1,29 @@
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 type { Constructor } from '@cratis/fundamentals';
5
+ import { getHandledEventType } from '../events/handles.js';
6
+
7
+ /** Finds prototype methods without invoking getters; explicit handlers require explicitly decorated overrides. */
8
+ export function getInstanceMethods(type: Constructor): Map<string, Function> {
9
+ const methods = new Map<string, Function>();
10
+ const declaringTypes = new Map<string, string>();
11
+ for (let current = type.prototype; current && current !== Object.prototype; current = Object.getPrototypeOf(current)) {
12
+ for (const name of Object.getOwnPropertyNames(current)) {
13
+ if (name === 'constructor') continue;
14
+ const method = Object.getOwnPropertyDescriptor(current, name)?.value;
15
+ const declaringType = declaringTypes.get(name);
16
+ if (declaringType !== undefined) {
17
+ const eventType = typeof method === 'function' ? getHandledEventType(method) : undefined;
18
+ const override = methods.get(name);
19
+ if (eventType && (!override || getHandledEventType(override) === undefined)) {
20
+ throw new Error(`Override '${name}' on '${declaringType}' hides @handles(${(eventType as Function).name}) declared on '${current.constructor.name}'; redecorate the override.`);
21
+ }
22
+ continue;
23
+ }
24
+ declaringTypes.set(name, current.constructor.name);
25
+ if (typeof method === 'function') methods.set(name, method);
26
+ }
27
+ }
28
+ return methods;
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/chronicle",
3
- "version": "6.33.0",
3
+ "version": "6.35.0",
4
4
  "description": "Event sourcing for TypeScript and Node.js — the idiomatic client for Cratis Chronicle, the open-source event-sourcing database",
5
5
  "author": "Cratis",
6
6
  "license": "MIT",
@@ -168,7 +168,7 @@
168
168
  "clean": "rm -rf dist",
169
169
  "test": "vitest run",
170
170
  "test:integration": "vitest run --config vitest.integration.config.ts",
171
- "test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts",
171
+ "test:standard-types": "tsc --ignoreConfig --noEmit --target es2022 --module es2022 --moduleResolution bundler --lib es2022,esnext.decorators --types node --skipLibCheck --strict schemas/decorators.standard.spec.ts projections/declarative/addChildCompatibility.standard.spec.ts projections/modelBound/decorators.standard.spec.ts events/classDecorators.standard.spec.ts reactors/decorators.standard.spec.ts readModels/standardFallbacks.standard.spec.ts readModels/registration.standard.spec.ts for_ChronicleClient/when_registering_artifacts/with_standard_property_mappings.standard.spec.ts artifacts/validateArtifactSchemas.spec.ts testing/for_EventScenario/when_appending_concept_fields/with_standard_decorators.standard.spec.ts testing/for_EventScenario/when_appending_scoped_constraints/with_standard_decorators.standard.spec.ts schemas/for_JsonSchemaGenerator/when_generating_children/with_standard_decorators.standard.spec.ts events/for_handles/when_decorating_a_method.standard.spec.ts",
172
172
  "test:native": "node ./scripts/check-native-esm.mjs"
173
173
  },
174
174
  "dependencies": {
@@ -184,6 +184,12 @@
184
184
  },
185
185
  "devDependencies": {
186
186
  "@cratis/fundamentals": "7.20.0",
187
+ "@opentelemetry/context-async-hooks": "2.11.0",
188
+ "@opentelemetry/core": "2.11.0",
189
+ "@opentelemetry/instrumentation-grpc": "0.222.0",
190
+ "@opentelemetry/instrumentation-http": "0.222.0",
191
+ "@opentelemetry/sdk-metrics": "2.11.0",
192
+ "@opentelemetry/sdk-trace-base": "2.11.0",
187
193
  "@types/node": "^26.6.3",
188
194
  "@typescript/native": "npm:typescript@^7.0.2",
189
195
  "esbuild": "^0.28.2",
@@ -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 { ProjectionOwner, type ProjectionDefinition } from '@cratis/chronicle.contracts';
6
6
  import { Constructor, Guid } from '@cratis/fundamentals';
7
7
  import { IClientArtifactsProvider } from '../artifacts/index.js';
@@ -36,7 +36,7 @@ export class Projections implements IProjections {
36
36
  private readonly _modelBound = new Map<string, Constructor>();
37
37
  private readonly _failedPartitions: FailedPartitions;
38
38
 
39
- private readonly _logger = diag.createComponentLogger({ namespace: '@cratis/chronicle/projections' });
39
+ private readonly _logger: ReturnType<typeof createLogger>;
40
40
 
41
41
  /**
42
42
  * Creates a new {@link Projections} instance.
@@ -55,6 +55,7 @@ export class Projections implements IProjections {
55
55
  private readonly _defaultSinkTypeId: string,
56
56
  private readonly _readModelNamingPolicy?: ReadModelNamingPolicy
57
57
  ) {
58
+ this._logger = createLogger('@cratis/chronicle/projections', _connection.logger);
58
59
  this._failedPartitions = new FailedPartitions(_eventStore, _namespace, _connection);
59
60
  }
60
61
 
@@ -296,7 +297,7 @@ export class Projections implements IProjections {
296
297
  identifier,
297
298
  attempt,
298
299
  nextAttemptInMs: delay,
299
- error: msg
300
+ error
300
301
  });
301
302
  await new Promise(resolve => setTimeout(resolve, delay));
302
303
  delay = Math.min(delay * 2, 15000);
@@ -8,6 +8,9 @@ import { ArtifactDelivery } from '../artifacts/ArtifactDelivery.js';
8
8
  import { runActivated } from '../artifacts/withActivatedArtifact.js';
9
9
  import type { EventContext } from '../events/EventContext.js';
10
10
  import { getEventTypeMetadata } from '../events/eventTypeDecorator.js';
11
+ import { getHandledEventType } from '../events/handles.js';
12
+ import { getEventHandlers } from '../observation/getEventHandlers.js';
13
+ import { getInstanceMethods } from '../observation/getInstanceMethods.js';
11
14
  import { isOnceOnly } from './onceOnly.js';
12
15
  import { getReplayEventType } from './replay.js';
13
16
  import type { ReactorServices } from './ReactorServices.js';
@@ -21,8 +24,7 @@ export interface ReactorEventTypeEntry {
21
24
  }
22
25
 
23
26
  export function getReactorEventTypes(reactorType: Constructor, registeredTypes: readonly Constructor[]): ReactorEventTypeEntry[] {
24
- const proto = reactorType.prototype as Record<string, unknown>;
25
- const entries: ReactorEventTypeEntry[] = [];
27
+ const methods = getInstanceMethods(reactorType);
26
28
  const replayHandlers = new Map<string, string>();
27
29
  const eventTypes = new Map<string, { eventTypeClass: Function; id: string; generation: number }>();
28
30
 
@@ -37,40 +39,34 @@ export function getReactorEventTypes(reactorType: Constructor, registeredTypes:
37
39
  }
38
40
  }
39
41
 
40
- // A derived method shadows a base method of the same name, even if it is not marked for replay.
41
- const seenMethods = new Set<string>();
42
- for (let current = proto; current && current !== Object.prototype; current = Object.getPrototypeOf(current) as Record<string, unknown>) {
43
- for (const name of Object.getOwnPropertyNames(current)) {
44
- if (seenMethods.has(name)) continue;
45
- seenMethods.add(name);
46
- const method = current[name];
47
- if (typeof method !== 'function') continue;
48
- const replayEventType = getReplayEventType(method);
49
- if (replayEventType === undefined) continue;
50
-
51
- const eventType = replayEventType === true
52
- ? eventTypes.get(name.startsWith('replay') ? name.slice('replay'.length) : '')
53
- : [...eventTypes.values()].find(candidate => candidate.eventTypeClass === replayEventType);
54
- if (!eventType) {
55
- throw new Error(`Replay handler '${name}' on reactor '${(reactorType as Function).name}' has no registered event type.`);
56
- }
57
- if (replayHandlers.has(eventType.id)) {
58
- throw new Error(`Reactor '${(reactorType as Function).name}' has multiple replay handlers for event type '${eventType.id}': '${replayHandlers.get(eventType.id)}' and '${name}'.`);
59
- }
60
- replayHandlers.set(eventType.id, name);
42
+ for (const [name, method] of methods) {
43
+ const replayEventType = getReplayEventType(method);
44
+ if (replayEventType === undefined) continue;
45
+ if (getHandledEventType(method) !== undefined) {
46
+ throw new Error(`Handler '${name}' on reactor '${(reactorType as Function).name}' cannot combine @handles and @replay. Use separate live and replay methods.`);
47
+ }
48
+ const eventType = replayEventType === true
49
+ ? eventTypes.get(name.startsWith('replay') ? name.slice('replay'.length) : '')
50
+ : [...eventTypes.values()].find(candidate => candidate.eventTypeClass === replayEventType);
51
+ if (!eventType) {
52
+ throw new Error(`Replay handler '${name}' on reactor '${(reactorType as Function).name}' has no registered event type.`);
53
+ }
54
+ if (replayHandlers.has(eventType.id)) {
55
+ throw new Error(`Reactor '${(reactorType as Function).name}' has multiple replay handlers for event type '${eventType.id}': '${replayHandlers.get(eventType.id)}' and '${name}'.`);
61
56
  }
57
+ replayHandlers.set(eventType.id, name);
58
+ methods.delete(name);
62
59
  }
63
60
 
64
- for (const [className, eventType] of eventTypes) {
65
- const methodName = className.charAt(0).toLowerCase() + className.slice(1);
66
- const liveMethod = proto[methodName];
67
- const liveMethodName = typeof liveMethod === 'function' && getReplayEventType(liveMethod) === undefined ? methodName : undefined;
61
+ const entries = new Map<string, ReactorEventTypeEntry>(getEventHandlers(reactorType, registeredTypes, methods)
62
+ .map(handler => [handler.id, handler]));
63
+ for (const eventType of eventTypes.values()) {
68
64
  const replayMethodName = replayHandlers.get(eventType.id);
69
- if (liveMethodName || replayMethodName) {
70
- entries.push({ id: eventType.id, generation: eventType.generation, methodName: liveMethodName, replayMethodName });
65
+ if (replayMethodName) {
66
+ entries.set(eventType.id, { id: eventType.id, generation: eventType.generation, ...entries.get(eventType.id), replayMethodName });
71
67
  }
72
68
  }
73
- return entries;
69
+ return [...entries.values()];
74
70
  }
75
71
 
76
72
  export function selectReactorHandler(entries: readonly ReactorEventTypeEntry[], reactorType: Constructor,