@cratis/chronicle 6.9.1 → 6.11.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 +52 -13
- package/ChronicleOptions.spec.ts +54 -0
- package/ChronicleOptions.ts +20 -5
- package/EventStore.ts +7 -1
- package/README.md +2 -3
- package/artifacts/DefaultClientArtifactsProvider.ts +4 -1
- package/connection/ChronicleConnection.auth.spec.ts +270 -0
- package/connection/ChronicleConnection.ts +90 -31
- package/connection/ChronicleServices.ts +1 -1
- package/connection/CompatibilityPreflight.ts +1 -1
- package/connection/RejectedChronicleCredentials.ts +10 -0
- package/connection/TokenProvider.spec.ts +5 -6
- package/connection/TokenProvider.ts +26 -17
- package/connection/clientVersion.spec.ts +27 -0
- package/connection/clientVersion.ts +9 -0
- package/connection/describeRequestError.spec.ts +22 -0
- package/connection/fetchOAuthAccessToken.ts +33 -2
- package/dist/ChronicleClient.d.ts +3 -0
- package/dist/ChronicleClient.d.ts.map +1 -1
- package/dist/ChronicleClient.js +55 -13
- package/dist/ChronicleClient.js.map +1 -1
- package/dist/ChronicleOptions.d.ts +5 -1
- package/dist/ChronicleOptions.d.ts.map +1 -1
- package/dist/ChronicleOptions.js +20 -5
- package/dist/ChronicleOptions.js.map +1 -1
- package/dist/EventStore.d.ts +2 -0
- package/dist/EventStore.d.ts.map +1 -1
- package/dist/EventStore.js +6 -1
- package/dist/EventStore.js.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.d.ts.map +1 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js +4 -1
- package/dist/artifacts/DefaultClientArtifactsProvider.js.map +1 -1
- package/dist/connection/ChronicleConnection.d.ts +3 -3
- package/dist/connection/ChronicleConnection.d.ts.map +1 -1
- package/dist/connection/ChronicleConnection.js +98 -26
- package/dist/connection/ChronicleConnection.js.map +1 -1
- package/dist/connection/ChronicleServices.d.ts +1 -2
- package/dist/connection/ChronicleServices.d.ts.map +1 -1
- package/dist/connection/CompatibilityPreflight.js +1 -1
- package/dist/connection/CompatibilityPreflight.js.map +1 -1
- package/dist/connection/RejectedChronicleCredentials.d.ts +5 -0
- package/dist/connection/RejectedChronicleCredentials.d.ts.map +1 -0
- package/dist/connection/RejectedChronicleCredentials.js +10 -0
- package/dist/connection/RejectedChronicleCredentials.js.map +1 -0
- package/dist/connection/TokenProvider.d.ts +8 -6
- package/dist/connection/TokenProvider.d.ts.map +1 -1
- package/dist/connection/TokenProvider.js +25 -11
- package/dist/connection/TokenProvider.js.map +1 -1
- package/dist/connection/clientVersion.d.ts +3 -0
- package/dist/connection/clientVersion.d.ts.map +1 -0
- package/dist/connection/clientVersion.js +7 -0
- package/dist/connection/clientVersion.js.map +1 -0
- package/dist/connection/fetchOAuthAccessToken.d.ts +12 -0
- package/dist/connection/fetchOAuthAccessToken.d.ts.map +1 -1
- package/dist/connection/fetchOAuthAccessToken.js +37 -2
- package/dist/connection/fetchOAuthAccessToken.js.map +1 -1
- package/dist/eventSequences/AppendResult.d.ts +7 -0
- package/dist/eventSequences/AppendResult.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.d.ts.map +1 -1
- package/dist/eventSequences/EventSequence.js +26 -17
- package/dist/eventSequences/EventSequence.js.map +1 -1
- package/dist/eventSequences/WaitForCompletionOptions.d.ts +8 -0
- package/dist/eventSequences/WaitForCompletionOptions.d.ts.map +1 -0
- package/dist/eventSequences/WaitForCompletionOptions.js +4 -0
- package/dist/eventSequences/WaitForCompletionOptions.js.map +1 -0
- package/dist/eventSequences/index.d.ts +1 -0
- package/dist/eventSequences/index.d.ts.map +1 -1
- package/dist/eventSequences/index.js.map +1 -1
- package/dist/externalServices/ExternalServiceBuilder.d.ts +1 -1
- package/dist/externalServices/ExternalServiceBuilder.d.ts.map +1 -1
- package/dist/externalServices/ExternalServiceBuilder.js +1 -1
- package/dist/externalServices/ExternalServiceBuilder.js.map +1 -1
- package/dist/externalServices/ExternalServices.d.ts.map +1 -1
- package/dist/externalServices/ExternalServices.js +3 -1
- package/dist/externalServices/ExternalServices.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/projections/Projections.d.ts.map +1 -1
- package/dist/projections/Projections.js +9 -7
- package/dist/projections/Projections.js.map +1 -1
- package/dist/projections/constantValueExpression.d.ts +3 -0
- package/dist/projections/constantValueExpression.d.ts.map +1 -0
- package/dist/projections/constantValueExpression.js +26 -0
- package/dist/projections/constantValueExpression.js.map +1 -0
- package/dist/projections/declarative/ChildrenBuilder.d.ts +2 -0
- package/dist/projections/declarative/ChildrenBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/ChildrenBuilder.js +5 -0
- package/dist/projections/declarative/ChildrenBuilder.js.map +1 -1
- package/dist/projections/declarative/FromBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/FromBuilder.js +3 -2
- package/dist/projections/declarative/FromBuilder.js.map +1 -1
- package/dist/projections/declarative/JoinBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/JoinBuilder.js +2 -1
- package/dist/projections/declarative/JoinBuilder.js.map +1 -1
- package/dist/projections/declarative/NestedBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/NestedBuilder.js +3 -1
- package/dist/projections/declarative/NestedBuilder.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.d.ts +5 -0
- package/dist/projections/declarative/ProjectionBuilderCore.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderCore.js +18 -2
- package/dist/projections/declarative/ProjectionBuilderCore.js.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderFor.d.ts +6 -0
- package/dist/projections/declarative/ProjectionBuilderFor.d.ts.map +1 -1
- package/dist/projections/declarative/ProjectionBuilderFor.js +5 -0
- package/dist/projections/declarative/ProjectionBuilderFor.js.map +1 -1
- package/dist/projections/declarative/SetBuilder.d.ts.map +1 -1
- package/dist/projections/declarative/SetBuilder.js +2 -1
- package/dist/projections/declarative/SetBuilder.js.map +1 -1
- package/dist/projections/modelBound/addFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/addFrom.js +3 -2
- package/dist/projections/modelBound/addFrom.js.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts +20 -0
- package/dist/projections/modelBound/childrenAndNestedBuilder.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenAndNestedBuilder.js +14 -11
- package/dist/projections/modelBound/childrenAndNestedBuilder.js.map +1 -1
- package/dist/projections/modelBound/childrenFrom.d.ts +4 -2
- package/dist/projections/modelBound/childrenFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/childrenFrom.js +8 -4
- package/dist/projections/modelBound/childrenFrom.js.map +1 -1
- package/dist/projections/modelBound/clearWith.d.ts.map +1 -1
- package/dist/projections/modelBound/clearWith.js +3 -2
- package/dist/projections/modelBound/clearWith.js.map +1 -1
- package/dist/projections/modelBound/count.d.ts.map +1 -1
- package/dist/projections/modelBound/count.js +3 -2
- package/dist/projections/modelBound/count.js.map +1 -1
- package/dist/projections/modelBound/decrement.d.ts.map +1 -1
- package/dist/projections/modelBound/decrement.js +3 -2
- package/dist/projections/modelBound/decrement.js.map +1 -1
- package/dist/projections/modelBound/fromAll.d.ts.map +1 -1
- package/dist/projections/modelBound/fromAll.js +3 -2
- package/dist/projections/modelBound/fromAll.js.map +1 -1
- package/dist/projections/modelBound/fromEvery.d.ts.map +1 -1
- package/dist/projections/modelBound/fromEvery.js +3 -2
- package/dist/projections/modelBound/fromEvery.js.map +1 -1
- package/dist/projections/modelBound/increment.d.ts.map +1 -1
- package/dist/projections/modelBound/increment.js +3 -2
- package/dist/projections/modelBound/increment.js.map +1 -1
- package/dist/projections/modelBound/join.d.ts.map +1 -1
- package/dist/projections/modelBound/join.js +3 -2
- package/dist/projections/modelBound/join.js.map +1 -1
- package/dist/projections/modelBound/nested.d.ts.map +1 -1
- package/dist/projections/modelBound/nested.js +3 -2
- package/dist/projections/modelBound/nested.js.map +1 -1
- package/dist/projections/modelBound/noAutoMap.d.ts.map +1 -1
- package/dist/projections/modelBound/noAutoMap.js +3 -2
- package/dist/projections/modelBound/noAutoMap.js.map +1 -1
- package/dist/projections/modelBound/removedWith.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWith.js +3 -2
- package/dist/projections/modelBound/removedWith.js.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.d.ts.map +1 -1
- package/dist/projections/modelBound/removedWithJoin.js +3 -2
- package/dist/projections/modelBound/removedWithJoin.js.map +1 -1
- package/dist/projections/modelBound/setFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/setFrom.js +3 -2
- package/dist/projections/modelBound/setFrom.js.map +1 -1
- package/dist/projections/modelBound/setFromContext.d.ts.map +1 -1
- package/dist/projections/modelBound/setFromContext.js +3 -2
- package/dist/projections/modelBound/setFromContext.js.map +1 -1
- package/dist/projections/modelBound/setValue.d.ts.map +1 -1
- package/dist/projections/modelBound/setValue.js +3 -2
- package/dist/projections/modelBound/setValue.js.map +1 -1
- package/dist/projections/modelBound/subtractFrom.d.ts.map +1 -1
- package/dist/projections/modelBound/subtractFrom.js +3 -2
- package/dist/projections/modelBound/subtractFrom.js.map +1 -1
- package/dist/projections/notSetPropertyPath.d.ts +3 -0
- package/dist/projections/notSetPropertyPath.d.ts.map +1 -0
- package/dist/projections/notSetPropertyPath.js +5 -0
- package/dist/projections/notSetPropertyPath.js.map +1 -0
- package/dist/reactors/Reactors.d.ts +4 -0
- package/dist/reactors/Reactors.d.ts.map +1 -1
- package/dist/reactors/Reactors.js +24 -6
- package/dist/reactors/Reactors.js.map +1 -1
- package/dist/reactors/reactor.d.ts +2 -2
- package/dist/reactors/reactor.js +2 -2
- package/dist/readModels/IReadModels.d.ts +3 -2
- package/dist/readModels/IReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.d.ts.map +1 -1
- package/dist/readModels/MaterializedReadModels.js +2 -4
- package/dist/readModels/MaterializedReadModels.js.map +1 -1
- package/dist/readModels/ReadModels.d.ts +3 -5
- package/dist/readModels/ReadModels.d.ts.map +1 -1
- package/dist/readModels/ReadModels.js +17 -14
- package/dist/readModels/ReadModels.js.map +1 -1
- package/dist/readModels/deserializeReadModel.d.ts +5 -0
- package/dist/readModels/deserializeReadModel.d.ts.map +1 -0
- package/dist/readModels/deserializeReadModel.js +84 -0
- package/dist/readModels/deserializeReadModel.js.map +1 -0
- package/dist/readModels/rootReadModelTypes.d.ts +5 -0
- package/dist/readModels/rootReadModelTypes.d.ts.map +1 -0
- package/dist/readModels/rootReadModelTypes.js +69 -0
- package/dist/readModels/rootReadModelTypes.js.map +1 -0
- package/dist/reducers/Reducers.d.ts +4 -0
- package/dist/reducers/Reducers.d.ts.map +1 -1
- package/dist/reducers/Reducers.js +24 -6
- package/dist/reducers/Reducers.js.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.d.ts.map +1 -1
- package/dist/schemas/JsonSchemaGenerator.js +4 -4
- package/dist/schemas/JsonSchemaGenerator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/TypeDiscoverer.d.ts +8 -1
- package/dist/types/TypeDiscoverer.d.ts.map +1 -1
- package/dist/types/TypeDiscoverer.js +25 -5
- package/dist/types/TypeDiscoverer.js.map +1 -1
- package/dist/types/TypeIntrospector.d.ts.map +1 -1
- package/dist/types/TypeIntrospector.js +3 -2
- package/dist/types/TypeIntrospector.js.map +1 -1
- package/dist/types/conceptValueType.d.ts +4 -0
- package/dist/types/conceptValueType.d.ts.map +1 -0
- package/dist/types/conceptValueType.js +10 -0
- package/dist/types/conceptValueType.js.map +1 -0
- package/dist/types/modelBoundProperty.d.ts +6 -0
- package/dist/types/modelBoundProperty.d.ts.map +1 -0
- package/dist/types/modelBoundProperty.js +53 -0
- package/dist/types/modelBoundProperty.js.map +1 -0
- package/eventSequences/AppendResult.ts +8 -0
- package/eventSequences/EventSequence.spec.ts +76 -1
- package/eventSequences/EventSequence.ts +28 -20
- package/eventSequences/WaitForCompletionOptions.ts +11 -0
- package/eventSequences/index.ts +1 -0
- package/externalServices/ExternalServiceBuilder.ts +4 -4
- package/externalServices/ExternalServices.spec.ts +87 -0
- package/externalServices/ExternalServices.ts +3 -1
- package/for_ChronicleClient/connectionAuthentication.spec.ts +67 -0
- package/index.ts +1 -0
- package/observation/observationDisposal.spec.ts +74 -0
- package/package.json +2 -1
- package/projections/Projections.childrenAndNested.spec.ts +2 -0
- package/projections/Projections.modelBoundCompleteness.spec.ts +47 -0
- package/projections/Projections.ts +9 -7
- package/projections/constantValueExpression.spec.ts +31 -0
- package/projections/constantValueExpression.ts +28 -0
- package/projections/declarative/ChildrenBuilder.spec.ts +46 -1
- package/projections/declarative/ChildrenBuilder.ts +6 -0
- package/projections/declarative/FromBuilder.ts +3 -2
- package/projections/declarative/JoinBuilder.ts +2 -1
- package/projections/declarative/NestedBuilder.ts +3 -1
- package/projections/declarative/ProjectionBuilderCore.ts +20 -2
- package/projections/declarative/ProjectionBuilderFor.spec.ts +61 -4
- package/projections/declarative/ProjectionBuilderFor.ts +7 -0
- package/projections/declarative/SetBuilder.ts +2 -1
- package/projections/modelBound/addFrom.ts +3 -2
- package/projections/modelBound/childrenAndNestedBuilder.ts +14 -11
- package/projections/modelBound/childrenFrom.ts +11 -5
- package/projections/modelBound/clearWith.ts +3 -2
- package/projections/modelBound/count.ts +3 -2
- package/projections/modelBound/decorators.standard.spec.ts +45 -0
- package/projections/modelBound/decrement.ts +3 -2
- package/projections/modelBound/fromAll.ts +3 -2
- package/projections/modelBound/fromEvery.ts +3 -2
- package/projections/modelBound/increment.ts +3 -2
- package/projections/modelBound/join.ts +3 -2
- package/projections/modelBound/nested.ts +3 -2
- package/projections/modelBound/noAutoMap.ts +3 -2
- package/projections/modelBound/removedWith.ts +3 -2
- package/projections/modelBound/removedWithJoin.ts +3 -2
- package/projections/modelBound/setFrom.ts +3 -2
- package/projections/modelBound/setFromContext.ts +3 -2
- package/projections/modelBound/setValue.ts +3 -2
- package/projections/modelBound/subtractFrom.ts +3 -2
- package/projections/notSetPropertyPath.ts +5 -0
- package/reactors/Reactors.ts +22 -7
- package/reactors/reactor.ts +2 -2
- package/readModels/IReadModels.ts +3 -2
- package/readModels/MaterializedReadModels.spec.ts +22 -0
- package/readModels/MaterializedReadModels.ts +2 -4
- package/readModels/ReadModels.spec.ts +71 -6
- package/readModels/ReadModels.ts +18 -15
- package/readModels/conceptValueType.spec.ts +20 -0
- package/readModels/deserializeReadModel.spec.ts +84 -0
- package/readModels/deserializeReadModel.ts +82 -0
- package/readModels/discovery.spec.ts +27 -0
- package/readModels/rootReadModelTypes.spec.ts +115 -0
- package/readModels/rootReadModelTypes.ts +65 -0
- package/reducers/Reducers.ts +22 -7
- package/schemas/JsonSchemaGenerator.ts +4 -4
- package/schemas/legacyDesignMetadata.spec.ts +24 -2
- package/types/TypeDiscoverer.spec.ts +57 -0
- package/types/TypeDiscoverer.ts +27 -6
- package/types/TypeIntrospector.ts +3 -2
- package/types/conceptValueType.ts +11 -0
- package/types/modelBoundProperty.ts +53 -0
- package/dist/externalServices/ExternalServicesContracts.d.ts +0 -137
- package/dist/externalServices/ExternalServicesContracts.d.ts.map +0 -1
- package/dist/externalServices/ExternalServicesContracts.js +0 -654
- package/dist/externalServices/ExternalServicesContracts.js.map +0 -1
- package/externalServices/ExternalServicesContracts.ts +0 -709
|
@@ -558,6 +558,81 @@ describe('EventSequence', () => {
|
|
|
558
558
|
const options = waitForCompletion.mock.calls[0][1];
|
|
559
559
|
expect(options.signal).toBeInstanceOf(AbortSignal);
|
|
560
560
|
});
|
|
561
|
+
|
|
562
|
+
it('should clear the timeout after completion', async () => {
|
|
563
|
+
vi.useFakeTimers();
|
|
564
|
+
try {
|
|
565
|
+
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
566
|
+
await appendResult.waitForCompletion({ timeoutMs: 1000 });
|
|
567
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
568
|
+
} finally {
|
|
569
|
+
vi.useRealTimers();
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
describe('when a caller cancels a completion wait', () => {
|
|
575
|
+
it('should reject a pre-aborted signal without starting the RPC or a timer', async () => {
|
|
576
|
+
const { eventSequence, waitForCompletion } = createEventSequence();
|
|
577
|
+
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
578
|
+
const controller = new AbortController();
|
|
579
|
+
const reason = new Error('already canceled');
|
|
580
|
+
controller.abort(reason);
|
|
581
|
+
vi.useFakeTimers();
|
|
582
|
+
try {
|
|
583
|
+
await expect(appendResult.waitForCompletion({ signal: controller.signal })).rejects.toBe(reason);
|
|
584
|
+
expect(waitForCompletion).not.toHaveBeenCalled();
|
|
585
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
586
|
+
} finally {
|
|
587
|
+
vi.useRealTimers();
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should abort the underlying RPC and reject with the caller reason', async () => {
|
|
592
|
+
const waitForCompletion = vi.fn().mockImplementation((_request, { signal }: { signal: AbortSignal }) =>
|
|
593
|
+
new Promise((_resolve, reject) => signal.addEventListener('abort', () => reject(signal.reason), { once: true })));
|
|
594
|
+
const { eventSequence } = createEventSequence({}, { waitForCompletion });
|
|
595
|
+
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
596
|
+
const controller = new AbortController();
|
|
597
|
+
const reason = new Error('caller canceled');
|
|
598
|
+
const pending = appendResult.waitForCompletion({ timeoutMs: 1000, signal: controller.signal });
|
|
599
|
+
const signal = waitForCompletion.mock.calls[0][1].signal as AbortSignal;
|
|
600
|
+
|
|
601
|
+
controller.abort(reason);
|
|
602
|
+
|
|
603
|
+
await expect(pending).rejects.toBe(reason);
|
|
604
|
+
expect(signal.aborted).toBe(true);
|
|
605
|
+
expect(signal.reason).toBe(reason);
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should clear the timeout when the caller aborts', async () => {
|
|
609
|
+
const waitForCompletion = vi.fn().mockImplementation((_request, { signal }: { signal: AbortSignal }) =>
|
|
610
|
+
new Promise((_resolve, reject) => signal.addEventListener('abort', () => reject(signal.reason), { once: true })));
|
|
611
|
+
const { eventSequence } = createEventSequence({}, { waitForCompletion });
|
|
612
|
+
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
613
|
+
const controller = new AbortController();
|
|
614
|
+
vi.useFakeTimers();
|
|
615
|
+
try {
|
|
616
|
+
const pending = appendResult.waitForCompletion({ timeoutMs: 1000, signal: controller.signal });
|
|
617
|
+
expect(vi.getTimerCount()).toBe(1);
|
|
618
|
+
const reason = new Error('caller canceled');
|
|
619
|
+
controller.abort(reason);
|
|
620
|
+
await expect(pending).rejects.toBe(reason);
|
|
621
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
622
|
+
} finally {
|
|
623
|
+
vi.useRealTimers();
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('should cancel the underlying RPC when the options timeout expires', async () => {
|
|
628
|
+
const waitForCompletion = vi.fn().mockImplementation((_request, { signal }: { signal: AbortSignal }) =>
|
|
629
|
+
new Promise((_resolve, reject) => signal.addEventListener('abort', () => reject(signal.reason), { once: true })));
|
|
630
|
+
const { eventSequence } = createEventSequence({}, { waitForCompletion });
|
|
631
|
+
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
632
|
+
|
|
633
|
+
await expect(appendResult.waitForCompletion({ timeoutMs: 5 })).rejects.toMatchObject({ name: 'TimeoutError' });
|
|
634
|
+
expect(waitForCompletion.mock.calls[0][1].signal.aborted).toBe(true);
|
|
635
|
+
});
|
|
561
636
|
});
|
|
562
637
|
|
|
563
638
|
describe('when waiting for completion and observers report a failed partition', () => {
|
|
@@ -577,7 +652,7 @@ describe('EventSequence', () => {
|
|
|
577
652
|
|
|
578
653
|
it('should report failure with the failed partitions', async () => {
|
|
579
654
|
const appendResult = await eventSequence.append('some-event-source', new SomethingHappened('a'));
|
|
580
|
-
const result = await appendResult.waitForCompletion();
|
|
655
|
+
const result = await appendResult.waitForCompletion({ timeoutMs: 1234 });
|
|
581
656
|
|
|
582
657
|
expect(result.isSuccess).toBe(false);
|
|
583
658
|
expect(result.failedPartitions).toHaveLength(1);
|
|
@@ -29,6 +29,7 @@ import { EventSequenceId } from './EventSequenceId.js';
|
|
|
29
29
|
import { EventSequenceNumber } from './EventSequenceNumber.js';
|
|
30
30
|
import { TransactionalEventSequence } from './TransactionalEventSequence.js';
|
|
31
31
|
import { WaitForCompletionResult } from './WaitForCompletionResult.js';
|
|
32
|
+
import type { WaitForCompletionOptions } from './WaitForCompletionOptions.js';
|
|
32
33
|
|
|
33
34
|
/** Default timeout for {@link AppendResult.waitForCompletion}, matching the C# client's default. */
|
|
34
35
|
const DEFAULT_WAIT_FOR_COMPLETION_TIMEOUT_MS = 5000;
|
|
@@ -725,7 +726,7 @@ export class EventSequence implements IEventSequence {
|
|
|
725
726
|
concurrencyViolation: mappedConcurrencyViolation,
|
|
726
727
|
errors: mappedErrors,
|
|
727
728
|
isSuccess,
|
|
728
|
-
waitForCompletion: (
|
|
729
|
+
waitForCompletion: (options?: number | WaitForCompletionOptions) => this.waitForObserverCompletion(eventSequenceNumber, isSuccess, options)
|
|
729
730
|
};
|
|
730
731
|
}
|
|
731
732
|
|
|
@@ -736,25 +737,38 @@ export class EventSequence implements IEventSequence {
|
|
|
736
737
|
private async waitForObserverCompletion(
|
|
737
738
|
tailSequenceNumber: EventSequenceNumber,
|
|
738
739
|
appendWasSuccessful: boolean,
|
|
739
|
-
|
|
740
|
+
options: number | WaitForCompletionOptions = DEFAULT_WAIT_FOR_COMPLETION_TIMEOUT_MS
|
|
740
741
|
): Promise<WaitForCompletionResult> {
|
|
741
742
|
if (!appendWasSuccessful) {
|
|
742
743
|
return { isSuccess: true, failedPartitions: [] };
|
|
743
744
|
}
|
|
744
745
|
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
746
|
+
const timeoutMs = typeof options === 'number' ? options : options.timeoutMs ?? DEFAULT_WAIT_FOR_COMPLETION_TIMEOUT_MS;
|
|
747
|
+
const callerSignal = typeof options === 'number' ? undefined : options.signal;
|
|
748
|
+
if (callerSignal?.aborted) throw callerSignal.reason;
|
|
749
|
+
|
|
750
|
+
const controller = new AbortController();
|
|
751
|
+
const forwardAbort = () => controller.abort(callerSignal?.reason);
|
|
752
|
+
callerSignal?.addEventListener('abort', forwardAbort, { once: true });
|
|
753
|
+
const timer = setTimeout(() => controller.abort(new DOMException('The operation was aborted due to timeout', 'TimeoutError')), timeoutMs);
|
|
754
|
+
try {
|
|
755
|
+
const response = await this._connection.observers.waitForCompletion(
|
|
756
|
+
{
|
|
757
|
+
EventStore: this._eventStoreName,
|
|
758
|
+
Namespace: this._namespace,
|
|
759
|
+
EventSequenceId: this.id.value,
|
|
760
|
+
TailEventSequenceNumber: tailSequenceNumber.value
|
|
761
|
+
},
|
|
762
|
+
{ signal: controller.signal });
|
|
753
763
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
764
|
+
return {
|
|
765
|
+
isSuccess: response.IsSuccess,
|
|
766
|
+
failedPartitions: (response.FailedPartitions ?? []).map(failedPartition => toClientFailedPartition(failedPartition))
|
|
767
|
+
};
|
|
768
|
+
} finally {
|
|
769
|
+
clearTimeout(timer);
|
|
770
|
+
callerSignal?.removeEventListener('abort', forwardAbort);
|
|
771
|
+
}
|
|
758
772
|
}
|
|
759
773
|
|
|
760
774
|
private toContractConcurrencyScope(scope?: ConcurrencyScope) {
|
|
@@ -776,12 +790,6 @@ export class EventSequence implements IEventSequence {
|
|
|
776
790
|
}
|
|
777
791
|
}
|
|
778
792
|
|
|
779
|
-
/**
|
|
780
|
-
* Converts a RFC 4122 Guid string into the protobuf Guid shape used by Chronicle contracts.
|
|
781
|
-
* @param guid - The Guid to convert.
|
|
782
|
-
* @returns The converted protobuf Guid with fixed64-safe hi/lo values.
|
|
783
|
-
*/
|
|
784
|
-
|
|
785
793
|
/**
|
|
786
794
|
* Converts an {@link Identity} into the CausedBy shape used by Chronicle contracts.
|
|
787
795
|
* @param identity - The identity to convert.
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
/** Options for waiting until observers complete an append. */
|
|
5
|
+
export interface WaitForCompletionOptions {
|
|
6
|
+
/** Timeout in milliseconds. Defaults to 5000 (5 seconds). */
|
|
7
|
+
readonly timeoutMs?: number;
|
|
8
|
+
|
|
9
|
+
/** Cancels the underlying wait RPC when aborted. */
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
}
|
package/eventSequences/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export type { CompleteStreamResult } from './CompleteStreamResult.js';
|
|
|
14
14
|
export type { ConcurrencyScope } from './ConcurrencyScope.js';
|
|
15
15
|
export type { EventForEventSourceId } from './EventForEventSourceId.js';
|
|
16
16
|
export type { WaitForCompletionResult } from './WaitForCompletionResult.js';
|
|
17
|
+
export type { WaitForCompletionOptions } from './WaitForCompletionOptions.js';
|
|
17
18
|
export type { IEventSequence } from './IEventSequence.js';
|
|
18
19
|
export type { IEventLog } from './IEventLog.js';
|
|
19
20
|
export type { ITransactionalEventSequence } from './ITransactionalEventSequence.js';
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
ExternalServiceDefinition,
|
|
6
|
-
ExternalServiceEndpoint,
|
|
5
|
+
type ExternalServiceDefinition,
|
|
6
|
+
type ExternalServiceEndpoint,
|
|
7
7
|
ExternalServiceEndpointType,
|
|
8
|
-
OneOfBasicAuthorizationBearerTokenAuthorizationOAuthAuthorization
|
|
9
|
-
} from '
|
|
8
|
+
type OneOfBasicAuthorizationBearerTokenAuthorizationOAuthAuthorization
|
|
9
|
+
} from '@cratis/chronicle.contracts';
|
|
10
10
|
import { IExternalServiceBuilder } from './IExternalServiceBuilder.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -0,0 +1,87 @@
|
|
|
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 { AddExternalServicesRequest, ExternalServiceEndpointType } from '@cratis/chronicle.contracts';
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import type { ChronicleConnection } from '../connection/index.js';
|
|
7
|
+
import { ChronicleCallFailed } from '../connection/callResults.js';
|
|
8
|
+
import type { IExternalServiceBuilder } from './IExternalServiceBuilder.js';
|
|
9
|
+
import { ExternalServices } from './ExternalServices.js';
|
|
10
|
+
|
|
11
|
+
function createServices(result = { IsAuthorized: true, ValidationResults: [], ExceptionMessages: [], AuthorizationFailureReason: '' }) {
|
|
12
|
+
const addExternalServices = vi.fn().mockResolvedValue(result);
|
|
13
|
+
const connection = { externalServices: { addExternalServices } } as unknown as ChronicleConnection;
|
|
14
|
+
return { services: new ExternalServices('my-event-store', connection), addExternalServices };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function requestFor(configure: (builder: IExternalServiceBuilder) => void): Promise<AddExternalServicesRequest> {
|
|
18
|
+
const { services, addExternalServices } = createServices();
|
|
19
|
+
await services.register('Customers', configure);
|
|
20
|
+
expect(addExternalServices).toHaveBeenCalledTimes(1);
|
|
21
|
+
// Round-trip through the generated codec to assert the actual wire request, not just the in-memory object.
|
|
22
|
+
const request = AddExternalServicesRequest.fromPartial(addExternalServices.mock.calls[0][0]);
|
|
23
|
+
return AddExternalServicesRequest.decode(AddExternalServicesRequest.encode(request).finish());
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('ExternalServices registration', () => {
|
|
27
|
+
it('sends an HTTP bearer endpoint with headers', async () => {
|
|
28
|
+
expect(await requestFor(builder => builder.http('https://example.com')
|
|
29
|
+
.withBearerToken('secret').withHeader('X-Tenant', 'acme').withHeader('X-Trace', 'enabled'))).toEqual({
|
|
30
|
+
EventStore: 'my-event-store',
|
|
31
|
+
ExternalServices: [{
|
|
32
|
+
Id: 'Customers', Name: 'Customers', Endpoint: {
|
|
33
|
+
Type: ExternalServiceEndpointType.Http,
|
|
34
|
+
Http: {
|
|
35
|
+
Url: 'https://example.com', Headers: { 'X-Tenant': 'acme', 'X-Trace': 'enabled' },
|
|
36
|
+
Authorization: { Value0: undefined, Value1: { Token: 'secret' }, Value2: undefined }
|
|
37
|
+
},
|
|
38
|
+
Database: undefined
|
|
39
|
+
}
|
|
40
|
+
}]
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('sends basic authorization', async () => {
|
|
45
|
+
const request = await requestFor(builder => builder.http('https://example.com').withBasicAuth('user', 'password'));
|
|
46
|
+
expect(request.ExternalServices[0].Endpoint?.Http?.Authorization).toEqual({
|
|
47
|
+
Value0: { Username: 'user', Password: 'password' }, Value1: undefined, Value2: undefined
|
|
48
|
+
});
|
|
49
|
+
expect(request.ExternalServices[0].Endpoint?.Http?.Headers).toEqual({});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('sends OAuth authorization', async () => {
|
|
53
|
+
const request = await requestFor(builder => builder.http('https://example.com').withOAuth('https://auth', 'client', 'secret'));
|
|
54
|
+
expect(request.ExternalServices[0].Endpoint?.Http?.Authorization).toEqual({
|
|
55
|
+
Value0: undefined, Value1: undefined,
|
|
56
|
+
Value2: { Authority: 'https://auth', ClientId: 'client', ClientSecret: 'secret' }
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it.each([
|
|
61
|
+
['msSql', ExternalServiceEndpointType.MsSql, 1433],
|
|
62
|
+
['postgreSql', ExternalServiceEndpointType.PostgreSql, 5432]
|
|
63
|
+
] as const)('sends %s with a port and options', async (method, type, port) => {
|
|
64
|
+
const request = await requestFor(builder => builder[method]('db.example.com', 'customers', 'user', 'password', port)
|
|
65
|
+
.withOption('sslmode', 'require'));
|
|
66
|
+
expect(request.ExternalServices[0]).toEqual({
|
|
67
|
+
Id: 'Customers', Name: 'Customers', Endpoint: {
|
|
68
|
+
Type: type, Http: undefined,
|
|
69
|
+
Database: {
|
|
70
|
+
Host: 'db.example.com', Port: port, Database: 'customers',
|
|
71
|
+
Username: 'user', Password: 'password', Options: { sslmode: 'require' }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('rejects a failed command result', async () => {
|
|
78
|
+
const { services } = createServices({
|
|
79
|
+
IsAuthorized: true, ValidationResults: [{ Message: 'invalid endpoint', Members: ['Endpoint'] }],
|
|
80
|
+
ExceptionMessages: [], AuthorizationFailureReason: ''
|
|
81
|
+
});
|
|
82
|
+
await expect(services.register('Customers', builder => builder.http('https://example.com')))
|
|
83
|
+
.rejects.toThrow(ChronicleCallFailed);
|
|
84
|
+
await expect(services.register('Customers', builder => builder.http('https://example.com')))
|
|
85
|
+
.rejects.toThrow(/register external service.*invalid endpoint/);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
3
3
|
|
|
4
4
|
import { ChronicleConnection } from '../connection/index.js';
|
|
5
|
+
import { ensureCommandSuccess } from '../connection/callResults.js';
|
|
5
6
|
import { ExternalServiceBuilder } from './ExternalServiceBuilder.js';
|
|
6
7
|
import { IExternalServiceBuilder } from './IExternalServiceBuilder.js';
|
|
7
8
|
import { IExternalServices } from './IExternalServices.js';
|
|
@@ -26,9 +27,10 @@ export class ExternalServices implements IExternalServices {
|
|
|
26
27
|
configure(builder);
|
|
27
28
|
const definition = builder.build(name, name);
|
|
28
29
|
|
|
29
|
-
await this._connection.externalServices.
|
|
30
|
+
const result = await this._connection.externalServices.addExternalServices({
|
|
30
31
|
EventStore: this._eventStore,
|
|
31
32
|
ExternalServices: [definition]
|
|
32
33
|
});
|
|
34
|
+
ensureCommandSuccess('register external service', result);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { ChronicleClient } from '../ChronicleClient.js';
|
|
6
|
+
import { ChronicleOptions } from '../ChronicleOptions.js';
|
|
7
|
+
import { RejectedChronicleCredentials } from '../connection/RejectedChronicleCredentials.js';
|
|
8
|
+
|
|
9
|
+
const transport = vi.hoisted(() => ({ connect: vi.fn(), resetChannel: vi.fn(), disconnect: vi.fn(), getVersionInfo: vi.fn(), allEventStores: vi.fn() }));
|
|
10
|
+
vi.mock('../connection/ChronicleConnection.js', () => ({
|
|
11
|
+
ChronicleConnection: class {
|
|
12
|
+
connect = transport.connect;
|
|
13
|
+
resetChannel = transport.resetChannel;
|
|
14
|
+
disconnect = transport.disconnect;
|
|
15
|
+
server = { getVersionInfo: transport.getVersionInfo };
|
|
16
|
+
eventStores = { allEventStores: transport.allEventStores };
|
|
17
|
+
}
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('../connection/KernelKeepAlive.js', () => ({ KernelKeepAlive: class { async start() {} } }));
|
|
20
|
+
|
|
21
|
+
let client: ChronicleClient;
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
vi.useFakeTimers();
|
|
24
|
+
vi.clearAllMocks();
|
|
25
|
+
transport.connect.mockResolvedValue(undefined);
|
|
26
|
+
transport.resetChannel.mockResolvedValue(undefined);
|
|
27
|
+
transport.getVersionInfo.mockResolvedValue({});
|
|
28
|
+
transport.allEventStores.mockResolvedValue({ IsAuthorized: true, Data: [] });
|
|
29
|
+
client = new ChronicleClient(ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { discoveryPatterns: [] }));
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => { client.dispose(); vi.useRealTimers(); });
|
|
32
|
+
|
|
33
|
+
describe('client authentication recovery', () => {
|
|
34
|
+
it('connects when an anonymous kernel accepts calls despite an unavailable token endpoint', async () => {
|
|
35
|
+
await expect(client.getEventStores()).resolves.toEqual([]);
|
|
36
|
+
expect(transport.connect).toHaveBeenCalledTimes(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('preserves a typed terminal error when an established call enters withReconnect', async () => {
|
|
40
|
+
await client.getEventStores();
|
|
41
|
+
transport.allEventStores.mockRejectedValue(new RejectedChronicleCredentials('rejected'));
|
|
42
|
+
await expect(client.getEventStores()).rejects.toBeInstanceOf(RejectedChronicleCredentials);
|
|
43
|
+
await expect(client.getEventStores()).rejects.toThrow('rejected');
|
|
44
|
+
expect(transport.connect).toHaveBeenCalledTimes(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('makes a terminal error on the retried call permanent', async () => {
|
|
48
|
+
await client.getEventStores();
|
|
49
|
+
const rejection = new RejectedChronicleCredentials('rejected after reconnect');
|
|
50
|
+
transport.allEventStores.mockRejectedValueOnce(Object.assign(new Error('unavailable'), { code: 14 }))
|
|
51
|
+
.mockRejectedValueOnce(rejection);
|
|
52
|
+
await expect(client.getEventStores()).rejects.toBe(rejection);
|
|
53
|
+
const calls = transport.allEventStores.mock.calls.length;
|
|
54
|
+
await expect(client.getEventStores()).rejects.toBe(rejection);
|
|
55
|
+
expect(transport.allEventStores).toHaveBeenCalledTimes(calls);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('continues retrying a network outage until disposed', async () => {
|
|
59
|
+
transport.connect.mockRejectedValue(new Error('UNAVAILABLE: connection refused'));
|
|
60
|
+
const pending = client.getEventStores().then(() => undefined, error => error as Error);
|
|
61
|
+
await vi.advanceTimersByTimeAsync(4500);
|
|
62
|
+
expect(transport.connect.mock.calls.length).toBeGreaterThan(1);
|
|
63
|
+
client.dispose();
|
|
64
|
+
await vi.runAllTimersAsync();
|
|
65
|
+
expect((await pending).message).toMatch(/disposed/);
|
|
66
|
+
});
|
|
67
|
+
});
|
package/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ import * as confidentiality from './confidentiality/index.js';
|
|
|
27
27
|
|
|
28
28
|
export { ChronicleClient } from './ChronicleClient.js';
|
|
29
29
|
export { IncompatibleChronicleServer } from './connection/IncompatibleChronicleServer.js';
|
|
30
|
+
export { RejectedChronicleCredentials } from './connection/RejectedChronicleCredentials.js';
|
|
30
31
|
export type { IChronicleClient } from './IChronicleClient.js';
|
|
31
32
|
export { ChronicleOptions } from './ChronicleOptions.js';
|
|
32
33
|
export { EventStore } from './EventStore.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
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 { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import type { Constructor } from '@cratis/fundamentals';
|
|
6
|
+
import type { IClientArtifactsProvider } from '../artifacts/index.js';
|
|
7
|
+
import { ChronicleClient } from '../ChronicleClient.js';
|
|
8
|
+
import { ChronicleOptions } from '../ChronicleOptions.js';
|
|
9
|
+
import type { ChronicleConnection } from '../connection/index.js';
|
|
10
|
+
import { ConnectionLifecycle } from '../connection/ConnectionLifecycle.js';
|
|
11
|
+
import type { EventStore } from '../EventStore.js';
|
|
12
|
+
import type { IEventLog } from '../eventSequences/IEventLog.js';
|
|
13
|
+
import { Reactors } from '../reactors/Reactors.js';
|
|
14
|
+
import { reactor } from '../reactors/reactor.js';
|
|
15
|
+
import { Reducers } from '../reducers/Reducers.js';
|
|
16
|
+
import { reducer } from '../reducers/reducer.js';
|
|
17
|
+
|
|
18
|
+
class ExampleReactor {}
|
|
19
|
+
reactor('disposal-reactor')(ExampleReactor);
|
|
20
|
+
class ExampleReducer {}
|
|
21
|
+
reducer('disposal-reducer')(ExampleReducer);
|
|
22
|
+
|
|
23
|
+
const artifacts = {
|
|
24
|
+
reactors: [ExampleReactor as Constructor], reducers: [ExampleReducer as Constructor],
|
|
25
|
+
eventTypes: [], readModels: []
|
|
26
|
+
} as unknown as IClientArtifactsProvider;
|
|
27
|
+
|
|
28
|
+
function stream() {
|
|
29
|
+
const signals: AbortSignal[] = [];
|
|
30
|
+
const observe = vi.fn((_queue: AsyncIterable<unknown>, options: { signal: AbortSignal }) => {
|
|
31
|
+
signals.push(options.signal);
|
|
32
|
+
return {
|
|
33
|
+
async *[Symbol.asyncIterator]() {
|
|
34
|
+
await new Promise<void>(resolve => options.signal.addEventListener('abort', () => resolve(), { once: true }));
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
return { observe, signals };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('observation disposal', () => {
|
|
42
|
+
it('aborts reactor and reducer streams even without a connected lifecycle and prevents re-observation', async () => {
|
|
43
|
+
const reactorStream = stream();
|
|
44
|
+
const reducerStream = stream();
|
|
45
|
+
const connection = {
|
|
46
|
+
reactors: { observe: reactorStream.observe }, reducers: { observe: reducerStream.observe },
|
|
47
|
+
readModels: { registerMany: vi.fn().mockResolvedValue({}) }
|
|
48
|
+
} as unknown as ChronicleConnection;
|
|
49
|
+
const lifecycle = new ConnectionLifecycle();
|
|
50
|
+
const reactors = new Reactors(artifacts, connection, 'store', 'Default', lifecycle, {} as IEventLog);
|
|
51
|
+
const reducers = new Reducers(artifacts, connection, 'store', 'Default', lifecycle, 'sink');
|
|
52
|
+
await reactors.register();
|
|
53
|
+
await reducers.register();
|
|
54
|
+
expect(reactorStream.observe).toHaveBeenCalledOnce();
|
|
55
|
+
expect(reducerStream.observe).toHaveBeenCalledOnce();
|
|
56
|
+
|
|
57
|
+
reactors.dispose();
|
|
58
|
+
reducers.dispose();
|
|
59
|
+
expect(reactorStream.signals[0].aborted).toBe(true);
|
|
60
|
+
expect(reducerStream.signals[0].aborted).toBe(true);
|
|
61
|
+
await reactors.register();
|
|
62
|
+
await reducers.register();
|
|
63
|
+
expect(reactorStream.observe).toHaveBeenCalledOnce();
|
|
64
|
+
expect(reducerStream.observe).toHaveBeenCalledOnce();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('disposes cached store observations even when the lifecycle is not connected', () => {
|
|
68
|
+
const client = new ChronicleClient(ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { discoveryPatterns: [] }));
|
|
69
|
+
const disposeObservations = vi.fn();
|
|
70
|
+
(client as unknown as { _stores: Map<string, EventStore> })._stores.set('store/Default', { disposeObservations } as unknown as EventStore);
|
|
71
|
+
client.dispose();
|
|
72
|
+
expect(disposeObservations).toHaveBeenCalledOnce();
|
|
73
|
+
});
|
|
74
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/chronicle",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.11.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",
|
|
@@ -170,6 +170,7 @@
|
|
|
170
170
|
"@cratis/chronicle.contracts": "19.4.0",
|
|
171
171
|
"@grpc/grpc-js": "^1.14.4",
|
|
172
172
|
"@opentelemetry/api": "^1.9.1",
|
|
173
|
+
"glob": "^13.0.6",
|
|
173
174
|
"nice-grpc": "^2.1.17",
|
|
174
175
|
"nice-grpc-common": "^2.0.4",
|
|
175
176
|
"reflect-metadata": "^0.2.2",
|
|
@@ -167,6 +167,7 @@ describe('Projections with childrenFrom, nested and clearWith', () => {
|
|
|
167
167
|
const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
|
|
168
168
|
const summary = definition.Nested.summary;
|
|
169
169
|
|
|
170
|
+
expect(summary.IdentifiedBy).toBe('*NotSet*');
|
|
170
171
|
const updateEntry = summary.From.find(candidate => candidate.Key.Id === 'SummaryUpdated')!;
|
|
171
172
|
expect(updateEntry.Value.Properties.total).toBe('total');
|
|
172
173
|
expect(summary.RemovedWith.some(candidate => candidate.Key.Id === 'SummaryCleared')).toBe(true);
|
|
@@ -179,6 +180,7 @@ describe('Projections with childrenFrom, nested and clearWith', () => {
|
|
|
179
180
|
const definition = registerMock.mock.calls[0][0].Projections[0] as BuiltDefinition;
|
|
180
181
|
const note = definition.Nested.note;
|
|
181
182
|
|
|
183
|
+
expect(note.IdentifiedBy).toBe('*NotSet*');
|
|
182
184
|
const updateEntry = note.From.find(candidate => candidate.Key.Id === 'NoteAdded')!;
|
|
183
185
|
expect(updateEntry.Value.Properties.text).toBe('text');
|
|
184
186
|
expect(note.RemovedWith.some(candidate => candidate.Key.Id === 'NoteCleared')).toBe(true);
|
|
@@ -12,7 +12,9 @@ import { eventLog, eventSequence } from './modelBound/eventSequence.js';
|
|
|
12
12
|
import { fromAll } from './modelBound/fromAll.js';
|
|
13
13
|
import { fromEvent } from './modelBound/fromEvent.js';
|
|
14
14
|
import { noAutoMap } from './modelBound/noAutoMap.js';
|
|
15
|
+
import { join } from './modelBound/join.js';
|
|
15
16
|
import { setFrom } from './modelBound/setFrom.js';
|
|
17
|
+
import { setValue } from './modelBound/setValue.js';
|
|
16
18
|
import { Projections } from './Projections.js';
|
|
17
19
|
|
|
18
20
|
// Decorators are applied as plain function calls (rather than `@decorator` syntax) so these
|
|
@@ -112,6 +114,28 @@ setFrom(MbProjectNoted, 'note')(MbProjectNotes.prototype, 'note');
|
|
|
112
114
|
clearWith(MbProjectNoteCleared)(MbProjectNotes.prototype, 'note');
|
|
113
115
|
fromEvent(MbProjectNoted)(MbProjectNotes);
|
|
114
116
|
|
|
117
|
+
class MbConstants {
|
|
118
|
+
active!: boolean;
|
|
119
|
+
status!: string;
|
|
120
|
+
total!: number;
|
|
121
|
+
started!: Date;
|
|
122
|
+
cleared!: string | null;
|
|
123
|
+
wrapped!: string;
|
|
124
|
+
}
|
|
125
|
+
setValue(MbProjectNoted, true)(MbConstants.prototype, 'active');
|
|
126
|
+
setValue(MbProjectNoted, 'on-loan')(MbConstants.prototype, 'status');
|
|
127
|
+
setValue(MbProjectNoted, 2.5)(MbConstants.prototype, 'total');
|
|
128
|
+
setValue(MbProjectNoted, new Date('2025-01-02T03:04:05.006Z'))(MbConstants.prototype, 'started');
|
|
129
|
+
setValue(MbProjectNoted, null)(MbConstants.prototype, 'cleared');
|
|
130
|
+
setValue(MbProjectNoted, { value: 'wrapped' })(MbConstants.prototype, 'wrapped');
|
|
131
|
+
fromEvent(MbProjectNoted, { constantKey: 'singleton' })(MbConstants);
|
|
132
|
+
|
|
133
|
+
class MbJoinedNote {
|
|
134
|
+
id!: string;
|
|
135
|
+
note!: string;
|
|
136
|
+
}
|
|
137
|
+
join(MbProjectNoted)(MbJoinedNote.prototype, 'note');
|
|
138
|
+
|
|
115
139
|
interface BuiltFromEntry {
|
|
116
140
|
Key: { Id: string };
|
|
117
141
|
Value: { Properties: Record<string, string>; Key: string; ParentKey: string };
|
|
@@ -121,6 +145,7 @@ interface BuiltDefinition {
|
|
|
121
145
|
EventSequenceId: string;
|
|
122
146
|
AutoMap: AutoMap;
|
|
123
147
|
NoAutoMapProperties: string[];
|
|
148
|
+
Join: Array<{ Value: { On: string } }>;
|
|
124
149
|
All: { Properties: Record<string, string> };
|
|
125
150
|
From: BuiltFromEntry[];
|
|
126
151
|
}
|
|
@@ -208,6 +233,28 @@ describe('Projections model-bound completeness', () => {
|
|
|
208
233
|
});
|
|
209
234
|
});
|
|
210
235
|
|
|
236
|
+
describe('when a property is set to a constant', () => {
|
|
237
|
+
it('should send value and null expressions for model-bound mappings', async () => {
|
|
238
|
+
const definition = await registerAndGetDefinition(MbConstants);
|
|
239
|
+
expect(findFromEntry(definition, 'MbProjectNoted').Value.Properties).toMatchObject({
|
|
240
|
+
active: '$value(true)',
|
|
241
|
+
status: '$value(on-loan)',
|
|
242
|
+
total: '$value(2.5)',
|
|
243
|
+
started: '$value(2025-01-02T03:04:05.006Z)',
|
|
244
|
+
cleared: '$null',
|
|
245
|
+
wrapped: '$value(wrapped)'
|
|
246
|
+
});
|
|
247
|
+
expect(findFromEntry(definition, 'MbProjectNoted').Value.Key).toBe('$value(singleton)');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('when a model-bound join has no explicit on property', () => {
|
|
252
|
+
it('should use the mapped property as the join path', async () => {
|
|
253
|
+
const definition = await registerAndGetDefinition(MbJoinedNote);
|
|
254
|
+
expect(definition.Join[0].Value.On).toBe('note');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
211
258
|
describe('when a root scalar property is cleared with an event', () => {
|
|
212
259
|
it('should add a $null mapping for that property on the clearing event', async () => {
|
|
213
260
|
const definition = await registerAndGetDefinition(MbProjectNotes);
|