├── .editorconfig ├── .gitattributes ├── .gitignore ├── Bookings.Domain ├── Bookings.Domain.csproj ├── Bookings │ ├── Booking.cs │ ├── BookingEvents.cs │ ├── BookingId.cs │ └── BookingState.cs ├── DomainModule.cs ├── Money.cs ├── RoomId.cs ├── Services.cs └── StayPeriod.cs ├── Bookings.Payments ├── Application │ ├── CommandApi.cs │ └── CommandService.cs ├── Bookings.Payments.csproj ├── Domain │ ├── Money.cs │ ├── Payment.cs │ └── PaymentEvents.cs ├── Infrastructure │ ├── Logging.cs │ └── Mongo.cs ├── Integration │ └── Payments.cs ├── Program.cs ├── Registrations.cs └── appsettings.json ├── Bookings.sln ├── Bookings.sln.DotSettings ├── Bookings ├── .dockerignore ├── Application │ ├── BookingsCommandService.cs │ ├── Commands.cs │ └── Queries │ │ ├── BookingDocument.cs │ │ ├── BookingStateProjection.cs │ │ ├── MyBookings.cs │ │ └── MyBookingsProjection.cs ├── Bookings.csproj ├── Dockerfile ├── HttpApi │ └── Bookings │ │ ├── CommandApi.cs │ │ └── QueryApi.cs ├── Infrastructure │ └── Mongo.cs ├── Integration │ └── Payments.cs ├── Program.cs ├── Registrations.cs └── appsettings.json ├── Directory.Build.props ├── NuGet.config ├── README.md ├── deploy └── cloudrun │ ├── .gitignore │ ├── Pulumi.yaml │ ├── index.ts │ ├── package.json │ ├── tsconfig.json │ └── yarn.lock ├── docker-compose.yml ├── grafana ├── __inputs.json └── datasources.yml └── prometheus └── prometheus.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*] 3 | 4 | # ReSharper properties 5 | resharper_instance_members_qualify_declared_in = this_class 6 | resharper_int_align_assignments = false 7 | resharper_use_heuristics_for_body_style = true 8 | resharper_wrap_after_invocation_lpar = false 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Explicitly declare text files we want to always be normalized and converted 2 | # to native line endings on checkout. 3 | 4 | *.pdf binary 5 | *.sch binary 6 | *.isch binary 7 | *.ist binary 8 | 9 | # Explicitly declare text files we want to always be normalized and converted 10 | # to native line endings on checkout. 11 | *.c text 12 | *.h text 13 | *.cs text 14 | *.config text 15 | *.xml text 16 | *.manifest text 17 | *.bat text 18 | *.cmd text 19 | *.sh text 20 | *.txt text 21 | *.dat text 22 | *.rc text 23 | *.ps1 text 24 | *.psm1 text 25 | *.js text 26 | *.css text 27 | *.html text 28 | *.sln text 29 | *.DotSettings text 30 | *.csproj text 31 | *.ncrunchproject text 32 | *.fs text 33 | *.fsproj text 34 | *.liquid text 35 | *.boo text 36 | *.pp text 37 | *.targets text 38 | *.markdown text 39 | *.md text 40 | *.bat text 41 | *.xslt text 42 | 43 | # Declare files that will always have CRLF line endings on checkout. 44 | 45 | # Denote all files that are truly binary and should not be modified. 46 | *.ico binary 47 | *.gif binary 48 | *.png binary 49 | *.jpg binary 50 | *.dll binary 51 | *.exe binary 52 | *.pdb binary 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/macos,csharp 3 | 4 | ### Csharp ### 5 | ## Ignore Visual Studio temporary files, build results, and 6 | ## files generated by popular Visual Studio add-ons. 7 | ## 8 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | *.userosscache 14 | *.sln.docstates 15 | 16 | # User-specific files (MonoDevelop/Xamarin Studio) 17 | *.userprefs 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | bld/ 27 | [Bb]in/ 28 | [Oo]bj/ 29 | [Ll]og/ 30 | 31 | # Visual Studio 2015 cache/options directory 32 | .vs/ 33 | # Uncomment if you have tasks that create the project's static files in wwwroot 34 | #wwwroot/ 35 | 36 | # MSTest test Results 37 | [Tt]est[Rr]esult*/ 38 | [Bb]uild[Ll]og.* 39 | 40 | # NUNIT 41 | *.VisualState.xml 42 | TestResult.xml 43 | 44 | # Build Results of an ATL Project 45 | [Dd]ebugPS/ 46 | [Rr]eleasePS/ 47 | dlldata.c 48 | 49 | # .NET Core 50 | project.lock.json 51 | project.fragment.lock.json 52 | artifacts/ 53 | **/Properties/launchSettings.json 54 | 55 | *_i.c 56 | *_p.c 57 | *_i.h 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.svclog 78 | *.scc 79 | 80 | # Chutzpah Test files 81 | _Chutzpah* 82 | 83 | # Visual C++ cache files 84 | ipch/ 85 | *.aps 86 | *.ncb 87 | *.opendb 88 | *.opensdf 89 | *.sdf 90 | *.cachefile 91 | *.VC.db 92 | *.VC.VC.opendb 93 | 94 | # Visual Studio profiler 95 | *.psess 96 | *.vsp 97 | *.vspx 98 | *.sap 99 | 100 | # TFS 2012 Local Workspace 101 | $tf/ 102 | 103 | # Guidance Automation Toolkit 104 | *.gpState 105 | 106 | # ReSharper is a .NET coding add-in 107 | _ReSharper*/ 108 | *.[Rr]e[Ss]harper 109 | *.DotSettings.user 110 | 111 | # JustCode is a .NET coding add-in 112 | .JustCode 113 | 114 | # TeamCity is a build add-in 115 | _TeamCity* 116 | 117 | # DotCover is a Code Coverage Tool 118 | *.dotCover 119 | 120 | # Visual Studio code coverage results 121 | *.coverage 122 | *.coveragexml 123 | 124 | # NCrunch 125 | _NCrunch_* 126 | .*crunch*.local.xml 127 | nCrunchTemp_* 128 | 129 | # MightyMoose 130 | *.mm.* 131 | AutoTest.Net/ 132 | 133 | # Web workbench (sass) 134 | .sass-cache/ 135 | 136 | # Installshield output folder 137 | [Ee]xpress/ 138 | 139 | # DocProject is a documentation generator add-in 140 | DocProject/buildhelp/ 141 | DocProject/Help/*.HxT 142 | DocProject/Help/*.HxC 143 | DocProject/Help/*.hhc 144 | DocProject/Help/*.hhk 145 | DocProject/Help/*.hhp 146 | DocProject/Help/Html2 147 | DocProject/Help/html 148 | 149 | # Click-Once directory 150 | publish/ 151 | 152 | # Publish Web Output 153 | *.[Pp]ublish.xml 154 | *.azurePubxml 155 | # TODO: Uncomment the next line to ignore your web deploy settings. 156 | # By default, sensitive information, such as encrypted password 157 | # should be stored in the .pubxml.user file. 158 | #*.pubxml 159 | *.pubxml.user 160 | *.publishproj 161 | 162 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 163 | # checkin your Azure Web App publish settings, but sensitive information contained 164 | # in these scripts will be unencrypted 165 | PublishScripts/ 166 | 167 | # NuGet Packages 168 | *.nupkg 169 | # The packages folder can be ignored because of Package Restore 170 | **/packages/* 171 | # except build/, which is used as an MSBuild target. 172 | !**/packages/build/ 173 | # Uncomment if necessary however generally it will be regenerated when needed 174 | #!**/packages/repositories.config 175 | # NuGet v3's project.json files produces more ignorable files 176 | *.nuget.props 177 | *.nuget.targets 178 | 179 | # Microsoft Azure Build Output 180 | csx/ 181 | *.build.csdef 182 | 183 | # Microsoft Azure Emulator 184 | ecf/ 185 | rcf/ 186 | 187 | # Windows Store app package directories and files 188 | AppPackages/ 189 | BundleArtifacts/ 190 | Package.StoreAssociation.xml 191 | _pkginfo.txt 192 | 193 | # Visual Studio cache files 194 | # files ending in .cache can be ignored 195 | *.[Cc]ache 196 | # but keep track of directories ending in .cache 197 | !*.[Cc]ache/ 198 | 199 | # Others 200 | ClientBin/ 201 | ~$* 202 | *~ 203 | *.dbmdl 204 | *.dbproj.schemaview 205 | *.jfm 206 | *.pfx 207 | *.publishsettings 208 | orleans.codegen.cs 209 | 210 | # Since there are multiple workflows, uncomment next line to ignore bower_components 211 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 212 | #bower_components/ 213 | 214 | # RIA/Silverlight projects 215 | Generated_Code/ 216 | 217 | # Backup & report files from converting an old project file 218 | # to a newer Visual Studio version. Backup files are not needed, 219 | # because we have git ;-) 220 | _UpgradeReport_Files/ 221 | Backup*/ 222 | UpgradeLog*.XML 223 | UpgradeLog*.htm 224 | 225 | # SQL Server files 226 | *.mdf 227 | *.ldf 228 | *.ndf 229 | 230 | # Business Intelligence projects 231 | *.rdl.data 232 | *.bim.layout 233 | *.bim_*.settings 234 | 235 | # Microsoft Fakes 236 | FakesAssemblies/ 237 | 238 | # GhostDoc plugin setting file 239 | *.GhostDoc.xml 240 | 241 | # Node.js Tools for Visual Studio 242 | .ntvs_analysis.dat 243 | node_modules/ 244 | 245 | # Typescript v1 declaration files 246 | typings/ 247 | 248 | # Visual Studio 6 build log 249 | *.plg 250 | 251 | # Visual Studio 6 workspace options file 252 | *.opt 253 | 254 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 255 | *.vbw 256 | 257 | # Visual Studio LightSwitch build output 258 | **/*.HTMLClient/GeneratedArtifacts 259 | **/*.DesktopClient/GeneratedArtifacts 260 | **/*.DesktopClient/ModelManifest.xml 261 | **/*.Server/GeneratedArtifacts 262 | **/*.Server/ModelManifest.xml 263 | _Pvt_Extensions 264 | 265 | # JetBrains Rider 266 | .idea/ 267 | *.sln.iml 268 | 269 | # CodeRush 270 | .cr/ 271 | 272 | ### macOS ### 273 | *.DS_Store 274 | .AppleDouble 275 | .LSOverride 276 | 277 | # Icon must end with two \r 278 | Icon 279 | 280 | # Thumbnails 281 | ._* 282 | 283 | # Files that might appear in the root of a volume 284 | .DocumentRevisions-V100 285 | .fseventsd 286 | .Spotlight-V100 287 | .TemporaryItems 288 | .Trashes 289 | .VolumeIcon.icns 290 | .com.apple.timemachine.donotpresent 291 | 292 | # Directories potentially created on remote AFP share 293 | .AppleDB 294 | .AppleDesktop 295 | Network Trash Folder 296 | Temporary Items 297 | .apdisk 298 | 299 | # End of https://www.gitignore.io/api/macos,csharpifest.xml 300 | _Pvt_Extensions 301 | 302 | ## Custom ## 303 | exclusive.lck 304 | *.dll 305 | *.exe 306 | .vscode 307 | version.txt 308 | /public/ 309 | -------------------------------------------------------------------------------- /Bookings.Domain/Bookings.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Bookings.Domain/Bookings/Booking.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using static Bookings.Domain.Bookings.BookingEvents; 3 | using static Bookings.Domain.Services; 4 | 5 | namespace Bookings.Domain.Bookings; 6 | 7 | public class Booking : Aggregate { 8 | public async Task BookRoom( 9 | string guestId, 10 | RoomId roomId, 11 | StayPeriod period, 12 | Money price, 13 | Money prepaid, 14 | DateTimeOffset bookedAt, 15 | IsRoomAvailable isRoomAvailable 16 | ) { 17 | EnsureDoesntExist(); 18 | await EnsureRoomAvailable(roomId, period, isRoomAvailable); 19 | 20 | var outstanding = price - prepaid; 21 | 22 | Apply( 23 | new V1.RoomBooked( 24 | guestId, 25 | roomId, 26 | period.CheckIn, 27 | period.CheckOut, 28 | price.Amount, 29 | prepaid.Amount, 30 | outstanding.Amount, 31 | price.Currency, 32 | bookedAt 33 | ) 34 | ); 35 | 36 | MarkFullyPaidIfNecessary(bookedAt); 37 | } 38 | 39 | public void RecordPayment( 40 | Money paid, 41 | string paymentId, 42 | string paidBy, 43 | DateTimeOffset paidAt 44 | ) { 45 | EnsureExists(); 46 | 47 | if (State.HasPaymentBeenRegistered(paymentId)) return; 48 | 49 | var outstanding = State.Outstanding - paid; 50 | 51 | Apply( 52 | new V1.PaymentRecorded( 53 | paid.Amount, 54 | outstanding.Amount, 55 | paid.Currency, 56 | paymentId, 57 | paidBy, 58 | paidAt 59 | ) 60 | ); 61 | 62 | MarkFullyPaidIfNecessary(paidAt); 63 | MarkOverpaid(paidAt); 64 | } 65 | 66 | void MarkFullyPaidIfNecessary(DateTimeOffset when) { 67 | if (State.Outstanding.Amount <= 0) 68 | Apply(new V1.BookingFullyPaid(when)); 69 | } 70 | 71 | void MarkOverpaid(DateTimeOffset when) { 72 | if (State.Outstanding.Amount < 0) 73 | Apply(new V1.BookingOverpaid(when)); 74 | } 75 | 76 | static async Task EnsureRoomAvailable(RoomId roomId, StayPeriod period, IsRoomAvailable isRoomAvailable) { 77 | var roomAvailable = await isRoomAvailable(roomId, period); 78 | if (!roomAvailable) throw new DomainException("Room not available"); 79 | } 80 | } -------------------------------------------------------------------------------- /Bookings.Domain/Bookings/BookingEvents.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using NodaTime; 3 | 4 | namespace Bookings.Domain.Bookings; 5 | 6 | public static class BookingEvents { 7 | public static class V1 { 8 | [EventType("V1.RoomBooked")] 9 | public record RoomBooked( 10 | string GuestId, 11 | string RoomId, 12 | LocalDate CheckInDate, 13 | LocalDate CheckOutDate, 14 | float BookingPrice, 15 | float PrepaidAmount, 16 | float OutstandingAmount, 17 | string Currency, 18 | DateTimeOffset BookingDate 19 | ); 20 | 21 | [EventType("V1.PaymentRecorded")] 22 | public record PaymentRecorded( 23 | float PaidAmount, 24 | float Outstanding, 25 | string Currency, 26 | string PaymentId, 27 | string PaidBy, 28 | DateTimeOffset PaidAt 29 | ); 30 | 31 | [EventType("V1.FullyPaid")] 32 | public record BookingFullyPaid(DateTimeOffset FullyPaidAt); 33 | 34 | [EventType("V1.Overpaid")] 35 | public record BookingOverpaid(DateTimeOffset OverpaidAt); 36 | 37 | [EventType("V1.BookingCancelled")] 38 | public record BookingCancelled(string CancelledBy, DateTimeOffset CancelledAt); 39 | } 40 | } -------------------------------------------------------------------------------- /Bookings.Domain/Bookings/BookingId.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Domain.Bookings; 4 | 5 | public record BookingId(string Value) : AggregateId(Value); -------------------------------------------------------------------------------- /Bookings.Domain/Bookings/BookingState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Eventuous; 3 | using static Bookings.Domain.Bookings.BookingEvents; 4 | // ReSharper disable MemberCanBePrivate.Global 5 | // ReSharper disable UnusedAutoPropertyAccessor.Global 6 | // ReSharper disable NotAccessedPositionalProperty.Global 7 | 8 | namespace Bookings.Domain.Bookings; 9 | 10 | public record BookingState : State { 11 | public string GuestId { get; init; } = null!; 12 | public RoomId RoomId { get; init; } = null!; 13 | public StayPeriod Period { get; init; } = null!; 14 | public Money Price { get; init; } = null!; 15 | public Money Outstanding { get; init; } = null!; 16 | public bool Paid { get; init; } 17 | 18 | public ImmutableArray Payments { get; init; } = ImmutableArray.Empty; 19 | 20 | internal bool HasPaymentBeenRegistered(string paymentId) => Payments.Any(x => x.PaymentId == paymentId); 21 | 22 | public BookingState() { 23 | On(HandleBooked); 24 | On(HandlePayment); 25 | On((state, paid) => state with { Paid = true }); 26 | } 27 | 28 | static BookingState HandlePayment(BookingState state, V1.PaymentRecorded e) 29 | => state with { 30 | Outstanding = new Money { Amount = e.Outstanding, Currency = e.Currency }, 31 | Payments = state.Payments.Add(new PaymentRecord(e.PaymentId, new Money(e.PaidAmount, e.Currency))) 32 | }; 33 | 34 | static BookingState HandleBooked(BookingState state, V1.RoomBooked booked) 35 | => state with { 36 | RoomId = new RoomId(booked.RoomId), 37 | Period = new StayPeriod(booked.CheckInDate, booked.CheckOutDate), 38 | GuestId = booked.GuestId, 39 | Price = new Money { Amount = booked.BookingPrice, Currency = booked.Currency }, 40 | Outstanding = new Money { Amount = booked.OutstandingAmount, Currency = booked.Currency } 41 | }; 42 | } 43 | 44 | public record PaymentRecord(string PaymentId, Money PaidAmount); 45 | 46 | public record DiscountRecord(Money Discount, string Reason); -------------------------------------------------------------------------------- /Bookings.Domain/DomainModule.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.Runtime.CompilerServices; 3 | using Eventuous; 4 | 5 | namespace Bookings.Domain; 6 | 7 | static class DomainModule { 8 | [ModuleInitializer] 9 | [SuppressMessage("Usage", "CA2255", MessageId = "The \'ModuleInitializer\' attribute should not be used in libraries")] 10 | internal static void InitializeDomainModule() => TypeMap.RegisterKnownEventTypes(); 11 | } -------------------------------------------------------------------------------- /Bookings.Domain/Money.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Domain; 4 | 5 | public record Money { 6 | public float Amount { get; internal init; } 7 | public string Currency { get; internal init; } = null!; 8 | 9 | static readonly string[] SupportedCurrencies = {"USD", "GPB", "EUR"}; 10 | 11 | internal Money() { } 12 | 13 | public Money(float amount, string currency) { 14 | if (!SupportedCurrencies.Contains(currency)) throw new DomainException($"Unsupported currency: {currency}"); 15 | 16 | Amount = amount; 17 | Currency = currency; 18 | } 19 | 20 | public bool IsSameCurrency(Money another) => Currency == another.Currency; 21 | 22 | public static Money operator -(Money one, Money another) { 23 | if (!one.IsSameCurrency(another)) throw new DomainException("Cannot operate on different currencies"); 24 | 25 | return new Money(one.Amount - another.Amount, one.Currency); 26 | } 27 | 28 | public static implicit operator double(Money money) => money.Amount; 29 | } -------------------------------------------------------------------------------- /Bookings.Domain/RoomId.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Domain; 4 | 5 | public record RoomId(string Value) : AggregateId(Value); -------------------------------------------------------------------------------- /Bookings.Domain/Services.cs: -------------------------------------------------------------------------------- 1 | namespace Bookings.Domain; 2 | 3 | public static class Services { 4 | public delegate ValueTask IsRoomAvailable(RoomId roomId, StayPeriod period); 5 | 6 | public delegate Money ConvertCurrency(Money from, string targetCurrency); 7 | } -------------------------------------------------------------------------------- /Bookings.Domain/StayPeriod.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using NodaTime; 3 | 4 | namespace Bookings.Domain; 5 | 6 | public record StayPeriod { 7 | public LocalDate CheckIn { get; } 8 | public LocalDate CheckOut { get; } 9 | 10 | internal StayPeriod() { } 11 | 12 | public StayPeriod(LocalDate checkIn, LocalDate checkOut) { 13 | if (checkIn > checkOut) throw new DomainException("Check in date must be before check out date"); 14 | 15 | (CheckIn, CheckOut) = (checkIn, checkOut); 16 | } 17 | } -------------------------------------------------------------------------------- /Bookings.Payments/Application/CommandApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Payments.Domain; 2 | using Eventuous; 3 | using Eventuous.AspNetCore.Web; 4 | using Microsoft.AspNetCore.Mvc; 5 | using static Bookings.Payments.Application.PaymentCommands; 6 | 7 | namespace Bookings.Payments.Application; 8 | 9 | [Route("payment")] 10 | public class CommandApi : CommandHttpApiBase { 11 | public CommandApi(IApplicationService service) : base(service) { } 12 | 13 | [HttpPost] 14 | public Task> RegisterPayment([FromBody] RecordPayment cmd, CancellationToken cancellationToken) 15 | => Handle(cmd, cancellationToken); 16 | } -------------------------------------------------------------------------------- /Bookings.Payments/Application/CommandService.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using Bookings.Payments.Domain; 3 | using Eventuous; 4 | using Eventuous.AspNetCore.Web; 5 | 6 | namespace Bookings.Payments.Application; 7 | 8 | public class CommandService : CommandService { 9 | public CommandService(IAggregateStore store) : base(store) { 10 | OnNew( 11 | cmd => new PaymentId(cmd.PaymentId), 12 | (payment, cmd) => payment.ProcessPayment( 13 | new PaymentId(cmd.PaymentId), 14 | cmd.BookingId, 15 | new Money(cmd.Amount, cmd.Currency), 16 | cmd.Method, 17 | cmd.Provider 18 | ) 19 | ); 20 | } 21 | } 22 | 23 | // [AggregateCommands(typeof(Payment))] 24 | public static class PaymentCommands { 25 | [HttpCommand] 26 | public record RecordPayment( 27 | string PaymentId, 28 | string BookingId, 29 | float Amount, 30 | string Currency, 31 | string Method, 32 | string Provider, 33 | [property: JsonIgnore] string PaidBy 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /Bookings.Payments/Bookings.Payments.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | true 25 | PreserveNewest 26 | PreserveNewest 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Bookings.Payments/Domain/Money.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Payments.Domain; 4 | 5 | public record Money { 6 | public float Amount { get; internal init; } 7 | public string Currency { get; internal init; } = null!; 8 | 9 | static readonly string[] SupportedCurrencies = { "USD", "GPB", "EUR" }; 10 | 11 | // ReSharper disable once UnusedMember.Global 12 | internal Money() { } 13 | 14 | public Money(float amount, string currency) { 15 | if (!SupportedCurrencies.Contains(currency)) throw new DomainException($"Unsupported currency: {currency}"); 16 | 17 | Amount = amount; 18 | Currency = currency; 19 | } 20 | 21 | public bool IsSameCurrency(Money another) => Currency == another.Currency; 22 | 23 | public static Money operator -(Money one, Money another) { 24 | if (!one.IsSameCurrency(another)) throw new DomainException("Cannot operate on different currencies"); 25 | 26 | return new Money(one.Amount - another.Amount, one.Currency); 27 | } 28 | 29 | public static implicit operator double(Money money) => money.Amount; 30 | } -------------------------------------------------------------------------------- /Bookings.Payments/Domain/Payment.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | using static Bookings.Payments.Domain.PaymentEvents; 3 | 4 | namespace Bookings.Payments.Domain; 5 | 6 | public class Payment : Aggregate { 7 | public void ProcessPayment( 8 | PaymentId paymentId, string bookingId, Money amount, string method, string provider 9 | ) 10 | => Apply(new PaymentRecorded(paymentId, bookingId, amount.Amount, amount.Currency, method, provider)); 11 | } 12 | 13 | public record PaymentState : State { 14 | public string BookingId { get; init; } = null!; 15 | public float Amount { get; init; } 16 | 17 | public PaymentState() { 18 | On( 19 | (state, recorded) => state with { 20 | BookingId = recorded.BookingId, 21 | Amount = recorded.Amount 22 | } 23 | ); 24 | } 25 | } 26 | 27 | public record PaymentId(string Value) : AggregateId(Value); -------------------------------------------------------------------------------- /Bookings.Payments/Domain/PaymentEvents.cs: -------------------------------------------------------------------------------- 1 | using Eventuous; 2 | 3 | namespace Bookings.Payments.Domain; 4 | 5 | public static class PaymentEvents { 6 | [EventType("PaymentRecorded")] 7 | public record PaymentRecorded( 8 | string PaymentId, string BookingId, float Amount, string Currency, string Method, string Provider 9 | ); 10 | } -------------------------------------------------------------------------------- /Bookings.Payments/Infrastructure/Logging.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using Serilog.Events; 3 | 4 | namespace Bookings.Payments.Infrastructure; 5 | 6 | public static class Logging { 7 | public static void ConfigureLog() 8 | => Log.Logger = new LoggerConfiguration() 9 | .MinimumLevel.Debug() 10 | .MinimumLevel.Override("Microsoft", LogEventLevel.Information) 11 | .MinimumLevel.Override("Microsoft.AspNetCore.Hosting.Diagnostics", LogEventLevel.Warning) 12 | .MinimumLevel.Override("Microsoft.AspNetCore.Routing", LogEventLevel.Warning) 13 | .MinimumLevel.Override("Grpc", LogEventLevel.Information) 14 | .MinimumLevel.Override("EventStore", LogEventLevel.Information) 15 | .Enrich.FromLogContext() 16 | .WriteTo.Console( 17 | outputTemplate: 18 | "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {NewLine}{Exception}" 19 | ) 20 | .CreateLogger(); 21 | } 22 | -------------------------------------------------------------------------------- /Bookings.Payments/Infrastructure/Mongo.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Driver; 2 | using MongoDB.Driver.Core.Extensions.DiagnosticSources; 3 | 4 | namespace Bookings.Payments.Infrastructure; 5 | 6 | public static class Mongo { 7 | public static IMongoDatabase ConfigureMongo(IConfiguration configuration) { 8 | var settings = MongoClientSettings.FromConnectionString(configuration["Mongo:ConnectionString"]); 9 | settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); 10 | return new MongoClient(settings).GetDatabase(configuration["Mongo:Database"]); 11 | } 12 | } -------------------------------------------------------------------------------- /Bookings.Payments/Integration/Payments.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Payments.Domain; 2 | using Eventuous; 3 | using Eventuous.EventStore.Producers; 4 | using Eventuous.Gateway; 5 | using Eventuous.Subscriptions.Context; 6 | using static Bookings.Payments.Integration.IntegrationEvents; 7 | 8 | namespace Bookings.Payments.Integration; 9 | 10 | public static class PaymentsGateway { 11 | static readonly StreamName Stream = new("PaymentsIntegration"); 12 | 13 | public static ValueTask[]> Transform(IMessageConsumeContext original) { 14 | var result = original.Message is PaymentEvents.PaymentRecorded evt 15 | ? new GatewayMessage( 16 | Stream, 17 | new BookingPaymentRecorded(evt.PaymentId, evt.BookingId, evt.Amount, evt.Currency), 18 | new Metadata(), 19 | new EventStoreProduceOptions() 20 | ) 21 | : null; 22 | return ValueTask.FromResult(result != null ? new []{result} : Array.Empty>()); 23 | } 24 | } 25 | 26 | public static class IntegrationEvents { 27 | [EventType("BookingPaymentRecorded")] 28 | public record BookingPaymentRecorded(string PaymentId, string BookingId, float Amount, string Currency); 29 | } -------------------------------------------------------------------------------- /Bookings.Payments/Program.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Payments; 2 | using Bookings.Payments.Domain; 3 | using Bookings.Payments.Infrastructure; 4 | using Eventuous; 5 | using Eventuous.AspNetCore; 6 | using Serilog; 7 | 8 | TypeMap.RegisterKnownEventTypes(); 9 | Logging.ConfigureLog(); 10 | 11 | var builder = WebApplication.CreateBuilder(args); 12 | 13 | builder.Services.AddEndpointsApiExplorer(); 14 | builder.Services.AddSwaggerGen(); 15 | 16 | // OpenTelemetry instrumentation must be added before adding Eventuous services 17 | builder.Services.AddTelemetry(); 18 | 19 | builder.Services.AddServices(builder.Configuration); 20 | builder.Host.UseSerilog(); 21 | 22 | var app = builder.Build(); 23 | app.AddEventuousLogs(); 24 | 25 | app.UseSwagger(); 26 | app.UseOpenTelemetryPrometheusScrapingEndpoint(); 27 | 28 | // Here we discover commands by their annotations 29 | // app.MapDiscoveredCommands(); 30 | app.MapDiscoveredCommands(); 31 | 32 | app.UseSwaggerUI(); 33 | 34 | app.Run(); -------------------------------------------------------------------------------- /Bookings.Payments/Registrations.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Payments.Application; 2 | using Bookings.Payments.Domain; 3 | using Bookings.Payments.Infrastructure; 4 | using Bookings.Payments.Integration; 5 | using Eventuous.Diagnostics.OpenTelemetry; 6 | using Eventuous.EventStore; 7 | using Eventuous.EventStore.Producers; 8 | using Eventuous.EventStore.Subscriptions; 9 | using Eventuous.Producers; 10 | using Eventuous.Projections.MongoDB; 11 | using OpenTelemetry.Metrics; 12 | using OpenTelemetry.Resources; 13 | using OpenTelemetry.Trace; 14 | 15 | namespace Bookings.Payments; 16 | 17 | public static class Registrations { 18 | public static void AddServices(this IServiceCollection services, IConfiguration configuration) { 19 | services.AddEventStoreClient(configuration["EventStore:ConnectionString"]!); 20 | services.AddAggregateStore(); 21 | services.AddCommandService(); 22 | services.AddSingleton(Mongo.ConfigureMongo(configuration)); 23 | services.AddCheckpointStore(); 24 | services.AddEventProducer(); 25 | 26 | services 27 | .AddGateway( 28 | subscriptionId: "IntegrationSubscription", 29 | routeAndTransform: PaymentsGateway.Transform 30 | ); 31 | } 32 | 33 | public static void AddTelemetry(this IServiceCollection services) { 34 | services.AddOpenTelemetry() 35 | .WithMetrics( 36 | builder => builder 37 | .AddAspNetCoreInstrumentation() 38 | .AddEventuous() 39 | .AddEventuousSubscriptions() 40 | .AddPrometheusExporter() 41 | ); 42 | 43 | services.AddOpenTelemetry() 44 | .WithTracing( 45 | builder => builder 46 | .AddAspNetCoreInstrumentation() 47 | .AddGrpcClientInstrumentation() 48 | .AddEventuousTracing() 49 | .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("payments")) 50 | .SetSampler(new AlwaysOnSampler()) 51 | .AddZipkinExporter() 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Bookings.Payments/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://mongoadmin:secret@localhost:27017", 4 | "Database": "Payments" 5 | }, 6 | "EventStore": { 7 | "ConnectionString": "esdb://localhost:2113?tls=false" 8 | }, 9 | "Logging": { 10 | "LogLevel": { 11 | "Default": "Debug", 12 | "Microsoft.AspNetCore": "Warning" 13 | } 14 | }, 15 | "AllowedHosts": "*" 16 | } 17 | -------------------------------------------------------------------------------- /Bookings.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bookings", "Bookings\Bookings.csproj", "{7B4C2F9A-452E-4C08-86D5-F761C67089A6}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bookings.Domain", "Bookings.Domain\Bookings.Domain.csproj", "{280363FF-F078-44B7-9A32-20FA7D6046E5}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bookings.Payments", "Bookings.Payments\Bookings.Payments.csproj", "{EBD8B561-B354-4FDB-BF88-0BF0681F5530}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {7B4C2F9A-452E-4C08-86D5-F761C67089A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {7B4C2F9A-452E-4C08-86D5-F761C67089A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {7B4C2F9A-452E-4C08-86D5-F761C67089A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {7B4C2F9A-452E-4C08-86D5-F761C67089A6}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {280363FF-F078-44B7-9A32-20FA7D6046E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {280363FF-F078-44B7-9A32-20FA7D6046E5}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {280363FF-F078-44B7-9A32-20FA7D6046E5}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {280363FF-F078-44B7-9A32-20FA7D6046E5}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {EBD8B561-B354-4FDB-BF88-0BF0681F5530}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {EBD8B561-B354-4FDB-BF88-0BF0681F5530}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {EBD8B561-B354-4FDB-BF88-0BF0681F5530}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {EBD8B561-B354-4FDB-BF88-0BF0681F5530}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(NestedProjects) = preSolution 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /Bookings.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True -------------------------------------------------------------------------------- /Bookings/.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 -------------------------------------------------------------------------------- /Bookings/Application/BookingsCommandService.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain; 2 | using Bookings.Domain.Bookings; 3 | using Eventuous; 4 | using NodaTime; 5 | using static Bookings.Application.BookingCommands; 6 | 7 | namespace Bookings.Application; 8 | 9 | public class BookingsCommandService : CommandService { 10 | public BookingsCommandService(IAggregateStore store, Services.IsRoomAvailable isRoomAvailable) : base(store) { 11 | OnNewAsync( 12 | cmd => new BookingId(cmd.BookingId), 13 | (booking, cmd, _) => booking.BookRoom( 14 | cmd.GuestId, 15 | new RoomId(cmd.RoomId), 16 | new StayPeriod(LocalDate.FromDateTime(cmd.CheckInDate), LocalDate.FromDateTime(cmd.CheckOutDate)), 17 | new Money(cmd.BookingPrice, cmd.Currency), 18 | new Money(cmd.PrepaidAmount, cmd.Currency), 19 | DateTimeOffset.Now, 20 | isRoomAvailable 21 | ) 22 | ); 23 | 24 | OnExisting( 25 | cmd => new BookingId(cmd.BookingId), 26 | (booking, cmd) => booking.RecordPayment( 27 | new Money(cmd.PaidAmount, cmd.Currency), 28 | cmd.PaymentId, 29 | cmd.PaidBy, 30 | DateTimeOffset.Now 31 | ) 32 | ); 33 | } 34 | } -------------------------------------------------------------------------------- /Bookings/Application/Commands.cs: -------------------------------------------------------------------------------- 1 | namespace Bookings.Application; 2 | 3 | public static class BookingCommands { 4 | public record BookRoom( 5 | string BookingId, 6 | string GuestId, 7 | string RoomId, 8 | DateTime CheckInDate, 9 | DateTime CheckOutDate, 10 | float BookingPrice, 11 | float PrepaidAmount, 12 | string Currency, 13 | DateTimeOffset BookingDate 14 | ); 15 | 16 | public record RecordPayment(string BookingId, float PaidAmount, string Currency, string PaymentId, string PaidBy); 17 | } -------------------------------------------------------------------------------- /Bookings/Application/Queries/BookingDocument.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | // ReSharper disable UnusedAutoPropertyAccessor.Global 4 | 5 | namespace Bookings.Application.Queries; 6 | 7 | public record BookingDocument : ProjectedDocument { 8 | public BookingDocument(string id) : base(id) { } 9 | 10 | public string GuestId { get; init; } = null!; 11 | public string RoomId { get; init; } = null!; 12 | public LocalDate CheckInDate { get; init; } 13 | public LocalDate CheckOutDate { get; init; } 14 | public float BookingPrice { get; init; } 15 | public float PaidAmount { get; init; } 16 | public float Outstanding { get; init; } 17 | public bool Paid { get; init; } 18 | } -------------------------------------------------------------------------------- /Bookings/Application/Queries/BookingStateProjection.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB; 2 | using Eventuous.Subscriptions.Context; 3 | using MongoDB.Driver; 4 | using static Bookings.Domain.Bookings.BookingEvents; 5 | 6 | // ReSharper disable UnusedAutoPropertyAccessor.Global 7 | 8 | namespace Bookings.Application.Queries; 9 | 10 | public class BookingStateProjection : MongoProjection { 11 | public BookingStateProjection(IMongoDatabase database) : base(database) { 12 | On(stream => stream.GetId(), HandleRoomBooked); 13 | 14 | On( 15 | b => b 16 | .UpdateOne 17 | .DefaultId() 18 | .Update((evt, update) => 19 | update.Set(x => x.Outstanding, evt.Outstanding) 20 | ) 21 | ); 22 | 23 | On(b => b 24 | .UpdateOne 25 | .DefaultId() 26 | .Update((_, update) => update.Set(x => x.Paid, true)) 27 | ); 28 | } 29 | 30 | static UpdateDefinition HandleRoomBooked( 31 | IMessageConsumeContext ctx, UpdateDefinitionBuilder update 32 | ) { 33 | var evt = ctx.Message; 34 | 35 | return update.SetOnInsert(x => x.Id, ctx.Stream.GetId()) 36 | .Set(x => x.GuestId, evt.GuestId) 37 | .Set(x => x.RoomId, evt.RoomId) 38 | .Set(x => x.CheckInDate, evt.CheckInDate) 39 | .Set(x => x.CheckOutDate, evt.CheckOutDate) 40 | .Set(x => x.BookingPrice, evt.BookingPrice) 41 | .Set(x => x.Outstanding, evt.OutstandingAmount); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Bookings/Application/Queries/MyBookings.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB.Tools; 2 | using NodaTime; 3 | 4 | namespace Bookings.Application.Queries; 5 | 6 | public record MyBookings : ProjectedDocument { 7 | public MyBookings(string id) : base(id) { } 8 | 9 | public List Bookings { get; init; } = new(); 10 | 11 | public record Booking(string BookingId, LocalDate CheckInDate, LocalDate CheckOutDate, float Price); 12 | } -------------------------------------------------------------------------------- /Bookings/Application/Queries/MyBookingsProjection.cs: -------------------------------------------------------------------------------- 1 | using Eventuous.Projections.MongoDB; 2 | using MongoDB.Driver; 3 | using static Bookings.Domain.Bookings.BookingEvents; 4 | 5 | namespace Bookings.Application.Queries; 6 | 7 | public class MyBookingsProjection : MongoProjection { 8 | public MyBookingsProjection(IMongoDatabase database) : base(database) { 9 | On(b => b 10 | .UpdateOne 11 | .Id(ctx => ctx.Message.GuestId) 12 | .UpdateFromContext((ctx, update) => 13 | update.AddToSet( 14 | x => x.Bookings, 15 | new MyBookings.Booking(ctx.Stream.GetId(), 16 | ctx.Message.CheckInDate, 17 | ctx.Message.CheckOutDate, 18 | ctx.Message.BookingPrice 19 | ) 20 | ) 21 | ) 22 | ); 23 | 24 | On( 25 | b => b.UpdateOne 26 | .Filter((ctx, doc) => 27 | doc.Bookings.Select(booking => booking.BookingId).Contains(ctx.Stream.GetId()) 28 | ) 29 | .UpdateFromContext((ctx, update) => 30 | update.PullFilter( 31 | x => x.Bookings, 32 | x => x.BookingId == ctx.Stream.GetId() 33 | ) 34 | ) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Bookings/Bookings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Linux 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | true 31 | PreserveNewest 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Bookings/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base 2 | WORKDIR /app 3 | 4 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 5 | WORKDIR /src 6 | COPY . . 7 | RUN dotnet restore "Bookings/Bookings.csproj" 8 | WORKDIR "/src/Bookings" 9 | RUN dotnet build "Bookings.csproj" -c Release -o /app/build 10 | 11 | FROM build AS publish 12 | RUN dotnet publish "Bookings.csproj" -c Release -o /app/publish 13 | 14 | FROM base AS final 15 | WORKDIR /app 16 | COPY --from=publish /app/publish . 17 | ENTRYPOINT ["dotnet", "Bookings.dll"] 18 | -------------------------------------------------------------------------------- /Bookings/HttpApi/Bookings/CommandApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain.Bookings; 2 | using Eventuous; 3 | using Eventuous.AspNetCore.Web; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc; 6 | using static Bookings.Application.BookingCommands; 7 | 8 | namespace Bookings.HttpApi.Bookings; 9 | 10 | [Route("/booking")] 11 | public class CommandApi : CommandHttpApiBase { 12 | public CommandApi(ICommandService service) : base(service) { } 13 | 14 | [HttpPost] 15 | [Route("book")] 16 | public Task> BookRoom([FromBody] BookRoom cmd, CancellationToken cancellationToken) 17 | => Handle(cmd, cancellationToken); 18 | 19 | /// 20 | /// This endpoint is for demo purposes only. The normal flow to register booking payments is to submit 21 | /// a command via the Booking.Payments HTTP API. It then gets propagated to the Booking aggregate 22 | /// via the integration messaging flow. 23 | /// 24 | /// Command to register the payment 25 | /// Cancellation token 26 | /// 27 | [HttpPost] 28 | [Route("recordPayment")] 29 | public Task> RecordPayment( 30 | [FromBody] RecordPayment cmd, CancellationToken cancellationToken 31 | ) 32 | => Handle(cmd, cancellationToken); 33 | } 34 | -------------------------------------------------------------------------------- /Bookings/HttpApi/Bookings/QueryApi.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain.Bookings; 2 | using Eventuous; 3 | using Microsoft.AspNetCore.Mvc; 4 | 5 | namespace Bookings.HttpApi.Bookings; 6 | 7 | [Route("/bookings")] 8 | public class QueryApi : ControllerBase { 9 | readonly IAggregateStore _store; 10 | 11 | public QueryApi(IAggregateStore store) => _store = store; 12 | 13 | [HttpGet] 14 | [Route("{id}")] 15 | public async Task GetBooking(string id, CancellationToken cancellationToken) { 16 | var booking = await _store.Load(StreamName.For(id), cancellationToken); 17 | return booking.State; 18 | } 19 | } -------------------------------------------------------------------------------- /Bookings/Infrastructure/Mongo.cs: -------------------------------------------------------------------------------- 1 | using MongoDb.Bson.NodaTime; 2 | using MongoDB.Driver; 3 | using MongoDB.Driver.Core.Extensions.DiagnosticSources; 4 | 5 | namespace Bookings.Infrastructure; 6 | 7 | public static class Mongo { 8 | public static IMongoDatabase ConfigureMongo(IConfiguration configuration) { 9 | NodaTimeSerializers.Register(); 10 | var config = configuration.GetSection("Mongo").Get(); 11 | 12 | var settings = MongoClientSettings.FromConnectionString(config!.ConnectionString); 13 | 14 | if (config.User != null && config.Password != null) { 15 | settings.Credential = new MongoCredential( 16 | null, 17 | new MongoInternalIdentity("admin", config.User), 18 | new PasswordEvidence(config.Password) 19 | ); 20 | } 21 | 22 | settings.ClusterConfigurator = cb => cb.Subscribe(new DiagnosticsActivityEventSubscriber()); 23 | return new MongoClient(settings).GetDatabase(config.Database); 24 | } 25 | 26 | public record MongoSettings { 27 | public string ConnectionString { get; init; } = null!; 28 | public string Database { get; init; } = null!; 29 | public string? User { get; init; } 30 | public string? Password { get; init; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Bookings/Integration/Payments.cs: -------------------------------------------------------------------------------- 1 | using Bookings.Domain.Bookings; 2 | using Eventuous; 3 | using static Bookings.Application.BookingCommands; 4 | using static Bookings.Integration.IntegrationEvents; 5 | using EventHandler = Eventuous.Subscriptions.EventHandler; 6 | 7 | namespace Bookings.Integration; 8 | 9 | public class PaymentsIntegrationHandler : EventHandler { 10 | public static readonly StreamName Stream = new("PaymentsIntegration"); 11 | 12 | readonly ICommandService _applicationService; 13 | 14 | public PaymentsIntegrationHandler(ICommandService applicationService) { 15 | _applicationService = applicationService; 16 | On(async ctx => await HandlePayment(ctx.Message, ctx.CancellationToken)); 17 | } 18 | 19 | Task HandlePayment(BookingPaymentRecorded evt, CancellationToken cancellationToken) 20 | => _applicationService.Handle( 21 | new RecordPayment( 22 | evt.BookingId, 23 | evt.Amount, 24 | evt.Currency, 25 | evt.PaymentId, 26 | "" 27 | ), 28 | cancellationToken 29 | ); 30 | } 31 | 32 | static class IntegrationEvents { 33 | [EventType("BookingPaymentRecorded")] 34 | public record BookingPaymentRecorded(string PaymentId, string BookingId, float Amount, string Currency); 35 | } -------------------------------------------------------------------------------- /Bookings/Program.cs: -------------------------------------------------------------------------------- 1 | using Bookings; 2 | using Bookings.Domain.Bookings; 3 | using Eventuous; 4 | using Eventuous.AspNetCore; 5 | using Eventuous.Diagnostics.Logging; 6 | using Eventuous.Spyglass; 7 | using Microsoft.AspNetCore.Http.Json; 8 | using NodaTime; 9 | using NodaTime.Serialization.SystemTextJson; 10 | using Serilog; 11 | using Serilog.Events; 12 | 13 | TypeMap.RegisterKnownEventTypes(typeof(BookingEvents.V1.RoomBooked).Assembly); 14 | 15 | Log.Logger = new LoggerConfiguration() 16 | .MinimumLevel.Verbose() 17 | .MinimumLevel.Override("Microsoft", LogEventLevel.Information) 18 | .MinimumLevel.Override("Grpc", LogEventLevel.Information) 19 | .MinimumLevel.Override("Grpc.Net.Client.Internal.GrpcCall", LogEventLevel.Error) 20 | .MinimumLevel.Override("Microsoft.AspNetCore.Mvc.Infrastructure", LogEventLevel.Warning) 21 | .Enrich.FromLogContext() 22 | .WriteTo.Console() 23 | .WriteTo.Seq("http://localhost:5341") 24 | .CreateLogger(); 25 | 26 | var builder = WebApplication.CreateBuilder(args); 27 | builder.Host.UseSerilog(); 28 | 29 | builder.Services 30 | .AddControllers() 31 | .AddJsonOptions(cfg => cfg.JsonSerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)); 32 | builder.Services.AddEndpointsApiExplorer(); 33 | builder.Services.AddSwaggerGen(); 34 | builder.Services.AddTelemetry(); 35 | builder.Services.AddEventuous(builder.Configuration); 36 | builder.Services.AddEventuousSpyglass(); 37 | 38 | builder.Services.Configure(options 39 | => options.SerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb) 40 | ); 41 | 42 | var app = builder.Build(); 43 | 44 | app.UseSerilogRequestLogging(); 45 | app.UseSwagger().UseSwaggerUI(); 46 | app.MapControllers(); 47 | app.UseOpenTelemetryPrometheusScrapingEndpoint(); 48 | app.MapEventuousSpyglass(null); 49 | 50 | var factory = app.Services.GetRequiredService(); 51 | var listener = new LoggingEventListener(factory, "OpenTelemetry"); 52 | 53 | try { 54 | app.Run("http://*:5051"); 55 | return 0; 56 | } 57 | catch (Exception e) { 58 | Log.Fatal(e, "Host terminated unexpectedly"); 59 | return 1; 60 | } 61 | finally { 62 | Log.CloseAndFlush(); 63 | listener.Dispose(); 64 | } 65 | -------------------------------------------------------------------------------- /Bookings/Registrations.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Bookings.Application; 3 | using Bookings.Application.Queries; 4 | using Bookings.Domain; 5 | using Bookings.Domain.Bookings; 6 | using Bookings.Infrastructure; 7 | using Bookings.Integration; 8 | using Eventuous; 9 | using Eventuous.Diagnostics.OpenTelemetry; 10 | using Eventuous.EventStore; 11 | using Eventuous.EventStore.Subscriptions; 12 | using Eventuous.Projections.MongoDB; 13 | using Eventuous.Subscriptions.Registrations; 14 | using NodaTime; 15 | using NodaTime.Serialization.SystemTextJson; 16 | using OpenTelemetry.Metrics; 17 | using OpenTelemetry.Resources; 18 | using OpenTelemetry.Trace; 19 | 20 | namespace Bookings; 21 | 22 | public static class Registrations { 23 | public static void AddEventuous(this IServiceCollection services, IConfiguration configuration) { 24 | DefaultEventSerializer.SetDefaultSerializer( 25 | new DefaultEventSerializer( 26 | new JsonSerializerOptions(JsonSerializerDefaults.Web).ConfigureForNodaTime( 27 | DateTimeZoneProviders.Tzdb 28 | ) 29 | ) 30 | ); 31 | 32 | services.AddEventStoreClient(configuration["EventStore:ConnectionString"]!); 33 | services.AddAggregateStore(); 34 | services.AddCommandService(); 35 | 36 | services.AddSingleton((id, period) => new ValueTask(true)); 37 | 38 | services.AddSingleton((from, currency) 39 | => new Money(from.Amount * 2, currency) 40 | ); 41 | 42 | services.AddSingleton(Mongo.ConfigureMongo(configuration)); 43 | services.AddCheckpointStore(); 44 | 45 | services.AddSubscription( 46 | "BookingsProjections", 47 | builder => builder 48 | .UseCheckpointStore() 49 | .AddEventHandler() 50 | .AddEventHandler() 51 | .WithPartitioningByStream(2) 52 | ); 53 | 54 | services.AddSubscription( 55 | "PaymentIntegration", 56 | builder => builder 57 | .Configure(x => x.StreamName = PaymentsIntegrationHandler.Stream) 58 | .AddEventHandler() 59 | ); 60 | } 61 | 62 | public static void AddTelemetry(this IServiceCollection services) { 63 | var otelEnabled = Environment.GetEnvironmentVariable("OTEL_EXPORTER_OTLP_ENDPOINT") != null; 64 | 65 | services.AddOpenTelemetry() 66 | .WithMetrics( 67 | builder => { 68 | builder 69 | .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("bookings")) 70 | .AddAspNetCoreInstrumentation() 71 | .AddEventuous() 72 | .AddEventuousSubscriptions() 73 | .AddPrometheusExporter(); 74 | if (otelEnabled) builder.AddOtlpExporter(); 75 | } 76 | ); 77 | 78 | services.AddOpenTelemetry() 79 | .WithTracing( 80 | builder => { 81 | builder 82 | .AddAspNetCoreInstrumentation() 83 | .AddGrpcClientInstrumentation() 84 | .AddEventuousTracing() 85 | .AddMongoDBInstrumentation() 86 | .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("bookings")) 87 | .SetSampler(new AlwaysOnSampler()); 88 | 89 | if (otelEnabled) 90 | builder.AddOtlpExporter(); 91 | else 92 | builder.AddZipkinExporter(); 93 | } 94 | ); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /Bookings/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Mongo": { 3 | "ConnectionString": "mongodb://localhost:27017", 4 | "User": "mongoadmin", 5 | "Password": "secret", 6 | "Database": "Bookings" 7 | }, 8 | "EventStore": { 9 | "ConnectionString": "esdb://localhost:2113?tls=false" 10 | }, 11 | "AllowedHosts": "*" 12 | } 13 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | net7.0 4 | enable 5 | enable 6 | 0.14.1-alpha.0.7 7 | 8 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecation notice 2 | 3 | Samples are moved to the [core repository](https://github.com/Eventuous/eventuous/tree/dev/samples). 4 | 5 | # Eventuous sample with EventStoreDB 6 | 7 | Simple example of using Eventuous with [EventStoreDB](https://eventstore.com). 8 | 9 | -------------------------------------------------------------------------------- /deploy/cloudrun/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /deploy/cloudrun/Pulumi.yaml: -------------------------------------------------------------------------------- 1 | name: cloudrun 2 | runtime: nodejs 3 | description: Eventuous sample application deployment to Google Cloud Run 4 | -------------------------------------------------------------------------------- /deploy/cloudrun/index.ts: -------------------------------------------------------------------------------- 1 | import * as pulumi from "@pulumi/pulumi"; 2 | import * as gcp from "@pulumi/gcp"; 3 | import * as docker from "@pulumi/docker"; 4 | import {input as inputs} from "@pulumi/gcp/types"; 5 | 6 | const config = new pulumi.Config(); 7 | const connectorName = config.require("vpcConnectorName"); 8 | 9 | const imageName = "bookings"; 10 | const location = gcp.config.region || "europe-west2"; 11 | 12 | const myImage = new docker.Image(imageName, { 13 | imageName: pulumi.interpolate`gcr.io/${gcp.config.project}/${imageName}:latest`, 14 | build: { 15 | context: "../..", 16 | extraOptions: ["--platform=linux/amd64"], 17 | dockerfile: "../../Bookings/Dockerfile", 18 | }, 19 | }); 20 | 21 | let env: pulumi.Input[] = [ 22 | {name: "Mongo__ConnectionString", value: config.require("mongoConnectionString"),}, 23 | {name: "Mongo__User", value: config.require("mongoUser"),}, 24 | {name: "Mongo__Password", value: config.require("mongoPassword"),}, 25 | {name: "EventStore__ConnectionString", value: config.require("esdbConnectionString"),}, 26 | {name: "ASPNETCORE_ENVIRONMENT", value: "Development",}, 27 | {name: "ASPNETCORE_URLS", value: "http://*:5003",}, 28 | ]; 29 | 30 | const otelEndpoint = config.get("otelEndpoint"); 31 | if (otelEndpoint) { 32 | env.push({name: "OTEL_EXPORTER_OTLP_ENDPOINT", value: otelEndpoint,}); 33 | } 34 | const otelHeaders = config.get("otelHeaders"); 35 | if (otelHeaders) { 36 | env.push({name: "OTEL_EXPORTER_OTLP_HEADERS", value: otelHeaders,}); 37 | } 38 | 39 | const bookingsService = new gcp.cloudrun.Service("bookings", { 40 | location, 41 | template: { 42 | spec: { 43 | containers: [{ 44 | image: myImage.imageName, 45 | resources: { 46 | limits: { 47 | memory: "512M", 48 | cpu: "1000m" 49 | }, 50 | }, 51 | ports: [{ 52 | name: "http1", 53 | containerPort: 5003, 54 | }], 55 | envs: env, 56 | }], 57 | containerConcurrency: 50, 58 | }, 59 | metadata: { 60 | annotations: { 61 | "autoscaling.knative.dev/maxScale": "1", 62 | "run.googleapis.com/vpc-access-connector": connectorName, 63 | "run.googleapis.com/vpc-access-egress": "private-ranges-only" 64 | } 65 | } 66 | }, 67 | autogenerateRevisionName: true, 68 | }); 69 | 70 | const iam = new gcp.cloudrun.IamMember("iam-everyone", { 71 | service: bookingsService.name, 72 | location, 73 | role: "roles/run.invoker", 74 | member: "allUsers", 75 | }); 76 | 77 | // Export the URL 78 | export const url = bookingsService.statuses[0].url; 79 | -------------------------------------------------------------------------------- /deploy/cloudrun/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudrun", 3 | "devDependencies": { 4 | "@types/node": "^14" 5 | }, 6 | "dependencies": { 7 | "@pulumi/docker": "^3.2.0", 8 | "@pulumi/gcp": "^6.0.0", 9 | "@pulumi/pulumi": "^3.0.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /deploy/cloudrun/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "outDir": "bin", 5 | "target": "es2016", 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "experimentalDecorators": true, 10 | "pretty": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noImplicitReturns": true, 13 | "forceConsistentCasingInFileNames": true 14 | }, 15 | "files": [ 16 | "index.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /deploy/cloudrun/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@grpc/grpc-js@~1.3.8": 6 | version "1.3.8" 7 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.8.tgz#0d7dce9de7aeb20702a28f0704e61b0bf34061c1" 8 | integrity sha512-4qJqqn+CU/nBydz9ePJP+oa8dz0U42Ut/GejlbyaQ1xTkynCc+ndNHHnISlNeHawDsv4MOAyP3mV/EnDNUw2zA== 9 | dependencies: 10 | "@types/node" ">=12.12.47" 11 | 12 | "@logdna/tail-file@^2.0.6": 13 | version "2.2.0" 14 | resolved "https://registry.yarnpkg.com/@logdna/tail-file/-/tail-file-2.2.0.tgz#158a362d293f940dacfd07c835bf3ae2f9e0455a" 15 | integrity sha512-XGSsWDweP80Fks16lwkAUIr54ICyBs6PsI4mpfTLQaWgEJRtY9xEV+PeyDpJ+sJEGZxqINlpmAwe/6tS1pP8Ng== 16 | 17 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": 18 | version "1.1.2" 19 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" 20 | integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== 21 | 22 | "@protobufjs/base64@^1.1.2": 23 | version "1.1.2" 24 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" 25 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== 26 | 27 | "@protobufjs/codegen@^2.0.4": 28 | version "2.0.4" 29 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" 30 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== 31 | 32 | "@protobufjs/eventemitter@^1.1.0": 33 | version "1.1.0" 34 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" 35 | integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== 36 | 37 | "@protobufjs/fetch@^1.1.0": 38 | version "1.1.0" 39 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" 40 | integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== 41 | dependencies: 42 | "@protobufjs/aspromise" "^1.1.1" 43 | "@protobufjs/inquire" "^1.1.0" 44 | 45 | "@protobufjs/float@^1.0.2": 46 | version "1.0.2" 47 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" 48 | integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== 49 | 50 | "@protobufjs/inquire@^1.1.0": 51 | version "1.1.0" 52 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" 53 | integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== 54 | 55 | "@protobufjs/path@^1.1.2": 56 | version "1.1.2" 57 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" 58 | integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== 59 | 60 | "@protobufjs/pool@^1.1.0": 61 | version "1.1.0" 62 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" 63 | integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== 64 | 65 | "@protobufjs/utf8@^1.1.0": 66 | version "1.1.0" 67 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 68 | integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== 69 | 70 | "@pulumi/docker@^3.2.0": 71 | version "3.2.0" 72 | resolved "https://registry.yarnpkg.com/@pulumi/docker/-/docker-3.2.0.tgz#bbfeccb6dbf4a522e844f4de889c822cf0743ee1" 73 | integrity sha512-Mm8xz1vMhuxOOtyCU/V2Py7QW+M6zMxPBBtTjKzvWBYxLe8cygh12+EjCDFK6E9X+x3mV+ZrEkon+JY93kQWhw== 74 | dependencies: 75 | "@pulumi/pulumi" "^3.0.0" 76 | semver "^5.4.0" 77 | 78 | "@pulumi/gcp@^6.0.0": 79 | version "6.24.0" 80 | resolved "https://registry.yarnpkg.com/@pulumi/gcp/-/gcp-6.24.0.tgz#312634a69e410083cf23e2166792ba934ba18eab" 81 | integrity sha512-ZIAIe+O6n/a9EOlvlL5VdbxTMJ5JN7haTKOmYesnvoiB84N0a+iqij+DXPH3Vm1oL9NGOlFpuZk0mtPHtvVJfQ== 82 | dependencies: 83 | "@pulumi/pulumi" "^3.0.0" 84 | "@types/express" "^4.16.0" 85 | read-package-json "^2.0.13" 86 | 87 | "@pulumi/pulumi@^3.0.0": 88 | version "3.32.1" 89 | resolved "https://registry.yarnpkg.com/@pulumi/pulumi/-/pulumi-3.32.1.tgz#d91a9fc14524ce749a8195b67aab8e26658f7027" 90 | integrity sha512-R3pOp8BWEfQjZUdZXp4FRYjM4lCvDDwe4toe1uyqhtCoGSdtomHz0s6bHZ7SlLtjCgtWJklbi0p5V6WyiJ8IPg== 91 | dependencies: 92 | "@grpc/grpc-js" "~1.3.8" 93 | "@logdna/tail-file" "^2.0.6" 94 | "@pulumi/query" "^0.3.0" 95 | google-protobuf "^3.5.0" 96 | ini "^2.0.0" 97 | js-yaml "^3.14.0" 98 | minimist "^1.2.6" 99 | normalize-package-data "^2.4.0" 100 | protobufjs "^6.8.6" 101 | read-package-tree "^5.3.1" 102 | require-from-string "^2.0.1" 103 | semver "^6.1.0" 104 | source-map-support "^0.4.16" 105 | ts-node "^7.0.1" 106 | typescript "~3.7.3" 107 | upath "^1.1.0" 108 | 109 | "@pulumi/query@^0.3.0": 110 | version "0.3.0" 111 | resolved "https://registry.yarnpkg.com/@pulumi/query/-/query-0.3.0.tgz#f496608e86a18c3dd31b6c533408e2441c29071d" 112 | integrity sha512-xfo+yLRM2zVjVEA4p23IjQWzyWl1ZhWOGobsBqRpIarzLvwNH/RAGaoehdxlhx4X92302DrpdIFgTICMN4P38w== 113 | 114 | "@types/body-parser@*": 115 | version "1.19.2" 116 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" 117 | integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== 118 | dependencies: 119 | "@types/connect" "*" 120 | "@types/node" "*" 121 | 122 | "@types/connect@*": 123 | version "3.4.35" 124 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" 125 | integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== 126 | dependencies: 127 | "@types/node" "*" 128 | 129 | "@types/express-serve-static-core@^4.17.18": 130 | version "4.17.28" 131 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" 132 | integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== 133 | dependencies: 134 | "@types/node" "*" 135 | "@types/qs" "*" 136 | "@types/range-parser" "*" 137 | 138 | "@types/express@^4.16.0": 139 | version "4.17.13" 140 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" 141 | integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== 142 | dependencies: 143 | "@types/body-parser" "*" 144 | "@types/express-serve-static-core" "^4.17.18" 145 | "@types/qs" "*" 146 | "@types/serve-static" "*" 147 | 148 | "@types/long@^4.0.1": 149 | version "4.0.2" 150 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" 151 | integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== 152 | 153 | "@types/mime@^1": 154 | version "1.3.2" 155 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" 156 | integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== 157 | 158 | "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0": 159 | version "17.0.34" 160 | resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef" 161 | integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== 162 | 163 | "@types/node@^14": 164 | version "14.18.18" 165 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.18.tgz#5c9503030df484ccffcbb935ea9a9e1d6fad1a20" 166 | integrity sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig== 167 | 168 | "@types/qs@*": 169 | version "6.9.7" 170 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" 171 | integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== 172 | 173 | "@types/range-parser@*": 174 | version "1.2.4" 175 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" 176 | integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== 177 | 178 | "@types/serve-static@*": 179 | version "1.13.10" 180 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" 181 | integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== 182 | dependencies: 183 | "@types/mime" "^1" 184 | "@types/node" "*" 185 | 186 | argparse@^1.0.7: 187 | version "1.0.10" 188 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 189 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 190 | dependencies: 191 | sprintf-js "~1.0.2" 192 | 193 | arrify@^1.0.0: 194 | version "1.0.1" 195 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 196 | integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== 197 | 198 | asap@^2.0.0: 199 | version "2.0.6" 200 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 201 | integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== 202 | 203 | balanced-match@^1.0.0: 204 | version "1.0.2" 205 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 206 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 207 | 208 | brace-expansion@^1.1.7: 209 | version "1.1.11" 210 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 211 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 212 | dependencies: 213 | balanced-match "^1.0.0" 214 | concat-map "0.0.1" 215 | 216 | buffer-from@^1.0.0, buffer-from@^1.1.0: 217 | version "1.1.2" 218 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" 219 | integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== 220 | 221 | call-bind@^1.0.0, call-bind@^1.0.2: 222 | version "1.0.2" 223 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" 224 | integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== 225 | dependencies: 226 | function-bind "^1.1.1" 227 | get-intrinsic "^1.0.2" 228 | 229 | concat-map@0.0.1: 230 | version "0.0.1" 231 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 232 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 233 | 234 | debuglog@^1.0.1: 235 | version "1.0.1" 236 | resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" 237 | integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= 238 | 239 | define-properties@^1.1.3, define-properties@^1.1.4: 240 | version "1.1.4" 241 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" 242 | integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== 243 | dependencies: 244 | has-property-descriptors "^1.0.0" 245 | object-keys "^1.1.1" 246 | 247 | dezalgo@^1.0.0: 248 | version "1.0.4" 249 | resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" 250 | integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== 251 | dependencies: 252 | asap "^2.0.0" 253 | wrappy "1" 254 | 255 | diff@^3.1.0: 256 | version "3.5.0" 257 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 258 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 259 | 260 | es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5: 261 | version "1.20.1" 262 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" 263 | integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== 264 | dependencies: 265 | call-bind "^1.0.2" 266 | es-to-primitive "^1.2.1" 267 | function-bind "^1.1.1" 268 | function.prototype.name "^1.1.5" 269 | get-intrinsic "^1.1.1" 270 | get-symbol-description "^1.0.0" 271 | has "^1.0.3" 272 | has-property-descriptors "^1.0.0" 273 | has-symbols "^1.0.3" 274 | internal-slot "^1.0.3" 275 | is-callable "^1.2.4" 276 | is-negative-zero "^2.0.2" 277 | is-regex "^1.1.4" 278 | is-shared-array-buffer "^1.0.2" 279 | is-string "^1.0.7" 280 | is-weakref "^1.0.2" 281 | object-inspect "^1.12.0" 282 | object-keys "^1.1.1" 283 | object.assign "^4.1.2" 284 | regexp.prototype.flags "^1.4.3" 285 | string.prototype.trimend "^1.0.5" 286 | string.prototype.trimstart "^1.0.5" 287 | unbox-primitive "^1.0.2" 288 | 289 | es-to-primitive@^1.2.1: 290 | version "1.2.1" 291 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" 292 | integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== 293 | dependencies: 294 | is-callable "^1.1.4" 295 | is-date-object "^1.0.1" 296 | is-symbol "^1.0.2" 297 | 298 | esprima@^4.0.0: 299 | version "4.0.1" 300 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 301 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 302 | 303 | fs.realpath@^1.0.0: 304 | version "1.0.0" 305 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 306 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 307 | 308 | function-bind@^1.1.1: 309 | version "1.1.1" 310 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 311 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 312 | 313 | function.prototype.name@^1.1.5: 314 | version "1.1.5" 315 | resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" 316 | integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== 317 | dependencies: 318 | call-bind "^1.0.2" 319 | define-properties "^1.1.3" 320 | es-abstract "^1.19.0" 321 | functions-have-names "^1.2.2" 322 | 323 | functions-have-names@^1.2.2: 324 | version "1.2.3" 325 | resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" 326 | integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== 327 | 328 | get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: 329 | version "1.1.1" 330 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" 331 | integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== 332 | dependencies: 333 | function-bind "^1.1.1" 334 | has "^1.0.3" 335 | has-symbols "^1.0.1" 336 | 337 | get-symbol-description@^1.0.0: 338 | version "1.0.0" 339 | resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" 340 | integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== 341 | dependencies: 342 | call-bind "^1.0.2" 343 | get-intrinsic "^1.1.1" 344 | 345 | glob@^7.1.1: 346 | version "7.2.3" 347 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 348 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 349 | dependencies: 350 | fs.realpath "^1.0.0" 351 | inflight "^1.0.4" 352 | inherits "2" 353 | minimatch "^3.1.1" 354 | once "^1.3.0" 355 | path-is-absolute "^1.0.0" 356 | 357 | google-protobuf@^3.5.0: 358 | version "3.20.1" 359 | resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.20.1.tgz#1b255c2b59bcda7c399df46c65206aa3c7a0ce8b" 360 | integrity sha512-XMf1+O32FjYIV3CYu6Tuh5PNbfNEU5Xu22X+Xkdb/DUexFlCzhvv7d5Iirm4AOwn8lv4al1YvIhzGrg2j9Zfzw== 361 | 362 | graceful-fs@^4.1.2: 363 | version "4.2.10" 364 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" 365 | integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== 366 | 367 | has-bigints@^1.0.1, has-bigints@^1.0.2: 368 | version "1.0.2" 369 | resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" 370 | integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== 371 | 372 | has-property-descriptors@^1.0.0: 373 | version "1.0.0" 374 | resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" 375 | integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== 376 | dependencies: 377 | get-intrinsic "^1.1.1" 378 | 379 | has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: 380 | version "1.0.3" 381 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 382 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 383 | 384 | has-tostringtag@^1.0.0: 385 | version "1.0.0" 386 | resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" 387 | integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== 388 | dependencies: 389 | has-symbols "^1.0.2" 390 | 391 | has@^1.0.3: 392 | version "1.0.3" 393 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 394 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 395 | dependencies: 396 | function-bind "^1.1.1" 397 | 398 | hosted-git-info@^2.1.4: 399 | version "2.8.9" 400 | resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" 401 | integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== 402 | 403 | inflight@^1.0.4: 404 | version "1.0.6" 405 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 406 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 407 | dependencies: 408 | once "^1.3.0" 409 | wrappy "1" 410 | 411 | inherits@2: 412 | version "2.0.4" 413 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 414 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 415 | 416 | ini@^2.0.0: 417 | version "2.0.0" 418 | resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" 419 | integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== 420 | 421 | internal-slot@^1.0.3: 422 | version "1.0.3" 423 | resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" 424 | integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== 425 | dependencies: 426 | get-intrinsic "^1.1.0" 427 | has "^1.0.3" 428 | side-channel "^1.0.4" 429 | 430 | is-bigint@^1.0.1: 431 | version "1.0.4" 432 | resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" 433 | integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== 434 | dependencies: 435 | has-bigints "^1.0.1" 436 | 437 | is-boolean-object@^1.1.0: 438 | version "1.1.2" 439 | resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" 440 | integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== 441 | dependencies: 442 | call-bind "^1.0.2" 443 | has-tostringtag "^1.0.0" 444 | 445 | is-callable@^1.1.4, is-callable@^1.2.4: 446 | version "1.2.4" 447 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" 448 | integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== 449 | 450 | is-core-module@^2.8.1: 451 | version "2.9.0" 452 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" 453 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== 454 | dependencies: 455 | has "^1.0.3" 456 | 457 | is-date-object@^1.0.1: 458 | version "1.0.5" 459 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" 460 | integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== 461 | dependencies: 462 | has-tostringtag "^1.0.0" 463 | 464 | is-negative-zero@^2.0.2: 465 | version "2.0.2" 466 | resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" 467 | integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== 468 | 469 | is-number-object@^1.0.4: 470 | version "1.0.7" 471 | resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" 472 | integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== 473 | dependencies: 474 | has-tostringtag "^1.0.0" 475 | 476 | is-regex@^1.1.4: 477 | version "1.1.4" 478 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" 479 | integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== 480 | dependencies: 481 | call-bind "^1.0.2" 482 | has-tostringtag "^1.0.0" 483 | 484 | is-shared-array-buffer@^1.0.2: 485 | version "1.0.2" 486 | resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" 487 | integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== 488 | dependencies: 489 | call-bind "^1.0.2" 490 | 491 | is-string@^1.0.5, is-string@^1.0.7: 492 | version "1.0.7" 493 | resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" 494 | integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== 495 | dependencies: 496 | has-tostringtag "^1.0.0" 497 | 498 | is-symbol@^1.0.2, is-symbol@^1.0.3: 499 | version "1.0.4" 500 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" 501 | integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== 502 | dependencies: 503 | has-symbols "^1.0.2" 504 | 505 | is-weakref@^1.0.2: 506 | version "1.0.2" 507 | resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" 508 | integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== 509 | dependencies: 510 | call-bind "^1.0.2" 511 | 512 | js-yaml@^3.14.0: 513 | version "3.14.1" 514 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" 515 | integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== 516 | dependencies: 517 | argparse "^1.0.7" 518 | esprima "^4.0.0" 519 | 520 | json-parse-even-better-errors@^2.3.0: 521 | version "2.3.1" 522 | resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" 523 | integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== 524 | 525 | long@^4.0.0: 526 | version "4.0.0" 527 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" 528 | integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== 529 | 530 | make-error@^1.1.1: 531 | version "1.3.6" 532 | resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" 533 | integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== 534 | 535 | minimatch@^3.1.1: 536 | version "3.1.2" 537 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 538 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 539 | dependencies: 540 | brace-expansion "^1.1.7" 541 | 542 | minimist@^1.2.0, minimist@^1.2.6: 543 | version "1.2.6" 544 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 545 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 546 | 547 | mkdirp@^0.5.1: 548 | version "0.5.6" 549 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" 550 | integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== 551 | dependencies: 552 | minimist "^1.2.6" 553 | 554 | normalize-package-data@^2.0.0, normalize-package-data@^2.4.0: 555 | version "2.5.0" 556 | resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" 557 | integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== 558 | dependencies: 559 | hosted-git-info "^2.1.4" 560 | resolve "^1.10.0" 561 | semver "2 || 3 || 4 || 5" 562 | validate-npm-package-license "^3.0.1" 563 | 564 | npm-normalize-package-bin@^1.0.0: 565 | version "1.0.1" 566 | resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" 567 | integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== 568 | 569 | object-inspect@^1.12.0, object-inspect@^1.9.0: 570 | version "1.12.0" 571 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" 572 | integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== 573 | 574 | object-keys@^1.1.1: 575 | version "1.1.1" 576 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 577 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 578 | 579 | object.assign@^4.1.2: 580 | version "4.1.2" 581 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" 582 | integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== 583 | dependencies: 584 | call-bind "^1.0.0" 585 | define-properties "^1.1.3" 586 | has-symbols "^1.0.1" 587 | object-keys "^1.1.1" 588 | 589 | object.getownpropertydescriptors@^2.0.3: 590 | version "2.1.3" 591 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz#b223cf38e17fefb97a63c10c91df72ccb386df9e" 592 | integrity sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw== 593 | dependencies: 594 | call-bind "^1.0.2" 595 | define-properties "^1.1.3" 596 | es-abstract "^1.19.1" 597 | 598 | once@^1.3.0: 599 | version "1.4.0" 600 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 601 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 602 | dependencies: 603 | wrappy "1" 604 | 605 | path-is-absolute@^1.0.0: 606 | version "1.0.1" 607 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 608 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 609 | 610 | path-parse@^1.0.7: 611 | version "1.0.7" 612 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 613 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 614 | 615 | protobufjs@^6.8.6: 616 | version "6.11.2" 617 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" 618 | integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== 619 | dependencies: 620 | "@protobufjs/aspromise" "^1.1.2" 621 | "@protobufjs/base64" "^1.1.2" 622 | "@protobufjs/codegen" "^2.0.4" 623 | "@protobufjs/eventemitter" "^1.1.0" 624 | "@protobufjs/fetch" "^1.1.0" 625 | "@protobufjs/float" "^1.0.2" 626 | "@protobufjs/inquire" "^1.1.0" 627 | "@protobufjs/path" "^1.1.2" 628 | "@protobufjs/pool" "^1.1.0" 629 | "@protobufjs/utf8" "^1.1.0" 630 | "@types/long" "^4.0.1" 631 | "@types/node" ">=13.7.0" 632 | long "^4.0.0" 633 | 634 | read-package-json@^2.0.0, read-package-json@^2.0.13: 635 | version "2.1.2" 636 | resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.1.2.tgz#6992b2b66c7177259feb8eaac73c3acd28b9222a" 637 | integrity sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA== 638 | dependencies: 639 | glob "^7.1.1" 640 | json-parse-even-better-errors "^2.3.0" 641 | normalize-package-data "^2.0.0" 642 | npm-normalize-package-bin "^1.0.0" 643 | 644 | read-package-tree@^5.3.1: 645 | version "5.3.1" 646 | resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.3.1.tgz#a32cb64c7f31eb8a6f31ef06f9cedf74068fe636" 647 | integrity sha512-mLUDsD5JVtlZxjSlPPx1RETkNjjvQYuweKwNVt1Sn8kP5Jh44pvYuUHCp6xSVDZWbNxVxG5lyZJ921aJH61sTw== 648 | dependencies: 649 | read-package-json "^2.0.0" 650 | readdir-scoped-modules "^1.0.0" 651 | util-promisify "^2.1.0" 652 | 653 | readdir-scoped-modules@^1.0.0: 654 | version "1.1.0" 655 | resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz#8d45407b4f870a0dcaebc0e28670d18e74514309" 656 | integrity sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw== 657 | dependencies: 658 | debuglog "^1.0.1" 659 | dezalgo "^1.0.0" 660 | graceful-fs "^4.1.2" 661 | once "^1.3.0" 662 | 663 | regexp.prototype.flags@^1.4.3: 664 | version "1.4.3" 665 | resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" 666 | integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== 667 | dependencies: 668 | call-bind "^1.0.2" 669 | define-properties "^1.1.3" 670 | functions-have-names "^1.2.2" 671 | 672 | require-from-string@^2.0.1: 673 | version "2.0.2" 674 | resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" 675 | integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== 676 | 677 | resolve@^1.10.0: 678 | version "1.22.0" 679 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" 680 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== 681 | dependencies: 682 | is-core-module "^2.8.1" 683 | path-parse "^1.0.7" 684 | supports-preserve-symlinks-flag "^1.0.0" 685 | 686 | "semver@2 || 3 || 4 || 5", semver@^5.4.0: 687 | version "5.7.1" 688 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 689 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 690 | 691 | semver@^6.1.0: 692 | version "6.3.0" 693 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 694 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 695 | 696 | side-channel@^1.0.4: 697 | version "1.0.4" 698 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" 699 | integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== 700 | dependencies: 701 | call-bind "^1.0.0" 702 | get-intrinsic "^1.0.2" 703 | object-inspect "^1.9.0" 704 | 705 | source-map-support@^0.4.16: 706 | version "0.4.18" 707 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 708 | integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== 709 | dependencies: 710 | source-map "^0.5.6" 711 | 712 | source-map-support@^0.5.6: 713 | version "0.5.21" 714 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 715 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 716 | dependencies: 717 | buffer-from "^1.0.0" 718 | source-map "^0.6.0" 719 | 720 | source-map@^0.5.6: 721 | version "0.5.7" 722 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 723 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 724 | 725 | source-map@^0.6.0: 726 | version "0.6.1" 727 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 728 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 729 | 730 | spdx-correct@^3.0.0: 731 | version "3.1.1" 732 | resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" 733 | integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== 734 | dependencies: 735 | spdx-expression-parse "^3.0.0" 736 | spdx-license-ids "^3.0.0" 737 | 738 | spdx-exceptions@^2.1.0: 739 | version "2.3.0" 740 | resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" 741 | integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== 742 | 743 | spdx-expression-parse@^3.0.0: 744 | version "3.0.1" 745 | resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" 746 | integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== 747 | dependencies: 748 | spdx-exceptions "^2.1.0" 749 | spdx-license-ids "^3.0.0" 750 | 751 | spdx-license-ids@^3.0.0: 752 | version "3.0.11" 753 | resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" 754 | integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== 755 | 756 | sprintf-js@~1.0.2: 757 | version "1.0.3" 758 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 759 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 760 | 761 | string.prototype.trimend@^1.0.5: 762 | version "1.0.5" 763 | resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" 764 | integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== 765 | dependencies: 766 | call-bind "^1.0.2" 767 | define-properties "^1.1.4" 768 | es-abstract "^1.19.5" 769 | 770 | string.prototype.trimstart@^1.0.5: 771 | version "1.0.5" 772 | resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" 773 | integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== 774 | dependencies: 775 | call-bind "^1.0.2" 776 | define-properties "^1.1.4" 777 | es-abstract "^1.19.5" 778 | 779 | supports-preserve-symlinks-flag@^1.0.0: 780 | version "1.0.0" 781 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 782 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 783 | 784 | ts-node@^7.0.1: 785 | version "7.0.1" 786 | resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-7.0.1.tgz#9562dc2d1e6d248d24bc55f773e3f614337d9baf" 787 | integrity sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw== 788 | dependencies: 789 | arrify "^1.0.0" 790 | buffer-from "^1.1.0" 791 | diff "^3.1.0" 792 | make-error "^1.1.1" 793 | minimist "^1.2.0" 794 | mkdirp "^0.5.1" 795 | source-map-support "^0.5.6" 796 | yn "^2.0.0" 797 | 798 | typescript@~3.7.3: 799 | version "3.7.7" 800 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.7.tgz#c931733e2ec10dda56b855b379cc488a72a81199" 801 | integrity sha512-MmQdgo/XenfZPvVLtKZOq9jQQvzaUAUpcKW8Z43x9B2fOm4S5g//tPtMweZUIP+SoBqrVPEIm+dJeQ9dfO0QdA== 802 | 803 | unbox-primitive@^1.0.2: 804 | version "1.0.2" 805 | resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" 806 | integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== 807 | dependencies: 808 | call-bind "^1.0.2" 809 | has-bigints "^1.0.2" 810 | has-symbols "^1.0.3" 811 | which-boxed-primitive "^1.0.2" 812 | 813 | upath@^1.1.0: 814 | version "1.2.0" 815 | resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" 816 | integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== 817 | 818 | util-promisify@^2.1.0: 819 | version "2.1.0" 820 | resolved "https://registry.yarnpkg.com/util-promisify/-/util-promisify-2.1.0.tgz#3c2236476c4d32c5ff3c47002add7c13b9a82a53" 821 | integrity sha1-PCI2R2xNMsX/PEcAKt18E7moKlM= 822 | dependencies: 823 | object.getownpropertydescriptors "^2.0.3" 824 | 825 | validate-npm-package-license@^3.0.1: 826 | version "3.0.4" 827 | resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" 828 | integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== 829 | dependencies: 830 | spdx-correct "^3.0.0" 831 | spdx-expression-parse "^3.0.0" 832 | 833 | which-boxed-primitive@^1.0.2: 834 | version "1.0.2" 835 | resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" 836 | integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== 837 | dependencies: 838 | is-bigint "^1.0.1" 839 | is-boolean-object "^1.1.0" 840 | is-number-object "^1.0.4" 841 | is-string "^1.0.5" 842 | is-symbol "^1.0.3" 843 | 844 | wrappy@1: 845 | version "1.0.2" 846 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 847 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 848 | 849 | yn@^2.0.0: 850 | version "2.0.0" 851 | resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" 852 | integrity sha1-5a2ryKz0CPY4X8dklWhMiOavaJo= 853 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | 5 | esdb: 6 | container_name: esdemo-esdb 7 | image: ghcr.io/eventstore/eventstore:21.10.0-alpha-arm64v8 8 | # image: eventstore/eventstore:latest #20.10.2-buster-slim 9 | ports: 10 | - '2113:2113' 11 | - '1113:1113' 12 | environment: 13 | EVENTSTORE_INSECURE: 'true' 14 | EVENTSTORE_CLUSTER_SIZE: 1 15 | EVENTSTORE_EXT_TCP_PORT: 1113 16 | EVENTSTORE_HTTP_PORT: 2113 17 | EVENTSTORE_ENABLE_EXTERNAL_TCP: 'true' 18 | EVENTSTORE_RUN_PROJECTIONS: all 19 | EVENTSTORE_START_STANDARD_PROJECTIONS: "true" 20 | EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP: "true" 21 | 22 | mongo: 23 | container_name: esdemo-mongo 24 | image: mongo 25 | ports: 26 | - '27017:27017' 27 | environment: 28 | MONGO_INITDB_ROOT_USERNAME: mongoadmin 29 | MONGO_INITDB_ROOT_PASSWORD: secret 30 | 31 | zipkin: 32 | image: openzipkin/zipkin 33 | container_name: esdemo-zipkin 34 | ports: 35 | - "9411:9411" 36 | 37 | prometheus: 38 | container_name: esdemo-prometheus 39 | image: prom/prometheus:v2.17.1 40 | ports: 41 | - "9090:9090" 42 | volumes: 43 | - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 44 | 45 | grafana: 46 | container_name: esdemo-grafana 47 | image: grafana/grafana:6.7.2 48 | ports: 49 | - "3000:3000" 50 | volumes: 51 | - ./grafana/datasources.yml:/etc/grafana/provisioning/datasources/prometheus.yaml 52 | - ./grafana/dashboards:/dashboards 53 | 54 | seq: 55 | image: datalust/seq:latest 56 | container_name: esdemo-seq 57 | environment: 58 | - ACCEPT_EULA=Y 59 | ports: 60 | - "5341:80" 61 | 62 | networks: 63 | default: 64 | name: eventuous-demo 65 | -------------------------------------------------------------------------------- /grafana/__inputs.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "Default data source", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /grafana/datasources.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: prometheus 5 | type: prometheus 6 | access: proxy 7 | orgId: 1 8 | url: http://prometheus:9090 9 | isDefault: true 10 | version: 1 11 | editable: false -------------------------------------------------------------------------------- /prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 5s 3 | 4 | scrape_configs: 5 | - job_name: 'prometheus' 6 | static_configs: 7 | - targets: ['localhost:9090'] 8 | - job_name: 'bookings' 9 | static_configs: 10 | - targets: 11 | - 'host.docker.internal:5000' --------------------------------------------------------------------------------