@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
|
@@ -0,0 +1,235 @@
|
|
|
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 { afterEach, beforeEach, chai, describe, it, vi } from 'vitest';
|
|
5
|
+
import { context, propagation, trace } from '@opentelemetry/api';
|
|
6
|
+
import { createServer, ServerError, Status, type ServiceImplementation } from 'nice-grpc';
|
|
7
|
+
import { Metadata } from 'nice-grpc-common';
|
|
8
|
+
import { ConnectionServiceDefinition, EventSequencesDefinition, EventStoresDefinition, ReactorsDefinition } from '@cratis/chronicle.contracts';
|
|
9
|
+
import { ChronicleConnection } from '../../ChronicleConnection.js';
|
|
10
|
+
import { OAuthTokenProvider } from '../../TokenProvider.js';
|
|
11
|
+
import { CorrelationId, correlationIdManager } from '../../../correlation/index.js';
|
|
12
|
+
import { telemetrySession } from '../../../telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.js';
|
|
13
|
+
|
|
14
|
+
const should = chai.should();
|
|
15
|
+
const telemetry = telemetrySession();
|
|
16
|
+
const correlation = 'f5a978af-cd69-4a98-9f1e-7f656cc8ba2a';
|
|
17
|
+
let server: ReturnType<typeof createServer>;
|
|
18
|
+
let connection: ChronicleConnection;
|
|
19
|
+
let received: Metadata[];
|
|
20
|
+
let original: Metadata;
|
|
21
|
+
let traceparent: string;
|
|
22
|
+
let address: string;
|
|
23
|
+
let rejectNext: boolean;
|
|
24
|
+
|
|
25
|
+
beforeEach(async () => {
|
|
26
|
+
received = [];
|
|
27
|
+
rejectNext = false;
|
|
28
|
+
server = createServer().use(async function* (call, callContext) {
|
|
29
|
+
received.push(Metadata(callContext.metadata));
|
|
30
|
+
return yield* call.next(call.request, callContext);
|
|
31
|
+
});
|
|
32
|
+
const unused = async () => { throw new Error('Unexpected RPC'); };
|
|
33
|
+
server.add(ConnectionServiceDefinition, {
|
|
34
|
+
...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
|
|
35
|
+
checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' }),
|
|
36
|
+
connectionKeepAlive: async () => ({})
|
|
37
|
+
} as ServiceImplementation<typeof ConnectionServiceDefinition>);
|
|
38
|
+
server.add(EventStoresDefinition, {
|
|
39
|
+
allEventStores: async () => {
|
|
40
|
+
if (rejectNext) {
|
|
41
|
+
rejectNext = false;
|
|
42
|
+
throw new ServerError(Status.UNAUTHENTICATED, 'Refresh required');
|
|
43
|
+
}
|
|
44
|
+
return { IsAuthorized: true, Data: [] };
|
|
45
|
+
},
|
|
46
|
+
ensureEventStore: unused,
|
|
47
|
+
observeEventStores: async function* () { yield { IsAuthorized: true, Data: [] }; }
|
|
48
|
+
} as ServiceImplementation<typeof EventStoresDefinition>);
|
|
49
|
+
server.add(EventSequencesDefinition, {
|
|
50
|
+
...Object.fromEntries(Object.keys(EventSequencesDefinition.methods).map(name => [name, unused])),
|
|
51
|
+
append: async () => ({ Response: { SequenceNumber: 0n } })
|
|
52
|
+
} as ServiceImplementation<typeof EventSequencesDefinition>);
|
|
53
|
+
server.add(ReactorsDefinition, {
|
|
54
|
+
...Object.fromEntries(Object.keys(ReactorsDefinition.methods).map(name => [name, unused])),
|
|
55
|
+
observe: async function* (requests) {
|
|
56
|
+
for await (const _request of requests) {
|
|
57
|
+
yield { Events: [], Partition: '', ReplayState: 0 };
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} as ServiceImplementation<typeof ReactorsDefinition>);
|
|
62
|
+
const port = await server.listen('127.0.0.1:0');
|
|
63
|
+
address = `chronicle://127.0.0.1:${port}?disableTls=true`;
|
|
64
|
+
connection = new ChronicleConnection({ connectionString: `${address}&apiKey=api-secret` });
|
|
65
|
+
original = Metadata({ authorization: 'Bearer caller-token', 'x-custom': 'preserved',
|
|
66
|
+
'custom-bin': new Uint8Array([1, 2, 3]), baggage: 'email=private', traceparent: 'stale' });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
connection?.dispose();
|
|
71
|
+
await server?.shutdown();
|
|
72
|
+
vi.restoreAllMocks();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
async function inContext(action: (parent: string) => Promise<void>, correlationValue = correlation) {
|
|
76
|
+
const span = telemetry.traces.getTracer('host').startSpan('host');
|
|
77
|
+
const spanContext = span.spanContext();
|
|
78
|
+
const parent = `00-${spanContext.traceId}-${spanContext.spanId}-01`;
|
|
79
|
+
traceparent = parent;
|
|
80
|
+
const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
|
|
81
|
+
'cratis.correlation_id': { value: correlationValue }, email: { value: 'private@example.com' },
|
|
82
|
+
'cratis.event_source.id': { value: 'private-source' }, 'cratis.tenant.id': { value: 'private-tenant' }
|
|
83
|
+
}));
|
|
84
|
+
try {
|
|
85
|
+
await context.with(active, async () => {
|
|
86
|
+
await Promise.resolve();
|
|
87
|
+
await action(parent);
|
|
88
|
+
propagation.getBaggage(context.active())!.getEntry('email')!.value.should.equal('private@example.com');
|
|
89
|
+
});
|
|
90
|
+
} finally { span.end(); }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe('when calling unary RPCs with active trace context and sensitive baggage', () => {
|
|
94
|
+
beforeEach(async () => {
|
|
95
|
+
await inContext(async () => {
|
|
96
|
+
await connection.connect();
|
|
97
|
+
await connection.eventStores.allEventStores({}, { metadata: original });
|
|
98
|
+
await connection.connections.connectionKeepAlive({ ConnectionId: 'test' });
|
|
99
|
+
await connection.reconnect();
|
|
100
|
+
await connection.eventStores.allEventStores({}, { metadata: original });
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
it('should detach connect and reconnect compatibility checks from caller context', () => {
|
|
104
|
+
received.should.have.lengthOf(5);
|
|
105
|
+
for (const index of [0, 3]) {
|
|
106
|
+
should.equal(received[index].get('traceparent'), undefined);
|
|
107
|
+
should.equal(received[index].get('baggage'), undefined);
|
|
108
|
+
should.equal(received[index].get('api-key'), 'api-secret');
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
it('should inject trace context and only correlation baggage on business calls and keep-alive', () => {
|
|
112
|
+
for (const index of [1, 2, 4]) {
|
|
113
|
+
should.equal(received[index].get('traceparent'), traceparent);
|
|
114
|
+
should.equal(received[index].get('baggage'), `cratis.correlation_id=${correlation}`);
|
|
115
|
+
should.equal(received[index].get('api-key'), 'api-secret');
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
it('should preserve authentication and unrelated string and binary metadata', () => {
|
|
119
|
+
const metadata = received[1];
|
|
120
|
+
should.equal(metadata.get('authorization'), 'Bearer caller-token');
|
|
121
|
+
should.equal(metadata.get('x-custom'), 'preserved');
|
|
122
|
+
Array.from(metadata.get('custom-bin')!).should.deep.equal([1, 2, 3]);
|
|
123
|
+
});
|
|
124
|
+
it('should not mutate the caller carrier', () => {
|
|
125
|
+
should.equal(original.get('traceparent'), 'stale');
|
|
126
|
+
should.equal(original.get('baggage'), 'email=private');
|
|
127
|
+
should.equal(original.get('api-key'), undefined);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('when calling an event sequence without a cached compatibility verdict', () => {
|
|
132
|
+
beforeEach(async () => {
|
|
133
|
+
await connection.resetChannel();
|
|
134
|
+
await correlationIdManager.run(new CorrelationId(correlation), () => inContext(async () => {
|
|
135
|
+
await connection.eventSequences.append({}, { metadata: original });
|
|
136
|
+
correlationIdManager.scoped!.toString().should.equal(correlation);
|
|
137
|
+
}));
|
|
138
|
+
});
|
|
139
|
+
it('should detach the shared compatibility check while preserving the business caller context', () => {
|
|
140
|
+
received.should.have.lengthOf(2);
|
|
141
|
+
should.equal(received[0].get('traceparent'), undefined);
|
|
142
|
+
should.equal(received[0].get('baggage'), undefined);
|
|
143
|
+
should.equal(received[1].get('traceparent'), traceparent);
|
|
144
|
+
should.equal(received[1].get('baggage'), `cratis.correlation_id=${correlation}`);
|
|
145
|
+
should.equal(received[1].get('authorization'), 'Bearer caller-token');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('when opening server and bidirectional streams', () => {
|
|
150
|
+
beforeEach(async () => {
|
|
151
|
+
await connection.connect();
|
|
152
|
+
received.length = 0;
|
|
153
|
+
await inContext(async () => {
|
|
154
|
+
for await (const _response of connection.eventStores.observeEventStores({}, { metadata: original })) { break; }
|
|
155
|
+
async function* requests() { yield { Content: undefined }; }
|
|
156
|
+
for await (const _response of connection.reactors.observe(requests(), { metadata: original })) { break; }
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
it('should propagate the stream-opening context and preserve authentication', () => {
|
|
160
|
+
received.should.have.lengthOf(2);
|
|
161
|
+
for (const metadata of received) {
|
|
162
|
+
should.equal(metadata.get('traceparent'), traceparent);
|
|
163
|
+
should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
|
|
164
|
+
should.equal(metadata.get('authorization'), 'Bearer caller-token');
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('when authentication refresh retries a unary call', () => {
|
|
170
|
+
beforeEach(async () => {
|
|
171
|
+
connection.dispose();
|
|
172
|
+
vi.spyOn(OAuthTokenProvider.prototype, 'getAccessToken').mockResolvedValue('old-token');
|
|
173
|
+
vi.spyOn(OAuthTokenProvider.prototype, 'refresh').mockResolvedValue('refreshed-token');
|
|
174
|
+
connection = new ChronicleConnection({ connectionString: address });
|
|
175
|
+
await connection.connect();
|
|
176
|
+
received.length = 0;
|
|
177
|
+
rejectNext = true;
|
|
178
|
+
await inContext(async () => {
|
|
179
|
+
await connection.eventStores.allEventStores({}, { metadata: original });
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
it('should keep trace context and filtered baggage on both attempts with the refreshed authorization', () => {
|
|
183
|
+
received.should.have.lengthOf(2);
|
|
184
|
+
for (const metadata of received) {
|
|
185
|
+
should.equal(metadata.get('traceparent'), traceparent);
|
|
186
|
+
should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
|
|
187
|
+
}
|
|
188
|
+
should.equal(received[0].get('authorization'), 'Bearer old-token');
|
|
189
|
+
should.equal(received[1].get('authorization'), 'Bearer refreshed-token');
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('when callers in two contexts use the same connection concurrently', () => {
|
|
194
|
+
const expected = new Map<string, { parent: string; correlationId: string }>();
|
|
195
|
+
beforeEach(async () => {
|
|
196
|
+
await connection.connect();
|
|
197
|
+
received.length = 0;
|
|
198
|
+
expected.clear();
|
|
199
|
+
const correlations = [correlation, '7144abf3-94bd-4429-93e5-3faf23f0bad1'];
|
|
200
|
+
let ready = 0;
|
|
201
|
+
let release!: () => void;
|
|
202
|
+
const bothReady = new Promise<void>(resolve => { release = resolve; });
|
|
203
|
+
await Promise.all(correlations.map((correlationId, index) =>
|
|
204
|
+
correlationIdManager.run(new CorrelationId(correlationId), () => inContext(async parent => {
|
|
205
|
+
const id = index.toString();
|
|
206
|
+
expected.set(id, { parent, correlationId });
|
|
207
|
+
if (++ready === 2) release();
|
|
208
|
+
await bothReady;
|
|
209
|
+
await connection.eventStores.allEventStores({}, { metadata: Metadata({ 'x-call': id }) });
|
|
210
|
+
correlationIdManager.scoped!.toString().should.equal(correlationId);
|
|
211
|
+
}, correlationId))));
|
|
212
|
+
});
|
|
213
|
+
it('should propagate each caller trace and business correlation without cross-contamination', () => {
|
|
214
|
+
received.should.have.lengthOf(2);
|
|
215
|
+
for (const metadata of received) {
|
|
216
|
+
const caller = expected.get(metadata.get('x-call')!)!;
|
|
217
|
+
should.equal(metadata.get('traceparent'), caller.parent);
|
|
218
|
+
should.equal(metadata.get('baggage'), `cratis.correlation_id=${caller.correlationId}`);
|
|
219
|
+
}
|
|
220
|
+
new Set(received.map(metadata => metadata.get('traceparent'))).size.should.equal(2);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('when calling without an active span or baggage', () => {
|
|
225
|
+
beforeEach(async () => {
|
|
226
|
+
await connection.connect();
|
|
227
|
+
received.length = 0;
|
|
228
|
+
await connection.eventStores.allEventStores({}, { metadata: original });
|
|
229
|
+
});
|
|
230
|
+
it('should remove stale propagation without losing other metadata', () => {
|
|
231
|
+
should.equal(received[0].get('traceparent'), undefined);
|
|
232
|
+
should.equal(received[0].get('baggage'), undefined);
|
|
233
|
+
should.equal(received[0].get('authorization'), 'Bearer caller-token');
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -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 { afterAll, beforeAll, beforeEach, chai, describe, it } from 'vitest';
|
|
5
|
+
import { context, propagation, trace } from '@opentelemetry/api';
|
|
6
|
+
import { GrpcInstrumentation } from '@opentelemetry/instrumentation-grpc';
|
|
7
|
+
import type { ServiceImplementation } from 'nice-grpc';
|
|
8
|
+
import { Metadata } from 'nice-grpc-common';
|
|
9
|
+
import type { ChronicleConnection } from '../../ChronicleConnection.js';
|
|
10
|
+
import { telemetrySession } from '../../../telemetry/for_ChronicleTelemetry/given/a_telemetry_session.fixture.js';
|
|
11
|
+
|
|
12
|
+
const should = chai.should();
|
|
13
|
+
const telemetry = telemetrySession();
|
|
14
|
+
const instrumentation = new GrpcInstrumentation({ enabled: false });
|
|
15
|
+
const correlation = 'f5a978af-cd69-4a98-9f1e-7f656cc8ba2a';
|
|
16
|
+
let server: ReturnType<typeof import('nice-grpc').createServer>;
|
|
17
|
+
let connection: ChronicleConnection;
|
|
18
|
+
const received: Metadata[] = [];
|
|
19
|
+
let hostTraceId: string;
|
|
20
|
+
|
|
21
|
+
beforeAll(async () => {
|
|
22
|
+
instrumentation.setTracerProvider(telemetry.traces);
|
|
23
|
+
instrumentation.enable();
|
|
24
|
+
// Load grpc-js only after the real instrumentation's require hooks are installed.
|
|
25
|
+
const { createServer } = await import('nice-grpc');
|
|
26
|
+
const { ConnectionServiceDefinition, EventStoresDefinition, ReactorsDefinition } = await import('@cratis/chronicle.contracts');
|
|
27
|
+
const { ChronicleConnection } = await import('../../ChronicleConnection.js');
|
|
28
|
+
server = createServer().use(async function* (call, callContext) {
|
|
29
|
+
received.push(Metadata(callContext.metadata));
|
|
30
|
+
return yield* call.next(call.request, callContext);
|
|
31
|
+
});
|
|
32
|
+
const unused = async () => { throw new Error('Unexpected RPC'); };
|
|
33
|
+
server.add(ConnectionServiceDefinition, {
|
|
34
|
+
...Object.fromEntries(Object.keys(ConnectionServiceDefinition.methods).map(name => [name, unused])),
|
|
35
|
+
checkCompatibility: async () => ({ IsCompatible: true, Incompatibilities: [], ServerVersion: 'test' })
|
|
36
|
+
} as ServiceImplementation<typeof ConnectionServiceDefinition>);
|
|
37
|
+
server.add(EventStoresDefinition, {
|
|
38
|
+
allEventStores: async () => ({ IsAuthorized: true, Data: [] }),
|
|
39
|
+
ensureEventStore: unused,
|
|
40
|
+
observeEventStores: async function* () {
|
|
41
|
+
yield { IsAuthorized: true, Data: [] };
|
|
42
|
+
yield { IsAuthorized: true, Data: [] };
|
|
43
|
+
}
|
|
44
|
+
} as ServiceImplementation<typeof EventStoresDefinition>);
|
|
45
|
+
server.add(ReactorsDefinition, {
|
|
46
|
+
...Object.fromEntries(Object.keys(ReactorsDefinition.methods).map(name => [name, unused])),
|
|
47
|
+
observe: async function* (requests) {
|
|
48
|
+
for await (const _request of requests) {
|
|
49
|
+
yield { Events: [], Partition: '', ReplayState: 0 };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} as ServiceImplementation<typeof ReactorsDefinition>);
|
|
53
|
+
const port = await server.listen('127.0.0.1:0');
|
|
54
|
+
connection = new ChronicleConnection({ connectionString: `chronicle://127.0.0.1:${port}?disableTls=true&apiKey=test` });
|
|
55
|
+
await connection.connect();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterAll(async () => {
|
|
59
|
+
connection?.dispose();
|
|
60
|
+
await server?.shutdown();
|
|
61
|
+
instrumentation.disable();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('when gRPC auto-instrumentation reinjects active context for unary and streaming calls', () => {
|
|
65
|
+
beforeEach(async () => {
|
|
66
|
+
received.length = 0;
|
|
67
|
+
const span = telemetry.traces.getTracer('host').startSpan('host');
|
|
68
|
+
hostTraceId = span.spanContext().traceId;
|
|
69
|
+
const active = propagation.setBaggage(trace.setSpan(context.active(), span), propagation.createBaggage({
|
|
70
|
+
'cratis.correlation_id': { value: correlation },
|
|
71
|
+
email: { value: 'private@example.com' },
|
|
72
|
+
'cratis.event_source.id': { value: 'private-source' },
|
|
73
|
+
'cratis.tenant.id': { value: 'private-tenant' }
|
|
74
|
+
}));
|
|
75
|
+
try {
|
|
76
|
+
await context.with(active, async () => {
|
|
77
|
+
await connection.eventStores.allEventStores({});
|
|
78
|
+
for await (const _response of connection.eventStores.observeEventStores({})) {
|
|
79
|
+
context.active().should.equal(active);
|
|
80
|
+
await Promise.resolve();
|
|
81
|
+
}
|
|
82
|
+
async function* requests() { yield { Content: undefined }; yield { Content: undefined }; }
|
|
83
|
+
for await (const _response of connection.reactors.observe(requests())) {
|
|
84
|
+
context.active().should.equal(active);
|
|
85
|
+
await Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
propagation.getBaggage(context.active())!.getEntry('email')!.value.should.equal('private@example.com');
|
|
88
|
+
});
|
|
89
|
+
} finally { span.end(); }
|
|
90
|
+
});
|
|
91
|
+
it('should never let auto-instrumentation restore forbidden baggage on the wire', () => {
|
|
92
|
+
received.should.have.lengthOf(3);
|
|
93
|
+
for (const metadata of received) {
|
|
94
|
+
should.equal(metadata.get('baggage'), `cratis.correlation_id=${correlation}`);
|
|
95
|
+
metadata.get('traceparent')!.should.contain(hostTraceId);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
it('should actually execute the enabled gRPC instrumentation', () => {
|
|
99
|
+
telemetry.spans.getFinishedSpans().filter(span => span.instrumentationScope.name === '@opentelemetry/instrumentation-grpc')
|
|
100
|
+
.length.should.be.greaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -18,11 +18,21 @@ export class CorrelationIdManager implements ICorrelationIdAccessor, ICorrelatio
|
|
|
18
18
|
return this._storage.getStore() ?? CorrelationId.create();
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** The explicitly scoped identifier, without generating an identifier when no scope exists. */
|
|
22
|
+
get scoped(): CorrelationId | undefined {
|
|
23
|
+
return this._storage.getStore();
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
/** Runs an async or synchronous operation with a correlation id, restoring the caller afterward. */
|
|
22
27
|
run<T>(correlationId: CorrelationId, callback: () => T): T {
|
|
23
28
|
return this._storage.run(correlationId, callback);
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
/** Runs work without inheriting a business correlation, restoring the caller afterward. */
|
|
32
|
+
runWithout<T>(callback: () => T): T {
|
|
33
|
+
return this._storage.exit(callback);
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
/** @inheritdoc */
|
|
27
37
|
setCurrent(correlationId: CorrelationId): void {
|
|
28
38
|
this._storage.enterWith(correlationId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChronicleClient.d.ts","sourceRoot":"","sources":["../ChronicleClient.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChronicleClient.d.ts","sourceRoot":"","sources":["../ChronicleClient.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAUzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAQ/C;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IAwBxC,QAAQ,CAAC,OAAO,EAAE,gBAAgB;IAvB9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,sBAAsB,CAAQ;IACtD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAU;IAE/C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA6B;IAExD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkC;IAE1D,OAAO,CAAC,eAAe,CAAC,CAAiC;IACzD,OAAO,CAAC,iBAAiB,CAAC,CAAgB;IAC1C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,mBAAmB,CAAC,CAAgB;IAC5C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,yBAAyB,CAAC,CAAkB;IACpD,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,kBAAkB,CAAC,CAAQ;IACnC,OAAO,CAAC,gCAAgC,CAAK;IAE7C;;;OAGG;IACH,YAAqB,OAAO,EAAE,gBAAgB,EAiD7C;IAED,kBAAkB;IACZ,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC,CA6DrH;IAED,kBAAkB;IACZ,cAAc,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAuBhD;IAED,kBAAkB;IAClB,OAAO,IAAI,IAAI,CAqBd;YAEa,gBAAgB;YA4ChB,eAAe;YAuBf,SAAS;IAmDvB;;;;OAIG;YACW,OAAO;YAcP,aAAa;IAyB3B,OAAO,CAAC,uBAAuB;IAW/B,OAAO,CAAC,2BAA2B;IAOnC,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,uBAAuB;YAQjB,cAAc;YAkBd,oBAAoB;IAmClC,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,0BAA0B;YAQpB,yBAAyB;CA4B1C"}
|
package/dist/ChronicleClient.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
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
|
import * as os from 'os';
|
|
4
|
-
import {
|
|
4
|
+
import { createLogger } from './logging/createLogger.js';
|
|
5
|
+
import { setTelemetryAttribute, recordSafeException } from './telemetry/spanAttributes.js';
|
|
6
|
+
import { WellKnownTelemetryNames } from './WellKnownTelemetryNames.js';
|
|
5
7
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
6
8
|
import { ChronicleConnection } from './connection/index.js';
|
|
7
9
|
import { IncompatibleChronicleServer } from './connection/IncompatibleChronicleServer.js';
|
|
@@ -15,7 +17,8 @@ import { EventStore } from './EventStore.js';
|
|
|
15
17
|
import { EventStoreName } from './EventStoreName.js';
|
|
16
18
|
import { EventStoreNamespaceName } from './EventStoreNamespaceName.js';
|
|
17
19
|
import { ChronicleMetrics } from './Metrics.js';
|
|
18
|
-
import {
|
|
20
|
+
import { observeOperation } from './telemetry/observeOperation.js';
|
|
21
|
+
import { runInBackgroundContext } from './telemetry/runInBackgroundContext.js';
|
|
19
22
|
import { TypeDiscoverer } from './types/index.js';
|
|
20
23
|
import { takeUnregisteredModelBoundMappings } from './types/modelBoundPropertyMetadata.js';
|
|
21
24
|
import { reachableReadModelTypes } from './readModels/rootReadModelTypes.js';
|
|
@@ -39,9 +42,7 @@ export class ChronicleClient {
|
|
|
39
42
|
_connection;
|
|
40
43
|
_stores = new Map();
|
|
41
44
|
_lifecycle = new ConnectionLifecycle();
|
|
42
|
-
_logger
|
|
43
|
-
namespace: '@cratis/chronicle/ChronicleClient'
|
|
44
|
-
});
|
|
45
|
+
_logger;
|
|
45
46
|
_watchdogHandle;
|
|
46
47
|
_connectOperation;
|
|
47
48
|
_reconnectOperation;
|
|
@@ -57,6 +58,7 @@ export class ChronicleClient {
|
|
|
57
58
|
*/
|
|
58
59
|
constructor(options) {
|
|
59
60
|
this.options = options;
|
|
61
|
+
this._logger = createLogger('@cratis/chronicle/ChronicleClient', options.logger);
|
|
60
62
|
// When TLS is disabled, pass pre-built insecure credentials directly.
|
|
61
63
|
// @cratis/chronicle.contracts ≤15.24.3 always composes call credentials with
|
|
62
64
|
// channel credentials, which gRPC forbids for insecure channels. Passing
|
|
@@ -65,15 +67,17 @@ export class ChronicleClient {
|
|
|
65
67
|
const connectionOptions = options.connectionString.disableTls
|
|
66
68
|
? { connectionString: options.connectionString, credentials: options.connectionString.createCredentials() }
|
|
67
69
|
: { connectionString: options.connectionString };
|
|
68
|
-
this._connection = new ChronicleConnection(connectionOptions);
|
|
70
|
+
this._connection = new ChronicleConnection({ ...connectionOptions, logger: options.logger });
|
|
69
71
|
this._logger.info('Created Chronicle client', {
|
|
70
72
|
serverAddress: `${options.connectionString.serverAddress.host}:${options.connectionString.serverAddress.port}`,
|
|
71
73
|
disableTls: options.connectionString.disableTls
|
|
72
74
|
});
|
|
73
75
|
if (options.discoveryPatterns.length > 0) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
this.
|
|
76
|
+
runInBackgroundContext(() => {
|
|
77
|
+
this._discoveryOperation = TypeDiscoverer.default.discover(options.discoveryPatterns);
|
|
78
|
+
this._discoveryOperation.catch(error => {
|
|
79
|
+
this._logger.error('Artifact file discovery failed', { error });
|
|
80
|
+
});
|
|
77
81
|
});
|
|
78
82
|
}
|
|
79
83
|
this._lifecycle.onConnected(async () => {
|
|
@@ -104,9 +108,9 @@ export class ChronicleClient {
|
|
|
104
108
|
: typeof namespace === 'string'
|
|
105
109
|
? new EventStoreNamespaceName(namespace)
|
|
106
110
|
: namespace;
|
|
107
|
-
return
|
|
108
|
-
span
|
|
109
|
-
span
|
|
111
|
+
return observeOperation(WellKnownTelemetryNames.spans.getEventStore, async (span) => {
|
|
112
|
+
setTelemetryAttribute(span, 'eventStore', storeName.value);
|
|
113
|
+
setTelemetryAttribute(span, 'namespace', namespaceName.value);
|
|
110
114
|
try {
|
|
111
115
|
const store = await this.withReconnect('get_event_store', async () => {
|
|
112
116
|
await this.ensureConnected();
|
|
@@ -123,10 +127,18 @@ export class ChronicleClient {
|
|
|
123
127
|
eventStore: storeName.value
|
|
124
128
|
});
|
|
125
129
|
ensureCommandSuccess('ensure event store', await this._connection.eventStores.ensureEventStore({ Name: storeName.value }));
|
|
126
|
-
const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy);
|
|
130
|
+
const created = new EventStore(storeName, namespaceName, this._connection, this._lifecycle, this.options.defaultSinkTypeId, this.options.clientArtifactsProvider, this.options.reactorResultHandler, this.options.artifactActivator, this.options.readModelNamingPolicy, this.options.telemetry);
|
|
127
131
|
this._stores.set(key, created);
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
try {
|
|
133
|
+
await this.registerArtifactsForStore(created, 'new-store');
|
|
134
|
+
return created;
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (this._stores.get(key) === created)
|
|
138
|
+
this._stores.delete(key);
|
|
139
|
+
created.disposeObservations();
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
130
142
|
});
|
|
131
143
|
ChronicleMetrics.eventStoreRetrievals.add(1, {
|
|
132
144
|
'chronicle.event_store': storeName.value,
|
|
@@ -136,12 +148,11 @@ export class ChronicleClient {
|
|
|
136
148
|
return store;
|
|
137
149
|
}
|
|
138
150
|
catch (error) {
|
|
139
|
-
span
|
|
140
|
-
span.recordException(error);
|
|
151
|
+
recordSafeException(span, error);
|
|
141
152
|
this._logger.error('Failed getting event store', {
|
|
142
153
|
eventStore: storeName.value,
|
|
143
154
|
namespace: namespaceName.value,
|
|
144
|
-
error
|
|
155
|
+
error
|
|
145
156
|
});
|
|
146
157
|
throw error;
|
|
147
158
|
}
|
|
@@ -152,7 +163,7 @@ export class ChronicleClient {
|
|
|
152
163
|
}
|
|
153
164
|
/** @inheritdoc */
|
|
154
165
|
async getEventStores() {
|
|
155
|
-
return
|
|
166
|
+
return observeOperation(WellKnownTelemetryNames.spans.getEventStores, async (span) => {
|
|
156
167
|
try {
|
|
157
168
|
const response = await this.withReconnect('get_event_stores', async () => {
|
|
158
169
|
await this.ensureConnected();
|
|
@@ -166,10 +177,9 @@ export class ChronicleClient {
|
|
|
166
177
|
return result;
|
|
167
178
|
}
|
|
168
179
|
catch (error) {
|
|
169
|
-
span
|
|
170
|
-
span.recordException(error);
|
|
180
|
+
recordSafeException(span, error);
|
|
171
181
|
this._logger.error('Failed retrieving event stores', {
|
|
172
|
-
error
|
|
182
|
+
error
|
|
173
183
|
});
|
|
174
184
|
throw error;
|
|
175
185
|
}
|
|
@@ -190,7 +200,7 @@ export class ChronicleClient {
|
|
|
190
200
|
if (this._lifecycle.isConnected) {
|
|
191
201
|
void this._lifecycle.disconnected(error => {
|
|
192
202
|
this._logger.error('Disconnected lifecycle callback failed during dispose', {
|
|
193
|
-
error
|
|
203
|
+
error
|
|
194
204
|
});
|
|
195
205
|
});
|
|
196
206
|
}
|
|
@@ -218,7 +228,7 @@ export class ChronicleClient {
|
|
|
218
228
|
await this.startKernelKeepAlive();
|
|
219
229
|
await this._lifecycle.connected(error => {
|
|
220
230
|
this._logger.error('Connected lifecycle callback failed', {
|
|
221
|
-
error
|
|
231
|
+
error
|
|
222
232
|
});
|
|
223
233
|
});
|
|
224
234
|
return;
|
|
@@ -249,7 +259,7 @@ export class ChronicleClient {
|
|
|
249
259
|
return;
|
|
250
260
|
}
|
|
251
261
|
if (!this._connectOperation) {
|
|
252
|
-
this._connectOperation = this.connectWithRetry().finally(() => {
|
|
262
|
+
this._connectOperation = runInBackgroundContext(() => this.connectWithRetry()).finally(() => {
|
|
253
263
|
this._connectOperation = undefined;
|
|
254
264
|
});
|
|
255
265
|
}
|
|
@@ -259,15 +269,15 @@ export class ChronicleClient {
|
|
|
259
269
|
if (this._connectionFailure)
|
|
260
270
|
throw this._connectionFailure;
|
|
261
271
|
if (!this._reconnectOperation) {
|
|
262
|
-
this._reconnectOperation = (async () => {
|
|
272
|
+
this._reconnectOperation = runInBackgroundContext(async () => {
|
|
263
273
|
this._logger.warn('Reconnecting to Chronicle kernel', {
|
|
264
274
|
reason,
|
|
265
|
-
error
|
|
275
|
+
error
|
|
266
276
|
});
|
|
267
277
|
if (this._lifecycle.isConnected) {
|
|
268
278
|
await this._lifecycle.disconnected(disconnectError => {
|
|
269
279
|
this._logger.error('Disconnected lifecycle callback failed', {
|
|
270
|
-
error:
|
|
280
|
+
error: disconnectError
|
|
271
281
|
});
|
|
272
282
|
});
|
|
273
283
|
}
|
|
@@ -282,7 +292,7 @@ export class ChronicleClient {
|
|
|
282
292
|
await this.startKernelKeepAlive();
|
|
283
293
|
await this._lifecycle.connected(connectedError => {
|
|
284
294
|
this._logger.error('Connected lifecycle callback failed after reconnect', {
|
|
285
|
-
error:
|
|
295
|
+
error: connectedError
|
|
286
296
|
});
|
|
287
297
|
});
|
|
288
298
|
return;
|
|
@@ -297,7 +307,7 @@ export class ChronicleClient {
|
|
|
297
307
|
await this.backOff(attempt, 'Reconnect attempt failed, retrying', reconnectError);
|
|
298
308
|
}
|
|
299
309
|
}
|
|
300
|
-
})
|
|
310
|
+
}).finally(() => {
|
|
301
311
|
this._reconnectOperation = undefined;
|
|
302
312
|
});
|
|
303
313
|
}
|
|
@@ -314,7 +324,7 @@ export class ChronicleClient {
|
|
|
314
324
|
this._logger.warn(message, {
|
|
315
325
|
attempt,
|
|
316
326
|
delayMs,
|
|
317
|
-
error
|
|
327
|
+
error
|
|
318
328
|
});
|
|
319
329
|
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
320
330
|
return delayMs;
|
|
@@ -394,9 +404,9 @@ export class ChronicleClient {
|
|
|
394
404
|
return String(error);
|
|
395
405
|
}
|
|
396
406
|
startConnectionWatchdog() {
|
|
397
|
-
this._watchdogHandle = setInterval(() => {
|
|
407
|
+
this._watchdogHandle = runInBackgroundContext(() => setInterval(() => {
|
|
398
408
|
void this.runHealthCheck().catch(error => this.backgroundConnectionFailed('watchdog-health-check', error));
|
|
399
|
-
}, ChronicleClient._healthCheckIntervalMs);
|
|
409
|
+
}, ChronicleClient._healthCheckIntervalMs));
|
|
400
410
|
this._watchdogHandle.unref?.();
|
|
401
411
|
}
|
|
402
412
|
async runHealthCheck() {
|
|
@@ -430,7 +440,7 @@ export class ChronicleClient {
|
|
|
430
440
|
return;
|
|
431
441
|
}
|
|
432
442
|
void this.reconnect(reason, error).catch(failure => this.backgroundConnectionFailed(reason, failure));
|
|
433
|
-
});
|
|
443
|
+
}, {}, this.options.logger);
|
|
434
444
|
await keepAlive.start({
|
|
435
445
|
ConnectionId: this._lifecycle.connectionId,
|
|
436
446
|
ClientVersion: clientVersion,
|
|
@@ -457,7 +467,7 @@ export class ChronicleClient {
|
|
|
457
467
|
this.failConnection(error instanceof Error ? error : new Error(String(error)));
|
|
458
468
|
this._logger.error('Background connection recovery failed; create a new client after correcting the server', {
|
|
459
469
|
reason,
|
|
460
|
-
error
|
|
470
|
+
error
|
|
461
471
|
});
|
|
462
472
|
}
|
|
463
473
|
async registerArtifactsForStore(store, reason) {
|