@cratis/chronicle 4.0.0 → 5.1.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 +3 -3
- package/EventStore.ts +3 -3
- package/connection/ChronicleConnection.ts +6 -0
- package/connection/ChronicleServices.ts +2 -0
- package/connection/callResults.ts +23 -0
- package/dist/ChronicleClient.js +3 -3
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/EventStore.js +3 -3
- package/dist/EventStore.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +1 -0
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +5 -1
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/ChronicleServices.d.ts +2 -1
- package/dist/connection/ChronicleServices.d.ts.map +1 -1
- package/dist/connection/callResults.d.ts +13 -0
- package/dist/connection/callResults.d.ts.map +1 -1
- package/dist/connection/callResults.js +14 -0
- package/dist/connection/callResults.js.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts +2 -2
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +39 -57
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/events/EventTypes.d.ts.map +1 -1
- package/dist/events/EventTypes.js +3 -2
- package/dist/events/EventTypes.js.map +1 -1
- package/dist/jobs/Jobs.js +3 -3
- package/dist/jobs/Jobs.js.map +1 -1
- package/dist/observation/toObserverRunningState.js +1 -1
- package/dist/observation/toObserverRunningState.js.map +1 -1
- package/dist/projections/declarative/FromEveryBuilder.d.ts +6 -0
- package/dist/projections/declarative/FromEveryBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/FromEveryBuilder.js +27 -0
- package/dist/projections/declarative/FromEveryBuilder.js.map +1 -1
- package/dist/projections/declarative/IFromEveryBuilder.d.ts +22 -0
- package/dist/projections/declarative/IFromEveryBuilder.d.ts.map +1 -1
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +8 -6
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/seeding/EventSeeding.d.ts.map +1 -1
- package/dist/seeding/EventSeeding.js +2 -1
- package/dist/seeding/EventSeeding.js.map +1 -1
- package/dist/webhooks/Webhooks.d.ts.map +1 -1
- package/dist/webhooks/Webhooks.js +22 -10
- package/dist/webhooks/Webhooks.js.map +1 -1
- package/eventSequences/EventSequence.spec.ts +83 -74
- package/eventSequences/EventSequence.ts +44 -63
- package/events/EventTypes.ts +3 -2
- package/jobs/Jobs.ts +3 -3
- package/observation/toObserverRunningState.ts +1 -1
- package/package.json +7 -7
- package/projections/declarative/FromEveryBuilder.ts +30 -0
- package/projections/declarative/IFromEveryBuilder.ts +25 -0
- package/projections/declarative/ProjectionBuilderFor.spec.ts +58 -0
- package/readModels/ReadModels.ts +8 -6
- package/seeding/EventSeeding.ts +2 -1
- package/webhooks/Webhooks.ts +24 -10
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import { ChronicleConnection } from '../connection';
|
|
5
5
|
import { SpanStatusCode } from '@opentelemetry/api';
|
|
6
|
-
import type {
|
|
6
|
+
import type { AppendedEventResponse as ContractsAppendedEvent } from '@cratis/chronicle.contracts';
|
|
7
7
|
import { Constructor, Guid, JsonSerializer } from '@cratis/fundamentals';
|
|
8
8
|
import { getEventTypeFor } from '../events/eventTypeDecorator';
|
|
9
9
|
import type { AppendedEvent } from '../events/AppendedEvent';
|
|
@@ -40,6 +40,7 @@ import { identityProvider, Identity } from '../identity';
|
|
|
40
40
|
import { causationManager, CausationType } from '../auditing';
|
|
41
41
|
import { correlationIdManager } from '../correlation';
|
|
42
42
|
import { fromContractsGuid, toContractsGuid } from '../connection/Guid';
|
|
43
|
+
import { ensureCommandResponse, ensureCommandSuccess, ensureQuerySuccess } from '../connection/callResults';
|
|
43
44
|
import type { ConcurrencyScope } from './ConcurrencyScope';
|
|
44
45
|
import { IUnitOfWorkManager } from '../transactions/IUnitOfWorkManager';
|
|
45
46
|
|
|
@@ -67,7 +68,7 @@ export class EventSequence implements IEventSequence {
|
|
|
67
68
|
const correlationId = options?.correlationId === undefined
|
|
68
69
|
? Guid.as(correlationIdManager.current.value)
|
|
69
70
|
: Guid.as(options.correlationId);
|
|
70
|
-
const content = JsonSerializer.serialize(event);
|
|
71
|
+
const content = JSON.parse(JsonSerializer.serialize(event));
|
|
71
72
|
|
|
72
73
|
// Merge static tags declared on the event type with tags supplied at append time.
|
|
73
74
|
const tags = mergeTags(getTagsFor(event.constructor as Function), options?.tags);
|
|
@@ -119,12 +120,13 @@ export class EventSequence implements IEventSequence {
|
|
|
119
120
|
Subject: eventSourceId
|
|
120
121
|
});
|
|
121
122
|
|
|
123
|
+
const appendResponse = ensureCommandResponse('append event', response);
|
|
122
124
|
const duration = Date.now() - startTime;
|
|
123
125
|
const result = this.mapAppendResponse(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
126
|
+
appendResponse.SequenceNumber,
|
|
127
|
+
appendResponse.ConstraintViolations ?? [],
|
|
128
|
+
appendResponse.Errors ?? [],
|
|
129
|
+
appendResponse.ConcurrencyViolation
|
|
128
130
|
);
|
|
129
131
|
span.setAttribute('chronicle.sequence_number', result.sequenceNumber.value.toString());
|
|
130
132
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
@@ -247,13 +249,7 @@ export class EventSequence implements IEventSequence {
|
|
|
247
249
|
Generation: eventType.generation.value,
|
|
248
250
|
Tombstone: eventType.tombstone
|
|
249
251
|
},
|
|
250
|
-
Content: JsonSerializer.serialize(event),
|
|
251
|
-
Causation: batchCausationChain.map(c => ({
|
|
252
|
-
Occurred: { Value: c.occurred.toISOString() },
|
|
253
|
-
Type: c.type.name,
|
|
254
|
-
Properties: { ...c.properties }
|
|
255
|
-
})),
|
|
256
|
-
CausedBy: toContractsCausedBy(identity),
|
|
252
|
+
Content: JSON.parse(JsonSerializer.serialize(event)),
|
|
257
253
|
Tags: tags,
|
|
258
254
|
Occurred: undefined,
|
|
259
255
|
Subject: subject ?? eventSourceId
|
|
@@ -279,7 +275,7 @@ export class EventSequence implements IEventSequence {
|
|
|
279
275
|
span.setAttribute('chronicle.events_count', eventsForEventSourceIds.length);
|
|
280
276
|
const startTime = Date.now();
|
|
281
277
|
try {
|
|
282
|
-
const response = await this._connection.eventSequences.
|
|
278
|
+
const response = await this._connection.eventSequences.appendManyForEventSources({
|
|
283
279
|
EventStore: this._eventStoreName,
|
|
284
280
|
Namespace: this._namespace,
|
|
285
281
|
EventSequenceId: this.id.value,
|
|
@@ -291,21 +287,23 @@ export class EventSequence implements IEventSequence {
|
|
|
291
287
|
Properties: { ...c.properties }
|
|
292
288
|
})),
|
|
293
289
|
CausedBy: toContractsCausedBy(identity),
|
|
294
|
-
ConcurrencyScopes: {
|
|
295
|
-
|
|
296
|
-
|
|
290
|
+
ConcurrencyScopes: distinctEventSourceIds.map(eventSourceId => ({
|
|
291
|
+
EventSourceId: eventSourceId,
|
|
292
|
+
Scope: resolveConcurrencyScope(eventSourceId)
|
|
293
|
+
}))
|
|
297
294
|
});
|
|
298
295
|
|
|
296
|
+
const appendManyResponse = ensureCommandResponse('append many events', response);
|
|
299
297
|
const duration = Date.now() - startTime;
|
|
300
298
|
// Mirrors the C# client: every per-event AppendResult in a batch carries all
|
|
301
299
|
// constraint violations and the first concurrency violation of the whole batch —
|
|
302
300
|
// the wire response doesn't correlate either back to a specific event index.
|
|
303
|
-
const firstConcurrencyViolation = (
|
|
304
|
-
const result = (
|
|
301
|
+
const firstConcurrencyViolation = (appendManyResponse.ConcurrencyViolations ?? [])[0];
|
|
302
|
+
const result = (appendManyResponse.SequenceNumbers ?? []).map((sequenceNumber: bigint, index: number) =>
|
|
305
303
|
this.mapAppendResponse(
|
|
306
304
|
sequenceNumber,
|
|
307
|
-
|
|
308
|
-
(
|
|
305
|
+
appendManyResponse.ConstraintViolations ?? [],
|
|
306
|
+
(appendManyResponse.Errors ?? []).filter((_: string, errorIndex: number) => errorIndex === index),
|
|
309
307
|
firstConcurrencyViolation
|
|
310
308
|
)
|
|
311
309
|
);
|
|
@@ -398,18 +396,19 @@ export class EventSequence implements IEventSequence {
|
|
|
398
396
|
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
399
397
|
}
|
|
400
398
|
try {
|
|
401
|
-
const response = await this._connection.eventSequences.
|
|
399
|
+
const response = await this._connection.eventSequences.tailSequenceNumber({
|
|
402
400
|
EventStore: this._eventStoreName,
|
|
403
401
|
Namespace: this._namespace,
|
|
404
402
|
EventSequenceId: this.id.value,
|
|
405
403
|
EventSourceId: eventSourceId ?? '',
|
|
406
|
-
|
|
404
|
+
EventTypeIds: this.joinEventTypeIds(filterEventTypes ?? []),
|
|
407
405
|
EventSourceType: eventSourceType ?? 'Default',
|
|
408
406
|
EventStreamId: eventStreamId ?? '',
|
|
409
407
|
EventStreamType: eventStreamType ?? 'Default'
|
|
410
408
|
});
|
|
411
409
|
|
|
412
|
-
const
|
|
410
|
+
const data = ensureQuerySuccess('get tail sequence number', response);
|
|
411
|
+
const result = new EventSequenceNumber(data?.SequenceNumber ?? 0n);
|
|
413
412
|
span.setAttribute('chronicle.sequence_number', result.value.toString());
|
|
414
413
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
415
414
|
return result;
|
|
@@ -444,7 +443,7 @@ export class EventSequence implements IEventSequence {
|
|
|
444
443
|
EventSourceId: eventSourceId
|
|
445
444
|
});
|
|
446
445
|
|
|
447
|
-
const result = response
|
|
446
|
+
const result = ensureQuerySuccess('has events for event source', response)?.HasEvents ?? false;
|
|
448
447
|
span.setAttribute('chronicle.has_events', result);
|
|
449
448
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
450
449
|
return result;
|
|
@@ -464,7 +463,8 @@ export class EventSequence implements IEventSequence {
|
|
|
464
463
|
eventTypes: Constructor[],
|
|
465
464
|
eventStreamType?: string,
|
|
466
465
|
eventStreamId?: string,
|
|
467
|
-
|
|
466
|
+
// Kept for IEventSequence signature compatibility; the wire no longer carries an event source type for this query.
|
|
467
|
+
_eventSourceType?: string
|
|
468
468
|
): Promise<AppendedEvent[]> {
|
|
469
469
|
return ChronicleTracer.startActiveSpan('chronicle.event_sequences.get_for_event_source_id_and_event_types', async span => {
|
|
470
470
|
span.setAttribute('chronicle.event_store', this._eventStoreName);
|
|
@@ -472,18 +472,17 @@ export class EventSequence implements IEventSequence {
|
|
|
472
472
|
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
473
473
|
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
474
474
|
try {
|
|
475
|
-
const response = await this._connection.eventSequences.
|
|
475
|
+
const response = await this._connection.eventSequences.forEventSourceIdAndEventTypes({
|
|
476
476
|
EventStore: this._eventStoreName,
|
|
477
477
|
Namespace: this._namespace,
|
|
478
478
|
EventSequenceId: this.id.value,
|
|
479
|
-
EventSourceType: eventSourceType ?? 'Default',
|
|
480
479
|
EventSourceId: eventSourceId,
|
|
481
480
|
EventStreamType: eventStreamType ?? 'Default',
|
|
482
481
|
EventStreamId: eventStreamId ?? '',
|
|
483
|
-
|
|
482
|
+
EventTypeIds: this.joinEventTypeIds(eventTypes)
|
|
484
483
|
});
|
|
485
484
|
|
|
486
|
-
const result = (
|
|
485
|
+
const result = ensureQuerySuccess('get events for event source id and event types', response).map(event => this.toClientAppendedEvent(event));
|
|
487
486
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
488
487
|
return result;
|
|
489
488
|
} catch (error) {
|
|
@@ -508,17 +507,16 @@ export class EventSequence implements IEventSequence {
|
|
|
508
507
|
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
509
508
|
span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
|
|
510
509
|
try {
|
|
511
|
-
const response = await this._connection.eventSequences.
|
|
510
|
+
const response = await this._connection.eventSequences.fromSequenceNumber({
|
|
512
511
|
EventStore: this._eventStoreName,
|
|
513
512
|
Namespace: this._namespace,
|
|
514
513
|
EventSequenceId: this.id.value,
|
|
515
514
|
FromEventSequenceNumber: sequenceNumber.value,
|
|
516
|
-
ToEventSequenceNumber: 0n,
|
|
517
515
|
EventSourceId: eventSourceId ?? '',
|
|
518
|
-
|
|
516
|
+
EventTypeIds: this.joinEventTypeIds(filterEventTypes ?? [])
|
|
519
517
|
});
|
|
520
518
|
|
|
521
|
-
const result = (
|
|
519
|
+
const result = ensureQuerySuccess('get events from sequence number', response).map(event => this.toClientAppendedEvent(event));
|
|
522
520
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
523
521
|
return result;
|
|
524
522
|
} catch (error) {
|
|
@@ -536,7 +534,6 @@ export class EventSequence implements IEventSequence {
|
|
|
536
534
|
causationManager.add(CausationType.redact, { sequenceNumber: sequenceNumber.value.toString() });
|
|
537
535
|
const causationChain = causationManager.getCurrentChain();
|
|
538
536
|
const identity = identityProvider.getCurrent();
|
|
539
|
-
const correlationId = Guid.as(correlationIdManager.current.value);
|
|
540
537
|
|
|
541
538
|
return ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact', async span => {
|
|
542
539
|
span.setAttribute('chronicle.event_store', this._eventStoreName);
|
|
@@ -544,20 +541,19 @@ export class EventSequence implements IEventSequence {
|
|
|
544
541
|
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
545
542
|
span.setAttribute('chronicle.sequence_number', sequenceNumber.value.toString());
|
|
546
543
|
try {
|
|
547
|
-
await this._connection.eventSequences.redact({
|
|
544
|
+
ensureCommandSuccess('redact event', await this._connection.eventSequences.redact({
|
|
548
545
|
EventStore: this._eventStoreName,
|
|
549
546
|
Namespace: this._namespace,
|
|
550
547
|
EventSequenceId: this.id.value,
|
|
551
548
|
SequenceNumber: sequenceNumber.value,
|
|
552
549
|
Reason: reason,
|
|
553
|
-
CorrelationId: toContractsGuid(correlationId),
|
|
554
550
|
Causation: causationChain.map(c => ({
|
|
555
551
|
Occurred: { Value: c.occurred.toISOString() },
|
|
556
552
|
Type: c.type.name,
|
|
557
553
|
Properties: { ...c.properties }
|
|
558
554
|
})),
|
|
559
555
|
CausedBy: toContractsCausedBy(identity)
|
|
560
|
-
});
|
|
556
|
+
}));
|
|
561
557
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
562
558
|
} catch (error) {
|
|
563
559
|
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
@@ -574,15 +570,7 @@ export class EventSequence implements IEventSequence {
|
|
|
574
570
|
causationManager.add(CausationType.redactForEventSource, { eventSourceId });
|
|
575
571
|
const causationChain = causationManager.getCurrentChain();
|
|
576
572
|
const identity = identityProvider.getCurrent();
|
|
577
|
-
const
|
|
578
|
-
const wireEventTypes = (eventTypes ?? []).map(constructor => {
|
|
579
|
-
const eventType = getEventTypeFor(constructor as unknown as Function);
|
|
580
|
-
return {
|
|
581
|
-
Id: eventType.id.value,
|
|
582
|
-
Generation: eventType.generation.value,
|
|
583
|
-
Tombstone: eventType.tombstone
|
|
584
|
-
};
|
|
585
|
-
});
|
|
573
|
+
const wireEventTypeIds = (eventTypes ?? []).map(constructor => getEventTypeFor(constructor as unknown as Function).id.value);
|
|
586
574
|
|
|
587
575
|
return ChronicleTracer.startActiveSpan('chronicle.event_sequences.redact_for_event_source', async span => {
|
|
588
576
|
span.setAttribute('chronicle.event_store', this._eventStoreName);
|
|
@@ -590,21 +578,20 @@ export class EventSequence implements IEventSequence {
|
|
|
590
578
|
span.setAttribute('chronicle.event_sequence_id', this.id.value);
|
|
591
579
|
span.setAttribute('chronicle.event_source_id', eventSourceId);
|
|
592
580
|
try {
|
|
593
|
-
await this._connection.eventSequences.redactForEventSource({
|
|
581
|
+
ensureCommandSuccess('redact event source', await this._connection.eventSequences.redactForEventSource({
|
|
594
582
|
EventStore: this._eventStoreName,
|
|
595
583
|
Namespace: this._namespace,
|
|
596
584
|
EventSequenceId: this.id.value,
|
|
597
585
|
EventSourceId: eventSourceId,
|
|
598
586
|
Reason: reason,
|
|
599
|
-
EventTypes:
|
|
600
|
-
CorrelationId: toContractsGuid(correlationId),
|
|
587
|
+
EventTypes: wireEventTypeIds,
|
|
601
588
|
Causation: causationChain.map(c => ({
|
|
602
589
|
Occurred: { Value: c.occurred.toISOString() },
|
|
603
590
|
Type: c.type.name,
|
|
604
591
|
Properties: { ...c.properties }
|
|
605
592
|
})),
|
|
606
593
|
CausedBy: toContractsCausedBy(identity)
|
|
607
|
-
});
|
|
594
|
+
}));
|
|
608
595
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
609
596
|
} catch (error) {
|
|
610
597
|
span.setStatus({ code: SpanStatusCode.ERROR, message: String(error) });
|
|
@@ -649,9 +636,10 @@ export class EventSequence implements IEventSequence {
|
|
|
649
636
|
EventStreamId: eventStreamId
|
|
650
637
|
});
|
|
651
638
|
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
639
|
+
const completeStreamResponse = ensureCommandResponse('complete stream', response);
|
|
640
|
+
const result: CompleteStreamResult = completeStreamResponse.IsSuccess
|
|
641
|
+
? { isSuccess: true, sequenceNumber: new EventSequenceNumber(completeStreamResponse.SequenceNumber ?? 0n) }
|
|
642
|
+
: { isSuccess: false, error: this.toClientCompleteStreamError(completeStreamResponse.Error) };
|
|
655
643
|
|
|
656
644
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
657
645
|
return result;
|
|
@@ -671,15 +659,8 @@ export class EventSequence implements IEventSequence {
|
|
|
671
659
|
return error === 1 ? CompleteStreamError.DefaultStreamCannotBeCompleted : CompleteStreamError.AlreadyCompleted;
|
|
672
660
|
}
|
|
673
661
|
|
|
674
|
-
private
|
|
675
|
-
return eventTypes.map(constructor =>
|
|
676
|
-
const eventType = getEventTypeFor(constructor as unknown as Function);
|
|
677
|
-
return {
|
|
678
|
-
Id: eventType.id.value,
|
|
679
|
-
Generation: eventType.generation.value,
|
|
680
|
-
Tombstone: eventType.tombstone
|
|
681
|
-
};
|
|
682
|
-
});
|
|
662
|
+
private joinEventTypeIds(eventTypes: Constructor[]): string {
|
|
663
|
+
return eventTypes.map(constructor => getEventTypeFor(constructor as unknown as Function).id.value).join(',');
|
|
683
664
|
}
|
|
684
665
|
|
|
685
666
|
private toClientAppendedEvent(wireEvent: ContractsAppendedEvent): AppendedEvent {
|
package/events/EventTypes.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { Constructor } from '@cratis/fundamentals';
|
|
5
5
|
import { IClientArtifactsProvider } from '../artifacts';
|
|
6
6
|
import { ChronicleConnection } from '../connection';
|
|
7
|
+
import { ensureCommandSuccess } from '../connection/callResults';
|
|
7
8
|
import { EventTypeId } from './EventTypeId';
|
|
8
9
|
import { IEventTypes } from './IEventTypes';
|
|
9
10
|
import { getEventTypeMetadata, getEventTypeJsonSchemaFor } from './eventTypeDecorator';
|
|
@@ -163,11 +164,11 @@ export class EventTypes implements IEventTypes {
|
|
|
163
164
|
return;
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
await this._connection.eventTypes.
|
|
167
|
+
ensureCommandSuccess('register event types', await this._connection.eventTypes.registerEventTypes({
|
|
167
168
|
EventStore: this._eventStore,
|
|
168
169
|
Types: registrations,
|
|
169
170
|
DisableValidation: false
|
|
170
|
-
});
|
|
171
|
+
}));
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
/** @inheritdoc */
|
package/jobs/Jobs.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import type { JobSummaryResponse, JobStepSummaryResponse } from '@cratis/chronicle.contracts';
|
|
5
5
|
import { Guid } from '@cratis/fundamentals';
|
|
6
6
|
import { ChronicleConnection } from '../connection';
|
|
7
|
-
import { ensureCommandSuccess, ensureQuerySuccess
|
|
7
|
+
import { ensureCommandSuccess, ensureQuerySuccess } from '../connection/callResults';
|
|
8
8
|
import { fromContractsGuid, toContractsGuid } from '../connection/Guid';
|
|
9
9
|
import { IJobs } from './IJobs';
|
|
10
10
|
import { JobId } from './JobId';
|
|
@@ -49,10 +49,10 @@ export class Jobs implements IJobs {
|
|
|
49
49
|
|
|
50
50
|
/** @inheritdoc */
|
|
51
51
|
async getJobs(): Promise<JobSummaryResponse[]> {
|
|
52
|
-
const response = await
|
|
52
|
+
const response = await this._connection.jobs.allJobs({
|
|
53
53
|
EventStore: this._eventStore,
|
|
54
54
|
Namespace: this._namespace
|
|
55
|
-
})
|
|
55
|
+
});
|
|
56
56
|
|
|
57
57
|
return ensureQuerySuccess('get jobs', response);
|
|
58
58
|
}
|
|
@@ -17,7 +17,7 @@ export function toObserverRunningState(state: ContractObserverRunningState): Obs
|
|
|
17
17
|
return ObserverRunningState.Suspended;
|
|
18
18
|
case ContractObserverRunningState.Replaying:
|
|
19
19
|
return ObserverRunningState.Replaying;
|
|
20
|
-
case ContractObserverRunningState.
|
|
20
|
+
case ContractObserverRunningState.OBSERVER_RUNNING_STATE_Disconnected:
|
|
21
21
|
return ObserverRunningState.Disconnected;
|
|
22
22
|
case ContractObserverRunningState.Quarantined:
|
|
23
23
|
return ObserverRunningState.Quarantined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/chronicle",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.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",
|
|
@@ -159,20 +159,20 @@
|
|
|
159
159
|
"test": "vitest run"
|
|
160
160
|
},
|
|
161
161
|
"dependencies": {
|
|
162
|
-
"@bufbuild/protobuf": "^2.
|
|
163
|
-
"@cratis/chronicle.contracts": "
|
|
162
|
+
"@bufbuild/protobuf": "^2.15.0",
|
|
163
|
+
"@cratis/chronicle.contracts": "18.2.0",
|
|
164
164
|
"@grpc/grpc-js": "^1.14.4",
|
|
165
165
|
"@opentelemetry/api": "^1.9.1",
|
|
166
166
|
"nice-grpc": "^2.1.17",
|
|
167
167
|
"nice-grpc-common": "^2.0.4",
|
|
168
168
|
"reflect-metadata": "^0.2.2",
|
|
169
|
-
"undici": "^8.10.
|
|
169
|
+
"undici": "^8.10.2"
|
|
170
170
|
},
|
|
171
171
|
"devDependencies": {
|
|
172
|
-
"@cratis/fundamentals": "7.
|
|
173
|
-
"@types/node": "^26.
|
|
172
|
+
"@cratis/fundamentals": "7.19.2",
|
|
173
|
+
"@types/node": "^26.5.1",
|
|
174
174
|
"typescript": "^6.0.3",
|
|
175
|
-
"vitest": "^
|
|
175
|
+
"vitest": "^5.0.0"
|
|
176
176
|
},
|
|
177
177
|
"peerDependencies": {
|
|
178
178
|
"@cratis/fundamentals": "^7.14.0"
|
|
@@ -38,6 +38,36 @@ export class FromEveryBuilder<TReadModel> implements IFromEveryBuilder<TReadMode
|
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/** @inheritdoc */
|
|
42
|
+
count(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel> {
|
|
43
|
+
const handler = new PropertyPathResolverProxyHandler();
|
|
44
|
+
const proxy = new Proxy({}, handler);
|
|
45
|
+
readModelPropertyAccessor(proxy as TReadModel);
|
|
46
|
+
const property = handler.property;
|
|
47
|
+
this.entry.properties[`${property}.$eventContext.${contextPropertyName}`] = '$count';
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @inheritdoc */
|
|
52
|
+
increment(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel> {
|
|
53
|
+
const handler = new PropertyPathResolverProxyHandler();
|
|
54
|
+
const proxy = new Proxy({}, handler);
|
|
55
|
+
readModelPropertyAccessor(proxy as TReadModel);
|
|
56
|
+
const property = handler.property;
|
|
57
|
+
this.entry.properties[`${property}.$eventContext.${contextPropertyName}`] = '$increment';
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @inheritdoc */
|
|
62
|
+
decrement(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel> {
|
|
63
|
+
const handler = new PropertyPathResolverProxyHandler();
|
|
64
|
+
const proxy = new Proxy({}, handler);
|
|
65
|
+
readModelPropertyAccessor(proxy as TReadModel);
|
|
66
|
+
const property = handler.property;
|
|
67
|
+
this.entry.properties[`${property}.$eventContext.${contextPropertyName}`] = '$decrement';
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
41
71
|
/** @inheritdoc */
|
|
42
72
|
excludeChildProjections(): IFromEveryBuilder<TReadModel> {
|
|
43
73
|
this.entry.includeChildren = false;
|
|
@@ -16,6 +16,31 @@ export interface IFromEveryBuilder<TReadModel> {
|
|
|
16
16
|
*/
|
|
17
17
|
set(readModelPropertyAccessor: PropertyAccessor<TReadModel>): IAllSetBuilder<TReadModel, IFromEveryBuilder<TReadModel>>;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Counts every projected event into a dictionary property, using a dynamic key resolved from an event context property.
|
|
21
|
+
* The dictionary value at the resolved key is incremented by one for each matching event.
|
|
22
|
+
* @param readModelPropertyAccessor - Accessor for the read model dictionary property (Record<string, number>).
|
|
23
|
+
* @param contextPropertyName - The event context property name used as the dictionary key (e.g., 'eventType').
|
|
24
|
+
* @returns This builder for fluent chaining.
|
|
25
|
+
*/
|
|
26
|
+
count(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Increments a dictionary property by one for every projected event, using a dynamic key resolved from an event context property.
|
|
30
|
+
* @param readModelPropertyAccessor - Accessor for the read model dictionary property (Record<string, number>).
|
|
31
|
+
* @param contextPropertyName - The event context property name used as the dictionary key (e.g., 'eventType').
|
|
32
|
+
* @returns This builder for fluent chaining.
|
|
33
|
+
*/
|
|
34
|
+
increment(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Decrements a dictionary property by one for every projected event, using a dynamic key resolved from an event context property.
|
|
38
|
+
* @param readModelPropertyAccessor - Accessor for the read model dictionary property (Record<string, number>).
|
|
39
|
+
* @param contextPropertyName - The event context property name used as the dictionary key (e.g., 'eventType').
|
|
40
|
+
* @returns This builder for fluent chaining.
|
|
41
|
+
*/
|
|
42
|
+
decrement(readModelPropertyAccessor: PropertyAccessor<TReadModel>, contextPropertyName: string): IFromEveryBuilder<TReadModel>;
|
|
43
|
+
|
|
19
44
|
/**
|
|
20
45
|
* Excludes child projections from the fromEvery definition.
|
|
21
46
|
* @returns This builder for fluent chaining.
|
|
@@ -27,6 +27,9 @@ class Inventory {
|
|
|
27
27
|
total!: number;
|
|
28
28
|
removedTotal!: number;
|
|
29
29
|
thingsHappenedCount!: number;
|
|
30
|
+
countsByEventType!: Record<string, number>;
|
|
31
|
+
incrementsByEventType!: Record<string, number>;
|
|
32
|
+
decrementsByEventType!: Record<string, number>;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
interface FromRecord {
|
|
@@ -39,6 +42,11 @@ interface JoinRecord {
|
|
|
39
42
|
Value: { Properties: Record<string, string> };
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
interface AllDefinition {
|
|
46
|
+
Properties: Record<string, string>;
|
|
47
|
+
IncludeChildren: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
42
50
|
describe('ProjectionBuilderFor', () => {
|
|
43
51
|
describe('when using add on a from builder', () => {
|
|
44
52
|
it('should produce a $add expression targeting the event property', () => {
|
|
@@ -112,4 +120,54 @@ describe('ProjectionBuilderFor', () => {
|
|
|
112
120
|
expect(entry.Value.Properties.thingsHappenedCount).toBe('$count');
|
|
113
121
|
});
|
|
114
122
|
});
|
|
123
|
+
|
|
124
|
+
describe('when using count with event context property on fromEvery', () => {
|
|
125
|
+
it('should produce a $count expression with dictionary key from event context', () => {
|
|
126
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
127
|
+
builder.fromEvery(fromEvery => fromEvery.count(model => model.countsByEventType, 'eventType'));
|
|
128
|
+
|
|
129
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { All: AllDefinition };
|
|
130
|
+
|
|
131
|
+
expect(definition.All.Properties['countsByEventType.$eventContext.eventType']).toBe('$count');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('when using increment with event context property on fromEvery', () => {
|
|
136
|
+
it('should produce a $increment expression with dictionary key from event context', () => {
|
|
137
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
138
|
+
builder.fromEvery(fromEvery => fromEvery.increment(model => model.incrementsByEventType, 'eventType'));
|
|
139
|
+
|
|
140
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { All: AllDefinition };
|
|
141
|
+
|
|
142
|
+
expect(definition.All.Properties['incrementsByEventType.$eventContext.eventType']).toBe('$increment');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('when using decrement with event context property on fromEvery', () => {
|
|
147
|
+
it('should produce a $decrement expression with dictionary key from event context', () => {
|
|
148
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
149
|
+
builder.fromEvery(fromEvery => fromEvery.decrement(model => model.decrementsByEventType, 'eventType'));
|
|
150
|
+
|
|
151
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { All: AllDefinition };
|
|
152
|
+
|
|
153
|
+
expect(definition.All.Properties['decrementsByEventType.$eventContext.eventType']).toBe('$decrement');
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe('when using multiple fromEvery operations', () => {
|
|
158
|
+
it('should produce all operations in the All.Properties definition', () => {
|
|
159
|
+
const builder = new ProjectionBuilderFor<Inventory>();
|
|
160
|
+
builder.fromEvery(fromEvery => {
|
|
161
|
+
fromEvery.count(model => model.countsByEventType, 'eventType');
|
|
162
|
+
fromEvery.increment(model => model.incrementsByEventType, 'correlationId');
|
|
163
|
+
fromEvery.set(model => model.id).toEventSourceId();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const definition = builder.build('inventory', 'Inventory') as unknown as { All: AllDefinition };
|
|
167
|
+
|
|
168
|
+
expect(definition.All.Properties['countsByEventType.$eventContext.eventType']).toBe('$count');
|
|
169
|
+
expect(definition.All.Properties['incrementsByEventType.$eventContext.correlationId']).toBe('$increment');
|
|
170
|
+
expect(definition.All.Properties.id).toBe('$eventSourceId');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
115
173
|
});
|
package/readModels/ReadModels.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { Guid, JsonSerializer } from '@cratis/fundamentals';
|
|
|
13
13
|
import { IClientArtifactsProvider } from '../artifacts';
|
|
14
14
|
import { toContractsGuid } from '../connection/Guid';
|
|
15
15
|
import { ChronicleConnection } from '../connection';
|
|
16
|
+
import { ensureQuerySuccess } from '../connection/callResults';
|
|
16
17
|
import { EventSequenceId } from '../eventSequences/EventSequenceId';
|
|
17
18
|
import { getProjectionMetadata } from '../projections/declarative/projection';
|
|
18
19
|
import { hasFromEventMetadata } from '../projections/modelBound/fromEvent';
|
|
@@ -115,16 +116,17 @@ export class ReadModels implements IReadModels {
|
|
|
115
116
|
/** @inheritdoc */
|
|
116
117
|
async getSnapshotsById<TReadModel>(readModelType: Constructor<TReadModel>, key: string): Promise<ReadModelSnapshot<TReadModel>[]> {
|
|
117
118
|
const readModel = this.resolveReadModel(readModelType);
|
|
118
|
-
const response = await this._connection.
|
|
119
|
+
const response = ensureQuerySuccess('get read model snapshots', await this._connection.readModelExplorer.allSnapshotsForReadModel({
|
|
119
120
|
EventStore: this._eventStore,
|
|
120
121
|
Namespace: this._namespace,
|
|
121
|
-
|
|
122
|
+
ReadModel: readModel.identifier,
|
|
122
123
|
EventSequenceId: readModel.eventSequenceId,
|
|
123
|
-
ReadModelKey: key
|
|
124
|
-
|
|
124
|
+
ReadModelKey: key,
|
|
125
|
+
Grouping: ''
|
|
126
|
+
}));
|
|
125
127
|
|
|
126
|
-
const snapshots: ReadModelSnapshot<TReadModel>[] = response.
|
|
127
|
-
readModel: this.deserializeReadModel(readModelType, snapshot.
|
|
128
|
+
const snapshots: ReadModelSnapshot<TReadModel>[] = response.map(snapshot => ({
|
|
129
|
+
readModel: this.deserializeReadModel(readModelType, snapshot.Instance),
|
|
128
130
|
events: (snapshot.Events ?? []) as AppendedEvent[],
|
|
129
131
|
occurred: snapshot.Occurred?.Value ? new Date(snapshot.Occurred.Value) : undefined,
|
|
130
132
|
correlationId: snapshot.CorrelationId
|
package/seeding/EventSeeding.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { diag } from '@opentelemetry/api';
|
|
5
5
|
import { IClientArtifactsProvider } from '../artifacts';
|
|
6
6
|
import { ChronicleConnection } from '../connection';
|
|
7
|
+
import { ensureCommandSuccess } from '../connection/callResults';
|
|
7
8
|
import { EventStoreNamespaceName } from '../EventStoreNamespaceName';
|
|
8
9
|
import { getEventTypeFor } from '../events/eventTypeDecorator';
|
|
9
10
|
import { getTagsFor } from '../events/tagDecorator';
|
|
@@ -94,7 +95,7 @@ export class EventSeeding implements IEventSeeding {
|
|
|
94
95
|
}))
|
|
95
96
|
};
|
|
96
97
|
|
|
97
|
-
await this._connection.eventSeeding.
|
|
98
|
+
ensureCommandSuccess('seed events', await this._connection.eventSeeding.seedEvents(request));
|
|
98
99
|
this._entries.length = 0;
|
|
99
100
|
}
|
|
100
101
|
|
package/webhooks/Webhooks.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Copyright (c) Cratis. All rights reserved.
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { type WebhookDefinition } from '@cratis/chronicle.contracts';
|
|
5
5
|
import { Constructor } from '@cratis/fundamentals';
|
|
6
6
|
import { IClientArtifactsProvider } from '../artifacts';
|
|
7
7
|
import { ChronicleConnection } from '../connection';
|
|
8
|
+
import { ensureCommandSuccess, ensureQuerySuccess } from '../connection/callResults';
|
|
8
9
|
import { EventSequenceId } from '../eventSequences/EventSequenceId';
|
|
9
10
|
import { IEventTypes } from '../events/IEventTypes';
|
|
10
11
|
import { IWebhook } from './IWebhook';
|
|
@@ -59,11 +60,10 @@ export class Webhooks implements IWebhooks {
|
|
|
59
60
|
return;
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
await this._connection.webhooks.
|
|
63
|
+
ensureCommandSuccess('register discovered webhooks', await this._connection.webhooks.addWebhooks({
|
|
63
64
|
EventStore: this._eventStore,
|
|
64
|
-
Owner: ObserverOwner.Client,
|
|
65
65
|
Webhooks: [...this._discovered.values()]
|
|
66
|
-
});
|
|
66
|
+
}));
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/** @inheritdoc */
|
|
@@ -79,26 +79,40 @@ export class Webhooks implements IWebhooks {
|
|
|
79
79
|
configure
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
await this._connection.webhooks.
|
|
82
|
+
ensureCommandSuccess('register webhook', await this._connection.webhooks.addWebhooks({
|
|
83
83
|
EventStore: this._eventStore,
|
|
84
|
-
Owner: ObserverOwner.Client,
|
|
85
84
|
Webhooks: [definition]
|
|
86
|
-
});
|
|
85
|
+
}));
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
/** @inheritdoc */
|
|
90
89
|
async getWebhooks(): Promise<WebhookDefinition[]> {
|
|
91
90
|
const response = await this._connection.webhooks.getWebhooks({ EventStore: this._eventStore });
|
|
92
|
-
|
|
91
|
+
const details = ensureQuerySuccess('get webhooks', response) ?? [];
|
|
92
|
+
|
|
93
|
+
// The kernel does not return authorization details on read-back, so the reconstructed target
|
|
94
|
+
// always carries no authorization, mirroring the C# client's WebhookTarget mapping.
|
|
95
|
+
return details.map(webhook => ({
|
|
96
|
+
EventSequenceId: webhook.EventSequenceId,
|
|
97
|
+
Identifier: webhook.Identifier,
|
|
98
|
+
EventTypes: webhook.EventTypes,
|
|
99
|
+
Target: {
|
|
100
|
+
Url: webhook.Url,
|
|
101
|
+
Authorization: undefined,
|
|
102
|
+
Headers: webhook.Headers
|
|
103
|
+
},
|
|
104
|
+
IsReplayable: webhook.IsReplayable,
|
|
105
|
+
IsActive: webhook.IsActive
|
|
106
|
+
}));
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
/** @inheritdoc */
|
|
96
110
|
async remove(webhookId: WebhookId | string): Promise<void> {
|
|
97
111
|
const id = typeof webhookId === 'string' ? webhookId : webhookId.value;
|
|
98
|
-
await this._connection.webhooks.
|
|
112
|
+
ensureCommandSuccess('remove webhook', await this._connection.webhooks.removeWebhooks({
|
|
99
113
|
EventStore: this._eventStore,
|
|
100
114
|
Webhooks: [id]
|
|
101
|
-
});
|
|
115
|
+
}));
|
|
102
116
|
}
|
|
103
117
|
|
|
104
118
|
private buildDefinition(
|