├── .config
└── dotnet-tools.json
├── .editorconfig
├── .gitattributes
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── release.yml
└── workflows
│ ├── base.yml
│ ├── ci.yml
│ ├── dispatch-ce.yml
│ ├── dispatch-ee.yml
│ └── publish.yml
├── .gitignore
├── CHANGELOG.md
├── Directory.Build.props
├── Directory.Build.targets
├── KurrentDB.Client.sln
├── KurrentDB.Client.sln.DotSettings
├── LICENSE
├── README.md
├── gencert.ps1
├── gencert.sh
├── ouro.png
├── ouro.svg
├── samples
├── Directory.Build.props
├── Samples.sln
├── Samples.sln.DotSettings
├── appending-events
│ ├── Program.cs
│ └── appending-events.csproj
├── connecting-to-a-cluster
│ ├── Program.cs
│ └── connecting-to-a-cluster.csproj
├── connecting-to-a-single-node
│ ├── DemoInterceptor.cs
│ ├── Program.cs
│ └── connecting-to-a-single-node.csproj
├── diagnostics
│ ├── Program.cs
│ └── diagnostics.csproj
├── persistent-subscriptions
│ ├── Program.cs
│ └── persistent-subscriptions.csproj
├── projection-management
│ ├── Program.cs
│ └── projection-management.csproj
├── quick-start
│ ├── Program.cs
│ ├── docker-compose.yml
│ └── quick-start.csproj
├── reading-events
│ ├── Program.cs
│ └── reading-events.csproj
├── secure-with-tls
│ ├── .dockerignore
│ ├── Dockerfile
│ ├── Program.cs
│ ├── README.md
│ ├── create-certs.ps1
│ ├── create-certs.sh
│ ├── docker-compose.app.yml
│ ├── docker-compose.certs.yml
│ ├── docker-compose.yml
│ ├── run-app.sh
│ └── secure-with-tls.csproj
├── server-side-filtering
│ ├── Program.cs
│ └── server-side-filtering.csproj
├── setting-up-dependency-injection
│ ├── Controllers
│ │ └── EventStoreController.cs
│ ├── Program.cs
│ ├── Properties
│ │ └── launchSettings.json
│ ├── Startup.cs
│ └── setting-up-dependency-injection.csproj
├── subscribing-to-streams
│ ├── Program.cs
│ └── subscribing-to-streams.csproj
└── user-certificates
│ ├── Program.cs
│ └── user-certificates.csproj
├── src
├── Directory.Build.props
└── KurrentDB.Client
│ ├── Core
│ ├── Certificates
│ │ └── X509Certificates.cs
│ ├── ChannelBaseExtensions.cs
│ ├── ChannelCache.cs
│ ├── ChannelFactory.cs
│ ├── ChannelInfo.cs
│ ├── ChannelSelector.cs
│ ├── ClusterMessage.cs
│ ├── Common
│ │ ├── AsyncStreamReaderExtensions.cs
│ │ ├── Constants.cs
│ │ ├── Diagnostics
│ │ │ ├── ActivitySourceExtensions.cs
│ │ │ ├── ActivityTagsCollectionExtensions.cs
│ │ │ ├── Core
│ │ │ │ ├── ActivityExtensions.cs
│ │ │ │ ├── ActivityStatus.cs
│ │ │ │ ├── ActivityStatusCodeHelper.cs
│ │ │ │ ├── ActivityTagsCollectionExtensions.cs
│ │ │ │ ├── ExceptionExtensions.cs
│ │ │ │ ├── Telemetry
│ │ │ │ │ └── TelemetryTags.cs
│ │ │ │ └── Tracing
│ │ │ │ │ ├── TracingConstants.cs
│ │ │ │ │ └── TracingMetadata.cs
│ │ │ ├── EventMetadataExtensions.cs
│ │ │ ├── KurrentDBClientDiagnostics.cs
│ │ │ ├── Telemetry
│ │ │ │ └── TelemetryTags.cs
│ │ │ └── Tracing
│ │ │ │ └── TracingConstants.cs
│ │ ├── EnumerableTaskExtensions.cs
│ │ ├── EpochExtensions.cs
│ │ ├── KurrentDBCallOptions.cs
│ │ ├── MetadataExtensions.cs
│ │ └── Shims
│ │ │ ├── Index.cs
│ │ │ ├── IsExternalInit.cs
│ │ │ ├── Range.cs
│ │ │ └── TaskCompletionSource.cs
│ ├── DefaultRequestVersionHandler.cs
│ ├── EndPointExtensions.cs
│ ├── EventData.cs
│ ├── EventRecord.cs
│ ├── EventTypeFilter.cs
│ ├── Exceptions
│ │ ├── AccessDeniedException.cs
│ │ ├── ConnectionString
│ │ │ ├── ConnectionStringParseException.cs
│ │ │ ├── DuplicateKeyException.cs
│ │ │ ├── InvalidClientCertificateException.cs
│ │ │ ├── InvalidHostException.cs
│ │ │ ├── InvalidKeyValuePairException.cs
│ │ │ ├── InvalidSchemeException.cs
│ │ │ ├── InvalidSettingException.cs
│ │ │ ├── InvalidUserCredentialsException.cs
│ │ │ └── NoSchemeException.cs
│ │ ├── DiscoveryException.cs
│ │ ├── NotAuthenticatedException.cs
│ │ ├── NotLeaderException.cs
│ │ ├── RequiredMetadataPropertyMissingException.cs
│ │ ├── ScavengeNotFoundException.cs
│ │ ├── StreamDeletedException.cs
│ │ ├── StreamNotFoundException.cs
│ │ ├── UserNotFoundException.cs
│ │ └── WrongExpectedVersionException.cs
│ ├── FromAll.cs
│ ├── FromStream.cs
│ ├── GossipChannelSelector.cs
│ ├── GrpcGossipClient.cs
│ ├── GrpcServerCapabilitiesClient.cs
│ ├── HashCode.cs
│ ├── HttpFallback.cs
│ ├── IChannelSelector.cs
│ ├── IEventFilter.cs
│ ├── IGossipClient.cs
│ ├── IPosition.cs
│ ├── IServerCapabilitiesClient.cs
│ ├── Interceptors
│ │ ├── ConnectionNameInterceptor.cs
│ │ ├── ReportLeaderInterceptor.cs
│ │ └── TypedExceptionInterceptor.cs
│ ├── KurrentDBClientBase.cs
│ ├── KurrentDBClientConnectivitySettings.cs
│ ├── KurrentDBClientOperationOptions.cs
│ ├── KurrentDBClientSerializationSettings.cs
│ ├── KurrentDBClientSettings.ConnectionString.cs
│ ├── KurrentDBClientSettings.cs
│ ├── MessageData.cs
│ ├── NodePreference.cs
│ ├── NodePreferenceComparers.cs
│ ├── NodeSelector.cs
│ ├── OperationOptions.cs
│ ├── Position.cs
│ ├── PrefixFilterExpression.cs
│ ├── ReconnectionRequired.cs
│ ├── RegularFilterExpression.cs
│ ├── ResolvedEvent.cs
│ ├── Serialization
│ │ ├── ISerializer.cs
│ │ ├── Message.cs
│ │ ├── MessageSerializer.cs
│ │ ├── MessageTypeRegistry.cs
│ │ ├── MessageTypeResolutionStrategy.cs
│ │ ├── SchemaRegistry.cs
│ │ ├── SystemTextJsonSerializer.cs
│ │ └── TypeProvider.cs
│ ├── ServerCapabilities.cs
│ ├── SharingProvider.cs
│ ├── SingleNodeChannelSelector.cs
│ ├── SingleNodeHttpHandler.cs
│ ├── StreamFilter.cs
│ ├── StreamIdentifier.cs
│ ├── StreamPosition.cs
│ ├── StreamState.cs
│ ├── SubscriptionDroppedReason.cs
│ ├── SystemRoles.cs
│ ├── SystemStreams.cs
│ ├── TaskExtensions.cs
│ ├── UserCredentials.cs
│ ├── Uuid.cs
│ └── protos
│ │ ├── code.proto
│ │ ├── gossip.proto
│ │ ├── operations.proto
│ │ ├── persistentsubscriptions.proto
│ │ ├── projectionmanagement.proto
│ │ ├── serverfeatures.proto
│ │ ├── shared.proto
│ │ ├── status.proto
│ │ ├── streams.proto
│ │ └── usermanagement.proto
│ ├── KurrentDB.Client.csproj
│ ├── OpenTelemetry
│ └── TracerProviderBuilderExtensions.cs
│ ├── Operations
│ ├── DatabaseScavengeResult.cs
│ ├── KurrentDBOperationsClient.Admin.cs
│ ├── KurrentDBOperationsClient.Scavenge.cs
│ ├── KurrentDBOperationsClient.cs
│ ├── KurrentDBOperationsClientServiceCollectionExtensions.cs
│ └── ScavengeResult.cs
│ ├── PersistentSubscriptions
│ ├── KurrentDBPersistentSubscriptionsClient.Create.cs
│ ├── KurrentDBPersistentSubscriptionsClient.Delete.cs
│ ├── KurrentDBPersistentSubscriptionsClient.Info.cs
│ ├── KurrentDBPersistentSubscriptionsClient.List.cs
│ ├── KurrentDBPersistentSubscriptionsClient.Read.cs
│ ├── KurrentDBPersistentSubscriptionsClient.ReplayParked.cs
│ ├── KurrentDBPersistentSubscriptionsClient.RestartSubsystem.cs
│ ├── KurrentDBPersistentSubscriptionsClient.Update.cs
│ ├── KurrentDBPersistentSubscriptionsClient.cs
│ ├── KurrentDBPersistentSubscriptionsClientCollectionExtensions.cs
│ ├── MaximumSubscribersReachedException.cs
│ ├── PersistentSubscription.cs
│ ├── PersistentSubscriptionDroppedByServerException.cs
│ ├── PersistentSubscriptionExtraStatistic.cs
│ ├── PersistentSubscriptionInfo.cs
│ ├── PersistentSubscriptionMessage.cs
│ ├── PersistentSubscriptionNakEventAction.cs
│ ├── PersistentSubscriptionNotFoundException.cs
│ ├── PersistentSubscriptionSettings.cs
│ └── SystemConsumerStrategies.cs
│ ├── ProjectionManagement
│ ├── KurrentDBProjectionManagementClient.Control.cs
│ ├── KurrentDBProjectionManagementClient.Create.cs
│ ├── KurrentDBProjectionManagementClient.State.cs
│ ├── KurrentDBProjectionManagementClient.Statistics.cs
│ ├── KurrentDBProjectionManagementClient.Update.cs
│ ├── KurrentDBProjectionManagementClient.cs
│ ├── KurrentDBProjectionManagementClientCollectionExtensions.cs
│ └── ProjectionDetails.cs
│ ├── Streams
│ ├── ConditionalWriteResult.cs
│ ├── ConditionalWriteStatus.cs
│ ├── DeadLine.cs
│ ├── DeleteResult.cs
│ ├── Direction.cs
│ ├── IWriteResult.cs
│ ├── InvalidTransactionException.cs
│ ├── KurrentDBClient.Append.cs
│ ├── KurrentDBClient.Delete.cs
│ ├── KurrentDBClient.Metadata.cs
│ ├── KurrentDBClient.Read.cs
│ ├── KurrentDBClient.Subscriptions.cs
│ ├── KurrentDBClient.Tombstone.cs
│ ├── KurrentDBClient.cs
│ ├── KurrentDBClientExtensions.cs
│ ├── KurrentDBClientServiceCollectionExtensions.cs
│ ├── MaximumAppendSizeExceededException.cs
│ ├── ReadState.cs
│ ├── StreamAcl.cs
│ ├── StreamAclJsonConverter.cs
│ ├── StreamMessage.cs
│ ├── StreamMetadata.cs
│ ├── StreamMetadataJsonConverter.cs
│ ├── StreamMetadataResult.cs
│ ├── StreamSubscription.cs
│ ├── Streams
│ │ ├── AppendReq.cs
│ │ ├── BatchAppendReq.cs
│ │ ├── BatchAppendResp.cs
│ │ ├── DeleteReq.cs
│ │ ├── ReadReq.cs
│ │ └── TombstoneReq.cs
│ ├── SubscriptionFilterOptions.cs
│ ├── SuccessResult.cs
│ ├── SystemEventTypes.cs
│ ├── SystemMetadata.cs
│ ├── SystemSettings.cs
│ ├── SystemSettingsJsonConverter.cs
│ ├── WriteResultExtensions.cs
│ └── WrongExpectedVersionResult.cs
│ └── UserManagement
│ ├── KurrentDBUserManagementClient.cs
│ ├── KurrentDBUserManagementClientCollectionExtensions.cs
│ ├── KurrentDBUserManagerClientExtensions.cs
│ └── UserDetails.cs
└── test
├── Directory.Build.props
├── KurrentDB.Client.Tests.Common
├── .env
├── ApplicationInfo.cs
├── AssertEx.cs
├── Certificates.cs
├── Extensions
│ ├── ConfigurationExtensions.cs
│ ├── KurrentDBClientExtensions.cs
│ ├── KurrentDBClientWarmupExtensions.cs
│ ├── OperatingSystemExtensions.cs
│ ├── ReadOnlyMemoryExtensions.cs
│ ├── ShouldThrowAsyncExtensions.cs
│ ├── TaskExtensions.cs
│ ├── TypeExtensions.cs
│ └── WithExtension.cs
├── Facts
│ ├── AnonymousAccess.cs
│ ├── Deprecation.cs
│ └── Regression.cs
├── Fakers
│ └── TestUserFaker.cs
├── Fixtures
│ ├── BaseTestNode.cs
│ ├── CertificatesManager.cs
│ ├── KurrentDBFixtureOptions.cs
│ ├── KurrentDBPermanentFixture.Helpers.cs
│ ├── KurrentDBPermanentFixture.cs
│ ├── KurrentDBPermanentTestNode.cs
│ ├── KurrentDBTemporaryFixture.Helpers.cs
│ ├── KurrentDBTemporaryFixture.cs
│ └── KurrentDBTemporaryTestNode.cs
├── FluentDocker
│ ├── FluentDockerBuilderExtensions.cs
│ ├── FluentDockerServiceExtensions.cs
│ ├── TestBypassService.cs
│ ├── TestCompositeService.cs
│ ├── TestContainerService.cs
│ └── TestService.cs
├── GlobalEnvironment.cs
├── InterlockedBoolean.cs
├── KurrentDB.Client.Tests.Common.csproj
├── Logging.cs
├── PasswordGenerator.cs
├── Shouldly
│ └── ShouldThrowAsyncExtensions.cs
├── TestCaseGenerator.cs
├── TestCredentials.cs
├── appsettings.Development.json
├── appsettings.json
├── docker-compose.certs.yml
├── docker-compose.cluster.yml
├── docker-compose.node.yml
├── docker-compose.yml
└── shared.env
├── KurrentDB.Client.Tests.ExternalAssembly
├── ExternalEvents.cs
└── KurrentDB.Client.Tests.ExternalAssembly.csproj
├── KurrentDB.Client.Tests.NeverLoadedAssembly
├── KurrentDB.Client.Tests.NeverLoadedAssembly.csproj
└── NotLoadedExternalEvent.cs
└── KurrentDB.Client.Tests
├── Assertions
├── ComparableAssertion.cs
├── EqualityAssertion.cs
├── NullArgumentAssertion.cs
├── StringConversionAssertion.cs
└── ValueObjectAssertion.cs
├── AutoScenarioDataAttribute.cs
├── ClientCertificatesTests.cs
├── ConnectionStringTests.cs
├── Core
└── Serialization
│ ├── ContentTypeExtensionsTests.cs
│ ├── MessageSerializerTests.cs
│ ├── MessageTypeNamingResolutionContextTests.cs
│ ├── MessageTypeRegistryTests.cs
│ ├── NullMessageSerializerTests.cs
│ ├── SchemaRegistryTests.cs
│ └── TypeProviderTests.cs
├── FromAllTests.cs
├── FromStreamTests.cs
├── GossipChannelSelectorTests.cs
├── GrpcServerCapabilitiesClientTests.cs
├── InvalidCredentialsTestCases.cs
├── KurrentDB.Client.Tests.csproj
├── KurrentDBClientOperationsTests.cs
├── NodePreferenceComparerTests.cs
├── NodeSelectorTests.cs
├── Operations
├── AuthenticationTests.cs
├── MergeIndexTests.cs
├── ResignNodeTests.cs
├── RestartPersistentSubscriptionsTests.cs
├── ScavengeTests.cs
├── ShutdownNodeAuthenticationTests.cs
└── ShutdownNodeTests.cs
├── PersistentSubscriptions
├── FilterTestCases.cs
├── SubscribeToAll
│ ├── Obsolete
│ │ ├── SubscribeToAllConnectToExistingWithStartFromNotSetObsoleteTests.cs
│ │ ├── SubscribeToAllConnectToExistingWithStartFromSetToEndPositionObsoleteTests.cs
│ │ ├── SubscribeToAllConnectWithoutReadPermissionsObsoleteTests.cs
│ │ ├── SubscribeToAllFilterObsoleteTests.cs
│ │ ├── SubscribeToAllGetInfoObsoleteTests.cs
│ │ ├── SubscribeToAllListWithIncorrectCredentialsObsoleteTests.cs
│ │ ├── SubscribeToAllNoDefaultCredentialsObsoleteTests.cs
│ │ ├── SubscribeToAllObsoleteTests.cs
│ │ ├── SubscribeToAllReplayParkedObsoleteTests.cs
│ │ ├── SubscribeToAllResultWithNormalUserCredentialsObsoleteTests.cs
│ │ ├── SubscribeToAllReturnsAllSubscriptionsObsoleteTests.cs
│ │ ├── SubscribeToAllReturnsSubscriptionsToAllStreamObsoleteTests.cs
│ │ ├── SubscribeToAllUpdateExistingWithCheckpointObsoleteTests.cs
│ │ └── SubscribeToAllWithoutPSObsoleteTests.cs
│ ├── SubscribeToAllConnectToExistingWithStartFromNotSetTests.cs
│ ├── SubscribeToAllConnectToExistingWithStartFromSetToEndPositionTests.cs
│ ├── SubscribeToAllConnectWithoutReadPermissionsTests.cs
│ ├── SubscribeToAllFilterTests.cs
│ ├── SubscribeToAllGetInfoTests.cs
│ ├── SubscribeToAllListWithIncorrectCredentialsTests.cs
│ ├── SubscribeToAllNoDefaultCredentialsTests.cs
│ ├── SubscribeToAllReplayParkedTests.cs
│ ├── SubscribeToAllResultWithNormalUserCredentialsTests.cs
│ ├── SubscribeToAllReturnsAllSubscriptions.cs
│ ├── SubscribeToAllReturnsSubscriptionsToAllStreamTests.cs
│ ├── SubscribeToAllTests.cs
│ ├── SubscribeToAllUpdateExistingWithCheckpointTest.cs
│ └── SubscribeToAllWithoutPSTests.cs
└── SubscribeToStream
│ ├── Obsolete
│ ├── SubscribeToStreamConnectToExistingWithoutPermissionObsoleteTests.cs
│ ├── SubscribeToStreamGetInfoObsoleteTests.cs
│ └── SubscribeToStreamObsoleteTests.cs
│ ├── SubscribeToStreamConnectToExistingWithStartFromBeginningTests.cs
│ ├── SubscribeToStreamGetInfoTests.cs
│ ├── SubscribeToStreamListTests.cs
│ ├── SubscribeToStreamNoDefaultCredentialsTests.cs
│ ├── SubscribeToStreamReplayParkedTests.cs
│ └── SubscribeToStreamTests.cs
├── PositionTests.cs
├── ProjectionManagement
├── DisableProjectionTests.cs
├── EnableProjectionTests.cs
├── GetProjectionResultTests.cs
├── GetProjectionStateTests.cs
├── GetProjectionStatusTests.cs
├── ListAllProjectionsTests.cs
├── ListContinuousProjectionsTests.cs
├── ListOneTimeProjectionsTests.cs
├── ProjectionManagementTests.cs
├── ResetProjectionTests.cs
├── RestartSubsystemTests.cs
└── UpdateProjectionTests.cs
├── RegularFilterExpressionTests.cs
├── Security
├── AllStreamWithNoAclSecurityTests.cs
├── DeleteStreamSecurityTests.cs
├── MultipleRoleSecurityTests.cs
├── OverridenSystemStreamSecurityForAllTests.cs
├── OverridenSystemStreamSecurityTests.cs
├── OverridenUserStreamSecurityTests.cs
├── ReadAllSecurityTests.cs
├── ReadStreamMetaSecurityTests.cs
├── ReadStreamSecurityTests.cs
├── SecurityFixture.cs
├── StreamSecurityInheritanceTests.cs
├── SubscribeToAllSecurityTests.cs
├── SubscribeToStreamSecurityTests.cs
├── SystemStreamSecurityTests.cs
├── WriteStreamMetaSecurityTests.cs
└── WriteStreamSecurityTests.cs
├── SharingProviderTests.cs
├── StreamPositionTests.cs
├── StreamStateTests.cs
├── Streams
├── AppendTests.cs
├── Bugs
│ └── Obsolete
│ │ ├── Issue104.cs
│ │ └── Issue2544.cs
├── DeleteTests.cs
├── Read
│ ├── MessageBinaryData.cs
│ ├── MessageDataComparer.cs
│ ├── ReadAllEventsBackwardTests.cs
│ ├── ReadAllEventsFixture.cs
│ ├── ReadAllEventsForwardTests.cs
│ ├── ReadStreamBackwardTests.cs
│ ├── ReadStreamEventsLinkedToDeletedStreamTests.cs
│ ├── ReadStreamForwardTests.cs
│ └── ReadStreamWhenHavingMaxCountSetForStreamTests.cs
├── Serialization
│ ├── SerializationTests.PersistentSubscriptions.cs
│ ├── SerializationTests.Subscriptions.cs
│ └── SerializationTests.cs
├── SoftDeleteTests.cs
├── StreamMetadataTests.cs
├── SubscriptionFilter.cs
└── Subscriptions
│ ├── Obsolete
│ ├── SubscribeToAllObsoleteTests.cs
│ └── SubscribeToStreamObsoleteTests.cs
│ ├── SubscribeToAllTests.cs
│ ├── SubscribeToStreamTests.cs
│ └── SubscriptionDroppedResult.cs
├── UserManagement
├── ChangePasswordTests.cs
├── CreateUserTests.cs
├── DeleteUserTests.cs
├── DisableUserTests.cs
├── EnableUserTests.cs
├── GetCurrentUserTests.cs
├── ListUserTests.cs
├── ResettingUserPasswordTests.cs
└── UserCredentialsTests.cs
├── UuidTests.cs
├── ValueObjectTests.cs
└── X509CertificatesTests.cs
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "minver-cli": {
6 | "version": "4.2.0",
7 | "commands": [
8 | "minver"
9 | ]
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set default behaviour, in case users don't have core.autocrlf set.
2 | * text=auto
3 |
4 | # Explicitly declare text files we want to always be normalized and converted
5 | # to native line endings on checkout.
6 | *.c text
7 | *.h text
8 | *.cs text
9 | *.config text
10 | *.xml text
11 | *.manifest text
12 | *.bat text
13 | *.cmd text
14 | *.sh text
15 | *.txt text
16 | *.dat text
17 | *.rc text
18 | *.ps1 text
19 | *.csproj text
20 | *.sln text
21 |
22 | # Declare files that will always have CRLF line endings on checkout.
23 |
24 | # Denote all files that are truly binary and should not be modified.
25 | *.png binary
26 | *.jpg binary
27 | *.dll binary
28 | *.exe binary
29 | *.pdb binary
30 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1.
16 | 2.
17 | 3.
18 | 4.
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Actual behavior**
24 | A clear and concise description of what actually happened.
25 |
26 | **Config/Logs/Screenshots**
27 | If applicable, please attach your node configuration, logs or any screenshots.
28 |
29 | **EventStore details**
30 | - EventStore server version:
31 |
32 | - Operating system:
33 |
34 | - EventStore client version (if applicable):
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is.
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - ignore-for-release
5 | categories:
6 | - title: Added
7 | labels:
8 | - enhancement
9 | - title: Fixed
10 | labels:
11 | - bug
12 | - title: Changed
13 | labels:
14 | - "*"
15 | - title: Deprecated
16 | labels:
17 | - deprecated
18 | - title: Breaking Changes
19 | labels:
20 | - breaking
21 | - title: Documentation
22 | labels:
23 | - documentation
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 | tags:
9 | - v*
10 |
11 | jobs:
12 | ce:
13 | uses: ./.github/workflows/base.yml
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | kurrentdb-tag: [ ci, lts ]
18 | test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement, Plugins, Security, Misc ]
19 | name: Test (${{ matrix.kurrentdb-tag }})
20 | with:
21 | kurrentdb-tag: ${{ matrix.kurrentdb-tag }}
22 | test: ${{ matrix.test }}
23 | secrets: inherit
24 |
25 | ee:
26 | uses: ./.github/workflows/base.yml
27 | strategy:
28 | fail-fast: false
29 | matrix:
30 | kurrentdb-tag: [ previous-lts ]
31 | test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement, Security, Misc ]
32 | name: Test (${{ matrix.kurrentdb-tag }})
33 | with:
34 | kurrentdb-tag: ${{ matrix.kurrentdb-tag }}
35 | test: ${{ matrix.test }}
36 | secrets: inherit
37 |
--------------------------------------------------------------------------------
/.github/workflows/dispatch-ce.yml:
--------------------------------------------------------------------------------
1 | name: Dispatch CE
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | docker-tag:
7 | description: "Docker tag"
8 | required: true
9 | type: string
10 | docker-image:
11 | description: "Docker image"
12 | required: true
13 | type: string
14 |
15 | jobs:
16 | test:
17 | uses: ./.github/workflows/base.yml
18 | strategy:
19 | fail-fast: false
20 | matrix:
21 | test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement, Security, Misc ]
22 | name: Test CE (${{ inputs.docker-tag }})
23 | with:
24 | docker-tag: ${{ inputs.docker-tag }}
25 | docker-image: ${{ inputs.docker-image }}
26 | test: ${{ matrix.test }}
27 | secrets:
28 | CLOUDSMITH_CICD_USER: ${{ secrets.CLOUDSMITH_CICD_USER }}
29 | CLOUDSMITH_CICD_TOKEN: ${{ secrets.CLOUDSMITH_CICD_TOKEN }}
30 |
--------------------------------------------------------------------------------
/.github/workflows/dispatch-ee.yml:
--------------------------------------------------------------------------------
1 | name: Dispatch
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | kurrentdb-tag:
7 | description: "The KurrentDB docker tag to use. If kurrentdb-image is empty, the action will use the values in the KURRENTDB_DOCKER_IMAGES variable (ci, lts, previous-lts)."
8 | required: true
9 | type: string
10 | kurrentdb-image:
11 | description: "The KurrentDB docker image to test against. Leave this empty to use the image in the KURRENTDB_DOCKER_IMAGES variable"
12 | required: false
13 | type: string
14 | kurrentdb-registry:
15 | description: "The docker registry containing the KurrentDB docker image. Leave this empty to use the registry in the KURRENTDB_DOCKER_IMAGES variable."
16 | required: false
17 | type: string
18 | jobs:
19 | test:
20 | uses: ./.github/workflows/base.yml
21 | strategy:
22 | fail-fast: false
23 | matrix:
24 | test: [ Streams, PersistentSubscriptions, Operations, UserManagement, ProjectionManagement, Plugins ]
25 | name: Test (${{ inputs.kurrentdb-tag }})
26 | with:
27 | kurrentdb-tag: ${{ inputs.kurrentdb-tag }}
28 | kurrentdb-image: ${{ inputs.kurrentdb-image }}
29 | kurrentdb-registry: ${{ inputs.kurrentdb-registry }}
30 | test: ${{ matrix.test }}
31 | secrets: inherit
32 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | net48;net8.0;net9.0
4 | true
5 | enable
6 | enable
7 | true
8 | preview
9 |
10 | Debug
11 | full
12 | pdbonly
13 |
14 | true
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KurrentDB .NET Client
2 |
3 | KurrentDB is the event-native database, where business events are immutably stored and streamed. Designed for event-sourced, event-driven, and microservices architectures
4 |
5 | This is the repository for the .NET client for KurrentDB version 20+ and uses gRPC as the communication protocol.
6 |
7 | ## Installation
8 |
9 | Reference the nuget package(s) for the API that you would like to call
10 |
11 | [KurrentDB.Client](https://www.nuget.org/packages/KurrentDB.Client)
12 |
13 | ## Support
14 |
15 | Information on support and commercial tools such as LDAP authentication can be found here: [Kurrent Support](https://kurrent.io/support/).
16 |
17 | ## CI Status
18 |
19 | 
20 | 
21 | 
22 |
23 | ## Documentation
24 |
25 | Documentation for KurrentDB can be found here: [Kurrent Docs](https://kurrent.io/docs/).
26 |
27 | Bear in mind that this client is not yet properly documented. We are working hard on a new version of the documentation.
28 |
29 | ## Communities
30 |
31 | - [Discuss](https://discuss.kurrent.io/)
32 | - [Discord (Kurrent)](https://discord.gg/Phn9pmCw3t)
33 | - [Discord (ddd-cqrs-es)](https://discord.com/invite/sEZGSHNNbH)
34 |
35 | ## Contributing
36 |
37 | Development is done on the `master` branch.
38 | We attempt to do our best to ensure that the history remains clean and to do so, we generally ask contributors to squash their commits into a set or single logical commit.
39 |
--------------------------------------------------------------------------------
/gencert.ps1:
--------------------------------------------------------------------------------
1 | Write-Host ">> Generating certificate..."
2 |
3 | # Create directory if it doesn't exist
4 | New-Item -ItemType Directory -Path .\certs -Force
5 |
6 | # Set permissions for the directory
7 | icacls .\certs /grant:r "$($env:UserName):(OI)(CI)F"
8 |
9 | # Pull the Docker image
10 | docker pull docker.kurrent.io/eventstore-utils/es-gencert-cli:latest
11 |
12 | docker run --rm --volume .\certs:/tmp docker.kurrent.io/eventstore-utils/es-gencert-cli create-ca -out /tmp/ca
13 |
14 | docker run --rm --volume .\certs:/tmp docker.kurrent.io/eventstore-utils/es-gencert-cli create-node -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/node -ip-addresses 127.0.0.1 -dns-names localhost
15 |
16 | # Create admin user
17 | docker run --rm --volume .\certs:/tmp docker.kurrent.io/eventstore-utils/es-gencert-cli create-user -username admin -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-admin
18 |
19 | # Create an invalid user
20 | docker run --rm --volume .\certs:/tmp docker.kurrent.io/eventstore-utils/es-gencert-cli create-user -username invalid -ca-certificate /tmp/ca/ca.crt -ca-key /tmp/ca/ca.key -out /tmp/user-invalid
21 |
22 | # Set permissions recursively for the directory
23 | icacls .\certs /grant:r "$($env:UserName):(OI)(CI)F"
24 |
--------------------------------------------------------------------------------
/ouro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kurrent-io/KurrentDB-Client-Dotnet/1c39a1181c7fe82eba3712253041285ac98f20f9/ouro.png
--------------------------------------------------------------------------------
/samples/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0;net9.0
4 | enable
5 | enable
6 | true
7 | Exe
8 | preview
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/samples/appending-events/appending-events.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | appending_events
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/connecting-to-a-cluster/Program.cs:
--------------------------------------------------------------------------------
1 | using KurrentDB.Client;
2 |
3 | #pragma warning disable CS8321 // Local function is declared but never used
4 |
5 | static void ConnectingToACluster() {
6 | #region connecting-to-a-cluster
7 |
8 | using var client = new KurrentDBClient(
9 | KurrentDBClientSettings.Create("esdb://localhost:1114,localhost:2114,localhost:3114")
10 | );
11 |
12 | #endregion connecting-to-a-cluster
13 | }
14 |
15 | static void ProvidingDefaultCredentials() {
16 | #region providing-default-credentials
17 |
18 | using var client = new KurrentDBClient(
19 | KurrentDBClientSettings.Create("esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114")
20 | );
21 |
22 | #endregion providing-default-credentials
23 | }
24 |
25 | static void ConnectingToAClusterComplex() {
26 | #region connecting-to-a-cluster-complex
27 |
28 | using var client = new KurrentDBClient(
29 | KurrentDBClientSettings.Create(
30 | "esdb://admin:changeit@localhost:1114,localhost:2114,localhost:3114?DiscoveryInterval=30000;GossipTimeout=10000;NodePreference=leader;MaxDiscoverAttempts=5"
31 | )
32 | );
33 |
34 | #endregion connecting-to-a-cluster-complex
35 | }
36 |
--------------------------------------------------------------------------------
/samples/connecting-to-a-cluster/connecting-to-a-cluster.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | connecting_to_a_cluster
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/connecting-to-a-single-node/DemoInterceptor.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 | using Grpc.Core.Interceptors;
3 |
4 | namespace connecting_to_a_single_node;
5 |
6 | #region interceptor
7 |
8 | public class DemoInterceptor : Interceptor {
9 | public override AsyncServerStreamingCall
10 | AsyncServerStreamingCall(
11 | TRequest request,
12 | ClientInterceptorContext context,
13 | AsyncServerStreamingCallContinuation continuation
14 | ) {
15 | Console.WriteLine($"AsyncServerStreamingCall: {context.Method.FullName}");
16 |
17 | return base.AsyncServerStreamingCall(request, context, continuation);
18 | }
19 |
20 | public override AsyncClientStreamingCall
21 | AsyncClientStreamingCall(
22 | ClientInterceptorContext context,
23 | AsyncClientStreamingCallContinuation continuation
24 | ) {
25 | Console.WriteLine($"AsyncClientStreamingCall: {context.Method.FullName}");
26 |
27 | return base.AsyncClientStreamingCall(context, continuation);
28 | }
29 | }
30 |
31 | #endregion interceptor
--------------------------------------------------------------------------------
/samples/connecting-to-a-single-node/connecting-to-a-single-node.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/diagnostics/diagnostics.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | connecting_to_a_cluster
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/samples/persistent-subscriptions/persistent-subscriptions.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | persistent_subscriptions
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/projection-management/projection-management.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | projection_management
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/quick-start/Program.cs:
--------------------------------------------------------------------------------
1 | var tokenSource = new CancellationTokenSource();
2 | var cancellationToken = tokenSource.Token;
3 |
4 | #region createClient
5 |
6 | const string connectionString = "esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false";
7 |
8 | var settings = KurrentDBClientSettings.Create(connectionString);
9 |
10 | var client = new KurrentDBClient(settings);
11 |
12 | #endregion createClient
13 |
14 | #region createEvent
15 |
16 | var evt = new TestEvent {
17 | EntityId = Guid.NewGuid().ToString("N"),
18 | ImportantData = "I wrote my first event!"
19 | };
20 |
21 | #endregion createEvent
22 |
23 | #region appendEvents
24 |
25 | await client.AppendToStreamAsync(
26 | "some-stream",
27 | StreamState.Any,
28 | [evt],
29 | cancellationToken: cancellationToken
30 | );
31 |
32 | #endregion appendEvents
33 |
34 | #region overriding-user-credentials
35 |
36 | await client.AppendToStreamAsync(
37 | "some-stream",
38 | StreamState.Any,
39 | [evt],
40 | new AppendToStreamOptions { UserCredentials = new UserCredentials("admin", "changeit") },
41 | cancellationToken
42 | );
43 |
44 | #endregion overriding-user-credentials
45 |
46 | #region readStream
47 |
48 | var result = client.ReadStreamAsync(
49 | "some-stream",
50 | cancellationToken: cancellationToken
51 | );
52 |
53 | var events = await result
54 | .DeserializedData()
55 | .ToListAsync(cancellationToken);
56 |
57 | #endregion readStream
58 |
59 | public class TestEvent {
60 | public string? EntityId { get; set; }
61 | public string? ImportantData { get; set; }
62 | }
63 |
--------------------------------------------------------------------------------
/samples/quick-start/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.5'
2 | services:
3 | eventstore:
4 | image: docker.eventstore.com/eventstore-ce/eventstoredb-ce:latest
5 | environment:
6 | EVENTSTORE_INSECURE: true
7 | EVENTSTORE_MEM_DB: false
8 | EVENTSTORE_RUN_PROJECTIONS: all
9 | EVENTSTORE_START_STANDARD_PROJECTIONS: true
10 | EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: true
11 | ports:
12 | - "2113:2113"
--------------------------------------------------------------------------------
/samples/quick-start/quick-start.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | quick_start
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/reading-events/reading-events.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | reading_events
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.dockerignore
2 | **/.env
3 | **/.git
4 | **/.gitignore
5 | **/.project
6 | **/.settings
7 | **/.toolstarget
8 | **/.vs
9 | **/.vscode
10 | **/.idea
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
--------------------------------------------------------------------------------
/samples/secure-with-tls/create-certs.ps1:
--------------------------------------------------------------------------------
1 | New-Item -ItemType Directory -Force -Path certs
2 |
3 | docker-compose -f docker-compose.certs.yml up --remove-orphans
4 |
5 | Import-Certificate -FilePath ".\certs\ca\ca.crt" -CertStoreLocation Cert:\CurrentUser\Root
6 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/create-certs.sh:
--------------------------------------------------------------------------------
1 | unameOutput="$(uname -sr)"
2 | case "${unameOutput}" in
3 | Linux*Microsoft*) machine=WSL;;
4 | Linux*) machine=Linux;;
5 | Darwin*) machine=MacOS;;
6 | *) machine="${unameOutput}"
7 | esac
8 |
9 | echo ">> Generating certificate..."
10 | mkdir -p certs
11 | docker-compose -f docker-compose.certs.yml up --remove-orphans
12 |
13 | echo ">> Copying certificate..."
14 | cp certs/ca/ca.crt /usr/local/share/ca-certificates/eventstore_ca.crt
15 | #rsync --progress certs/ca/ca.crt /usr/local/share/ca-certificates/eventstore_ca.crt
16 |
17 | echo ">> Updating certificate permissions..."
18 | #chmod 644 /usr/local/share/ca-certificates/eventstore_ca.crt
19 |
20 | if [ "${machine}" == "MacOS" ]; then
21 | echo ">> Installing certificate on ${machine}..."
22 | sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /usr/local/share/ca-certificates/eventstore_ca.crt
23 | elif [ "$(machine)" == "Linux" ]; then
24 | echo ">> Installing certificate on ${machine}..."
25 | sudo update-ca-certificates
26 | elif [ "$(machine)" == "WSL" ]; then
27 | echo ">> Installing certificate on ${machine}..."
28 | sudo update-ca-certificates
29 | else
30 | echo ">> Unknown platform. Please install the certificate manually."
31 | fi
--------------------------------------------------------------------------------
/samples/secure-with-tls/docker-compose.app.yml:
--------------------------------------------------------------------------------
1 | version: "3.5"
2 |
3 | networks:
4 | default:
5 | name: eventstore-network
6 |
7 | services:
8 |
9 | app:
10 | container_name: app
11 | build: ./
12 | environment:
13 | # URL should match the DNS name in certificate and container name
14 | - ESDB__CONNECTION__STRING=esdb://admin:changeit@eventstore:2113?Tls=true&tlsVerifyCert=false
15 | depends_on:
16 | eventstore:
17 | condition: service_healthy
18 | links:
19 | - eventstore
20 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/docker-compose.certs.yml:
--------------------------------------------------------------------------------
1 | version: "3.5"
2 |
3 | networks:
4 | default:
5 | name: eventstore-network
6 |
7 | services:
8 |
9 | volumes-provisioner:
10 | image: hasnat/volumes-provisioner
11 | container_name: volumes-provisioner
12 | environment:
13 | PROVISION_DIRECTORIES: "1000:1000:0755:/tmp/certs"
14 | volumes:
15 | - "./certs:/tmp/certs"
16 | network_mode: none
17 |
18 | cert-gen:
19 | image: docker.eventstore.com/eventstore-utils/es-gencert-cli:latest
20 | container_name: cert-gen
21 | user: "1000:1000"
22 | entrypoint: [ "/bin/sh","-c" ]
23 | command:
24 | - |
25 | es-gencert-cli create-ca -out /tmp/certs/ca
26 | es-gencert-cli create-node -ca-certificate /tmp/certs/ca/ca.crt -ca-key /tmp/certs/ca/ca.key -out /tmp/certs/node -ip-addresses 127.0.0.1 -dns-names localhost,eventstore
27 | volumes:
28 | - "./certs:/tmp/certs"
29 | depends_on:
30 | - volumes-provisioner
31 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.5"
2 |
3 | networks:
4 | default:
5 | name: eventstore-network
6 |
7 | services:
8 |
9 | eventstore:
10 | image: docker.eventstore.com/eventstore-ce/eventstoredb-ce:latest
11 | container_name: eventstore
12 | environment:
13 | - EVENTSTORE_MEM_DB=true
14 | - EVENTSTORE_HTTP_PORT=2113
15 | - EVENTSTORE_LOG_LEVEL=Information
16 | - EVENTSTORE_RUN_PROJECTIONS=None
17 | - EVENTSTORE_START_STANDARD_PROJECTIONS=true
18 | - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true
19 |
20 | # set certificates location
21 | - EVENTSTORE_CERTIFICATE_FILE=/etc/eventstore/certs/node/node.crt
22 | - EVENTSTORE_CERTIFICATE_PRIVATE_KEY_FILE=/etc/eventstore/certs/node/node.key
23 | - EVENTSTORE_TRUSTED_ROOT_CERTIFICATES_PATH=/etc/eventstore/certs/ca
24 | ports:
25 | - "2113:2113"
26 | volumes:
27 | - ./certs:/etc/eventstore/certs
28 | - type: volume
29 | source: eventstore-volume-data1
30 | target: /var/lib/eventstore
31 | - type: volume
32 | source: eventstore-volume-logs1
33 | target: /var/log/eventstore
34 | restart: unless-stopped
35 |
36 | volumes:
37 | eventstore-volume-data1:
38 | eventstore-volume-logs1:
39 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/run-app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Going down..."
4 | docker-compose -f docker-compose.yml -f docker-compose.app.yml down
5 |
6 | echo "Going up..."
7 | docker-compose -f docker-compose.yml -f docker-compose.app.yml up --remove-orphans
8 |
--------------------------------------------------------------------------------
/samples/secure-with-tls/secure-with-tls.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0
5 | secure_with_tls
6 | enable
7 | enable
8 | Linux
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/samples/server-side-filtering/server-side-filtering.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | server_side_filtering
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/setting-up-dependency-injection/Controllers/EventStoreController.cs:
--------------------------------------------------------------------------------
1 | using KurrentDB.Client;
2 | using Microsoft.AspNetCore.Mvc;
3 |
4 | namespace setting_up_dependency_injection.Controllers {
5 | [ApiController]
6 | [Route("[controller]")]
7 | public class EventStoreController : ControllerBase {
8 | #region using-dependency
9 | private readonly KurrentDBClient _KurrentClient;
10 |
11 | public EventStoreController(KurrentDBClient KurrentDBClient) {
12 | _KurrentClient = KurrentDBClient;
13 | }
14 |
15 | [HttpGet]
16 | public IAsyncEnumerable Get() {
17 | return _KurrentClient.ReadAllAsync();
18 | }
19 | #endregion using-dependency
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/samples/setting-up-dependency-injection/Program.cs:
--------------------------------------------------------------------------------
1 | namespace setting_up_dependency_injection;
2 |
3 | public class Program {
4 | public static async Task Main(string[] args) {
5 | using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));
6 | await CreateHostBuilder(args).Build().WaitForShutdownAsync(cts.Token);
7 | }
8 |
9 | public static IHostBuilder CreateHostBuilder(string[] args) =>
10 | Host.CreateDefaultBuilder(args)
11 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); });
12 | }
--------------------------------------------------------------------------------
/samples/setting-up-dependency-injection/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:58204",
8 | "sslPort": 44377
9 | }
10 | },
11 | "profiles": {
12 | "IIS Express": {
13 | "commandName": "IISExpress",
14 | "launchBrowser": true,
15 | "launchUrl": "weatherforecast",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "setting_up_dependency_injection": {
21 | "commandName": "Project",
22 | "launchBrowser": true,
23 | "launchUrl": "weatherforecast",
24 | "applicationUrl": "https://localhost:5001;http://localhost:5000",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/samples/setting-up-dependency-injection/Startup.cs:
--------------------------------------------------------------------------------
1 | namespace setting_up_dependency_injection;
2 |
3 | public class Startup {
4 | public Startup(IConfiguration configuration) => Configuration = configuration;
5 |
6 | public IConfiguration Configuration { get; }
7 |
8 | public void ConfigureServices(IServiceCollection services) {
9 | services.AddControllers();
10 |
11 | #region setting-up-dependency
12 |
13 | services.AddKurrentDBClient("esdb://admin:changeit@localhost:2113?tls=false");
14 |
15 | #endregion setting-up-dependency
16 | }
17 |
18 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
19 | if (env.IsDevelopment()) app.UseDeveloperExceptionPage();
20 |
21 | app.UseHttpsRedirection();
22 | app.UseRouting();
23 | app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/samples/setting-up-dependency-injection/setting-up-dependency-injection.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | setting_up_dependency_injection
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/subscribing-to-streams/subscribing-to-streams.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | subscribing_to_streams
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/samples/user-certificates/Program.cs:
--------------------------------------------------------------------------------
1 | using KurrentDB.Client;
2 |
3 | await ClientWithUserCertificates();
4 |
5 | return;
6 |
7 | static async Task ClientWithUserCertificates() {
8 | try {
9 | # region client-with-user-certificates
10 |
11 | const string userCertFile = "/path/to/user.crt";
12 | const string userKeyFile = "/path/to/user.key";
13 |
14 | var settings = KurrentDBClientSettings.Create(
15 | $"esdb://localhost:2113/?tls=true&tlsVerifyCert=true&userCertFile={userCertFile}&userKeyFile={userKeyFile}"
16 | );
17 |
18 | await using var client = new KurrentDBClient(settings);
19 |
20 | # endregion client-with-user-certificates
21 | } catch (InvalidClientCertificateException) {
22 | // ignore for sample purposes
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/samples/user-certificates/user-certificates.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 | user_certificates
7 | enable
8 | enable
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/ChannelBaseExtensions.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 |
3 | namespace KurrentDB.Client;
4 |
5 | static class ChannelBaseExtensions {
6 | public static async ValueTask DisposeAsync(this ChannelBase channel) {
7 | await channel.ShutdownAsync().ConfigureAwait(false);
8 | (channel as IDisposable)?.Dispose();
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/ChannelInfo.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 |
3 | namespace KurrentDB.Client {
4 | #pragma warning disable 1591
5 | public record ChannelInfo(
6 | ChannelBase Channel,
7 | ServerCapabilities ServerCapabilities,
8 | CallInvoker CallInvoker);
9 | }
10 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/ChannelSelector.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using Grpc.Core;
5 |
6 | namespace KurrentDB.Client {
7 | internal class ChannelSelector : IChannelSelector {
8 | private readonly IChannelSelector _inner;
9 |
10 | public ChannelSelector(
11 | KurrentDBClientSettings settings,
12 | ChannelCache channelCache) {
13 | _inner = settings.ConnectivitySettings.IsSingleNode
14 | ? new SingleNodeChannelSelector(settings, channelCache)
15 | : new GossipChannelSelector(settings, channelCache, new GrpcGossipClient(settings));
16 | }
17 |
18 | public Task SelectChannelAsync(CancellationToken cancellationToken) =>
19 | _inner.SelectChannelAsync(cancellationToken);
20 |
21 | public ChannelBase SelectChannel(DnsEndPoint endPoint) =>
22 | _inner.SelectChannel(endPoint);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/ClusterMessage.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 |
3 | namespace KurrentDB.Client {
4 | internal static class ClusterMessages {
5 | public record ClusterInfo(MemberInfo[] Members);
6 |
7 | public record MemberInfo(Uuid InstanceId, VNodeState State, bool IsAlive, DnsEndPoint EndPoint);
8 |
9 | public enum VNodeState {
10 | Initializing = 0,
11 | DiscoverLeader = 1,
12 | Unknown = 2,
13 | PreReplica = 3,
14 | CatchingUp = 4,
15 | Clone = 5,
16 | Follower = 6,
17 | PreLeader = 7,
18 | Leader = 8,
19 | Manager = 9,
20 | ShuttingDown = 10,
21 | Shutdown = 11,
22 | ReadOnlyLeaderless = 12,
23 | PreReadOnlyReplica = 13,
24 | ReadOnlyReplica = 14,
25 | ResigningLeader = 15
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/AsyncStreamReaderExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Channels;
2 | using System.Runtime.CompilerServices;
3 | using Grpc.Core;
4 |
5 | namespace KurrentDB.Client;
6 |
7 | static class AsyncStreamReaderExtensions {
8 | public static async IAsyncEnumerable ReadAllAsync(
9 | this IAsyncStreamReader reader,
10 | [EnumeratorCancellation]
11 | CancellationToken cancellationToken = default
12 | ) {
13 | while (await reader.MoveNext(cancellationToken).ConfigureAwait(false))
14 | yield return reader.Current;
15 | }
16 |
17 | public static async IAsyncEnumerable ReadAllAsync(this ChannelReader reader, [EnumeratorCancellation] CancellationToken cancellationToken = default) {
18 | #if NET
19 | await foreach (var item in reader.ReadAllAsync(cancellationToken))
20 | yield return item;
21 | #else
22 | while (await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false)) {
23 | while (reader.TryRead(out T? item)) {
24 | yield return item;
25 | }
26 | }
27 | #endif
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/ActivityTagsCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Runtime.CompilerServices;
3 | using KurrentDB.Client;
4 | using Kurrent.Diagnostics;
5 | using Kurrent.Diagnostics.Telemetry;
6 |
7 | namespace KurrentDB.Client.Diagnostics;
8 |
9 | static class ActivityTagsCollectionExtensions {
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static ActivityTagsCollection WithGrpcChannelServerTags(this ActivityTagsCollection tags, ChannelInfo? channelInfo) {
12 | if (channelInfo is null)
13 | return tags;
14 |
15 | var authorityParts = channelInfo.Channel.Target.Split(':');
16 |
17 | return tags
18 | .WithRequiredTag(TelemetryTags.Server.Address, authorityParts[0])
19 | .WithRequiredTag(TelemetryTags.Server.Port, int.Parse(authorityParts[1]));
20 | }
21 |
22 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
23 | public static ActivityTagsCollection WithClientSettingsServerTags(this ActivityTagsCollection source, KurrentDBClientSettings settings) {
24 | if (settings.ConnectivitySettings.DnsGossipSeeds?.Length != 1)
25 | return source;
26 |
27 | var gossipSeed = settings.ConnectivitySettings.DnsGossipSeeds[0];
28 |
29 | return source
30 | .WithRequiredTag(TelemetryTags.Server.Address, gossipSeed.Host)
31 | .WithRequiredTag(TelemetryTags.Server.Port, gossipSeed.Port);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/ActivityStatus.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | using System.Diagnostics;
4 |
5 | namespace Kurrent.Diagnostics;
6 |
7 | record ActivityStatus(ActivityStatusCode StatusCode, string? Description, Exception? Exception) {
8 | public static ActivityStatus Ok(string? description = null) =>
9 | new(ActivityStatusCode.Ok, description, null);
10 |
11 | public static ActivityStatus Error(Exception exception, string? description = null) =>
12 | new(ActivityStatusCode.Error, description ?? exception.Message, exception);
13 | }
14 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/ActivityStatusCodeHelper.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | using System.Diagnostics;
4 | using System.Runtime.CompilerServices;
5 |
6 | using static System.Diagnostics.ActivityStatusCode;
7 | using static System.StringComparison;
8 |
9 | namespace Kurrent.Diagnostics;
10 |
11 | static class ActivityStatusCodeHelper {
12 | public const string UnsetStatusCodeTagValue = "UNSET";
13 | public const string OkStatusCodeTagValue = "OK";
14 | public const string ErrorStatusCodeTagValue = "ERROR";
15 |
16 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
17 | public static string? GetTagValueForStatusCode(ActivityStatusCode statusCode) =>
18 | statusCode switch {
19 | Unset => UnsetStatusCodeTagValue,
20 | Error => ErrorStatusCodeTagValue,
21 | Ok => OkStatusCodeTagValue,
22 | _ => null
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/ActivityTagsCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | using System.Diagnostics;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace Kurrent.Diagnostics;
7 |
8 | static class ActivityTagsCollectionExtensions {
9 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
10 | public static ActivityTagsCollection WithRequiredTag(this ActivityTagsCollection source, string key, object? value) {
11 | source[key] = value ?? throw new ArgumentNullException(key);
12 | return source;
13 | }
14 |
15 | ///
16 | /// - If the key previously existed in the collection and the value is , the collection item matching the key will get removed from the collection.
17 | /// - If the key previously existed in the collection and the value is not , the value will replace the old value stored in the collection.
18 | /// - Otherwise, a new item will get added to the collection.
19 | ///
20 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
21 | public static ActivityTagsCollection WithOptionalTag(this ActivityTagsCollection source, string key, object? value) {
22 | source[key] = value;
23 | return source;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/ExceptionExtensions.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | using System.Globalization;
4 |
5 | namespace Kurrent.Diagnostics;
6 |
7 | static class ExceptionExtensions {
8 | ///
9 | /// Returns a culture-independent string representation of the given object,
10 | /// appropriate for diagnostics tracing.
11 | ///
12 | /// Exception to convert to string.
13 | /// Exception as string with no culture.
14 | public static string ToInvariantString(this Exception exception) {
15 | var originalUiCulture = Thread.CurrentThread.CurrentUICulture;
16 |
17 | try {
18 | Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture;
19 | return exception.ToString();
20 | }
21 | finally {
22 | Thread.CurrentThread.CurrentUICulture = originalUiCulture;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/Telemetry/TelemetryTags.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | namespace Kurrent.Diagnostics.Telemetry;
4 |
5 | // The attributes below match the specification of v1.24.0 of the Open Telemetry semantic conventions.
6 | // Some attributes are ignored where not required or relevant.
7 | // https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/general/trace.md
8 | // https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md
9 | // https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/exceptions/exceptions-spans.md
10 |
11 | static partial class TelemetryTags {
12 | public static class Database {
13 | public const string User = "db.user";
14 | public const string System = "db.system";
15 | public const string Operation = "db.operation";
16 | }
17 |
18 | public static class Server {
19 | public const string Address = "server.address";
20 | public const string Port = "server.port";
21 | public const string SocketAddress = "server.socket.address"; // replaces: "net.peer.ip" (AttributeNetPeerIp)
22 | }
23 |
24 | public static class Exception {
25 | public const string EventName = "exception";
26 | public const string Type = "exception.type";
27 | public const string Message = "exception.message";
28 | public const string Stacktrace = "exception.stacktrace";
29 | }
30 |
31 | public static class Otel {
32 | public const string StatusCode = "otel.status_code";
33 | public const string StatusDescription = "otel.status_description";
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/Tracing/TracingConstants.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | namespace Kurrent.Diagnostics.Tracing;
4 |
5 | static partial class TracingConstants {
6 | public static class Metadata {
7 | public const string TraceId = "$traceId";
8 | public const string SpanId = "$spanId";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Core/Tracing/TracingMetadata.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | using System.Diagnostics;
4 | using System.Text.Json.Serialization;
5 |
6 | namespace Kurrent.Diagnostics.Tracing;
7 |
8 | readonly record struct TracingMetadata(
9 | [property: JsonPropertyName(TracingConstants.Metadata.TraceId)]
10 | string? TraceId,
11 | [property: JsonPropertyName(TracingConstants.Metadata.SpanId)]
12 | string? SpanId
13 | ) {
14 | public static readonly TracingMetadata None = new(null, null);
15 |
16 | [JsonIgnore] public bool IsValid => TraceId != null && SpanId != null;
17 |
18 | public ActivityContext? ToActivityContext(bool isRemote = true) {
19 | try {
20 | return IsValid
21 | ? new ActivityContext(
22 | ActivityTraceId.CreateFromString(new ReadOnlySpan(TraceId!.ToCharArray())),
23 | ActivitySpanId.CreateFromString(new ReadOnlySpan(SpanId!.ToCharArray())),
24 | ActivityTraceFlags.Recorded,
25 | isRemote: isRemote
26 | )
27 | : default;
28 | } catch (Exception) {
29 | return default;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/KurrentDBClientDiagnostics.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace KurrentDB.Client.Diagnostics;
4 |
5 | public static class KurrentDBClientDiagnostics {
6 | public const string InstrumentationName = "kurrent";
7 | public static readonly ActivitySource ActivitySource = new(InstrumentationName);
8 | }
9 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Telemetry/TelemetryTags.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | namespace Kurrent.Diagnostics.Telemetry;
4 |
5 | static partial class TelemetryTags {
6 | public static class Kurrent {
7 | public const string Stream = "db.kurrent.stream";
8 | public const string SubscriptionId = "db.kurrent.subscription.id";
9 | public const string EventId = "db.kurrent.event.id";
10 | public const string EventType = "db.kurrent.event.type";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Diagnostics/Tracing/TracingConstants.cs:
--------------------------------------------------------------------------------
1 | // ReSharper disable CheckNamespace
2 |
3 | namespace Kurrent.Diagnostics.Tracing;
4 |
5 | static partial class TracingConstants {
6 | public static class Operations {
7 | public const string Append = "streams.append";
8 | public const string Subscribe = "streams.subscribe";
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/EnumerableTaskExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 |
3 | namespace KurrentDB.Client;
4 |
5 | static class EnumerableTaskExtensions {
6 | [DebuggerStepThrough]
7 | public static Task WhenAll(this IEnumerable source) => Task.WhenAll(source);
8 |
9 | [DebuggerStepThrough]
10 | public static Task WhenAll(this IEnumerable> source) => Task.WhenAll(source);
11 | }
12 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/EpochExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace KurrentDB.Client;
2 |
3 | static class EpochExtensions {
4 | #if NET
5 | static readonly DateTime UnixEpoch = DateTime.UnixEpoch;
6 | #else
7 | const long TicksPerMillisecond = 10000;
8 | const long TicksPerSecond = TicksPerMillisecond * 1000;
9 | const long TicksPerMinute = TicksPerSecond * 60;
10 | const long TicksPerHour = TicksPerMinute * 60;
11 | const long TicksPerDay = TicksPerHour * 24;
12 | const int DaysPerYear = 365;
13 | const int DaysPer4Years = DaysPerYear * 4 + 1;
14 | const int DaysPer100Years = DaysPer4Years * 25 - 1;
15 | const int DaysPer400Years = DaysPer100Years * 4 + 1;
16 | const int DaysTo1970 = DaysPer400Years * 4 + DaysPer100Years * 3 + DaysPer4Years * 17 + DaysPerYear;
17 | const long UnixEpochTicks = DaysTo1970 * TicksPerDay;
18 |
19 | static readonly DateTime UnixEpoch = new(UnixEpochTicks, DateTimeKind.Utc);
20 | #endif
21 |
22 | public static DateTime FromTicksSinceEpoch(this long value) => new(UnixEpoch.Ticks + value, DateTimeKind.Utc);
23 |
24 | public static long ToTicksSinceEpoch(this DateTime value) => (value - UnixEpoch).Ticks;
25 | }
26 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/MetadataExtensions.cs:
--------------------------------------------------------------------------------
1 | using Grpc.Core;
2 |
3 | namespace KurrentDB.Client;
4 |
5 | static class MetadataExtensions {
6 | public static bool TryGetValue(this Metadata metadata, string key, out string? value) {
7 | value = default;
8 |
9 | foreach (var entry in metadata) {
10 | if (entry.Key != key)
11 | continue;
12 |
13 | value = entry.Value;
14 | return true;
15 | }
16 |
17 | return false;
18 | }
19 |
20 | public static StreamState GetStreamState(this Metadata metadata, string key) =>
21 | metadata.TryGetValue(key, out var s) && ulong.TryParse(s, out var value)
22 | ? value
23 | : StreamState.NoStream;
24 |
25 | public static int GetIntValueOrDefault(this Metadata metadata, string key) =>
26 | metadata.TryGetValue(key, out var s) && int.TryParse(s, out var value)
27 | ? value
28 | : default;
29 | }
30 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Shims/IsExternalInit.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 |
3 | using System.ComponentModel;
4 |
5 | // ReSharper disable once CheckNamespace
6 | namespace System.Runtime.CompilerServices;
7 |
8 | [EditorBrowsable(EditorBrowsableState.Never)]
9 | class IsExternalInit{}
10 | #endif
11 |
--------------------------------------------------------------------------------
/src/KurrentDB.Client/Core/Common/Shims/TaskCompletionSource.cs:
--------------------------------------------------------------------------------
1 | #if !NET
2 | // ReSharper disable CheckNamespace
3 |
4 | namespace System.Threading.Tasks;
5 |
6 | class TaskCompletionSource : TaskCompletionSource