{"id":13767237,"url":"https://github.com/jet/equinox","last_synced_at":"2026-02-04T14:09:52.413Z","repository":{"id":33585294,"uuid":"101387536","full_name":"jet/equinox","owner":"jet","description":".NET event sourcing library with CosmosDB, DynamoDB, EventStoreDB, message-db, SqlStreamStore and integration test backends. Focused at stream level; see https://github.com/jet/propulsion for cross-stream projections/subscriptions/reactions","archived":false,"fork":false,"pushed_at":"2025-02-07T20:28:04.000Z","size":4920,"stargazers_count":481,"open_issues_count":18,"forks_count":69,"subscribers_count":22,"default_branch":"master","last_synced_at":"2025-05-13T12:51:21.607Z","etag":null,"topics":["cosmosdb","csharp","dotnet","dotnet-core","dynamodb","event-sourcing","eventstore","eventstoredb","fsharp","memorystore","message-db","mysql","postgres","sql-server","sqlstreamstore","todo-backend"],"latest_commit_sha":null,"homepage":"https://github.com/jet/dotnet-templates","language":"F#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jet.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-08-25T09:15:13.000Z","updated_at":"2025-05-09T19:48:02.000Z","dependencies_parsed_at":"2023-02-16T09:15:52.291Z","dependency_job_id":"f1958dd9-4562-4e7c-a9cf-6312c6e66d16","html_url":"https://github.com/jet/equinox","commit_stats":{"total_commits":672,"total_committers":38,"mean_commits":17.68421052631579,"dds":0.2455357142857143,"last_synced_commit":"9a47f7ec8ad98da79de53bb27163fdd90c395f7e"},"previous_names":[],"tags_count":127,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jet%2Fequinox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jet%2Fequinox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jet%2Fequinox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jet%2Fequinox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jet","download_url":"https://codeload.github.com/jet/equinox/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254076850,"owners_count":22010611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cosmosdb","csharp","dotnet","dotnet-core","dynamodb","event-sourcing","eventstore","eventstoredb","fsharp","memorystore","message-db","mysql","postgres","sql-server","sqlstreamstore","todo-backend"],"created_at":"2024-08-03T16:01:06.504Z","updated_at":"2026-02-04T14:09:52.402Z","avatar_url":"https://github.com/jet.png","language":"F#","funding_links":[],"categories":["Clients by language","Libraries/Frameworks"],"sub_categories":[".NET"],"readme":"# Equinox [![Build Status](https://dev.azure.com/jet-opensource/opensource/_apis/build/status/jet.equinox?branchName=master)](https://dev.azure.com/jet-opensource/opensource/_build/latest?definitionId=4?branchName=master) [![release](https://img.shields.io/github/release/jet/equinox.svg)](https://github.com/jet/equinox/releases) [![NuGet](https://img.shields.io/nuget/vpre/equinox)](https://www.nuget.org/packages/Equinox/) [![license](https://img.shields.io/github/license/jet/Equinox.svg)](LICENSE) ![code size](https://img.shields.io/github/languages/code-size/jet/equinox.svg) [![docs status](https://img.shields.io/badge/DOCUMENTATION-WIP-important.svg?style=popout)](DOCUMENTATION.md) [![Discord](https://img.shields.io/discord/514783899440775168?color=blue\u0026label=Chat%20in%20equinox%20on%20DDD-CQRS-ES%20Discord)](https://discord.gg/sEZGSHNNbH)\n\nEquinox is a set of low dependency libraries that allow for event-sourced processing against stream-based stores handling:\n* Snapshots\n* Caching\n* [Optimistic concurrency control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control)\n\n**Not a framework**; *you* compose the libraries into an architecture that fits your apps' evolving needs. \n\nIt does not and will not handle projections and subscriptions. See [Propulsion](https://github.com/jet/propulsion) for that.\n\n# Table of Contents\n\n* [Getting Started](#getting-started)\n* [Design Motivation](#design-motivation)\n* [Features](#features)\n* [Currently Supported Data Stores](#currently-supported-data-stores)\n* [Components](#components)\n  * [Core library](#core-library)\n  * [Serialization Support](#serialization-support)\n  * [Data Store Libraries](#data-store-libraries)\n  * [Projection Libraries](#projection-libraries)\n  * [Tools](#tools)\n  * [Starter Project Templates and Sample Applications](#starter-project-templates-and-sample-applications)\n* [Overview](#overview)\n* [Templates](#templates)\n* [Samples](#samples)\n* [Building](#building)\n* [Releasing](#releasing)\n* [FAQ](#faq)\n* [Acknowledgements](#acknowledgements)\n* [Further Reading](#further-reading)\n\n# Getting Started\n\n- If you want to start with code samples that run in F# interactive, [there's a simple `Counter` example using `Equinox.MemoryStore`](https://github.com/jet/equinox/blob/master/samples/Tutorial/Counter.fsx#L20)\n- If you are experienced with event sourcing, CosmosDB and F#, you might gain most from this [100 LOC end-to-end example using CosmosDB](https://github.com/jet/equinox/blob/master/samples/Tutorial/Cosmos.fsx#L42) \n- If you are familiar with basic event sourcing mechanisms and want a meatier example of applying Equinox to a problem, [Einar Norðfjörð](https://github.com/nordfjord)'s article, [The Equinox Programming model](https://nordfjord.io/2022/12/05/equinox.html) walks through a [complete end-to-end sample](https://github.com/nordfjord/minimal-equinox) covering the key design considerations.\n- If you are experienced with CosmosDB and something like [CosmoStore](https://github.com/Dzoukr/CosmoStore), but want to understand what sort of facilities Equinox adds on top of raw event management, see the [Access Strategies guide](https://github.com/jet/equinox/blob/master/DOCUMENTATION.md#access-strategies)\n\n# Design Motivation\n\nEquinox's design is informed by discussions, talks and countless hours of hard and thoughtful work invested into many previous systems, [frameworks](https://github.com/NEventStore), [samples](https://github.com/thinkbeforecoding/FsUno.Prod), [forks of samples](https://github.com/bartelink/FunDomain), the outstanding continuous work of the [EventStore](https://github.com/eventstore) founders and team and the wider [DDD-CQRS-ES](https://groups.google.com/forum/#!forum/dddcqrs) community. It would be unfair to single out even a small number of people despite the immense credit that is due. Some aspects of the implementation are distilled from [`Jet.com` systems dating all the way back to 2013](http://gorodinski.com/blog/2013/02/17/domain-driven-design-with-fsharp-and-eventstore/).\n\nAn event sourcing system usually needs to address the following concerns:\n1. Storing events with good performance and debugging capabilities\n2. Transaction processing\n    - Optimistic concurrency (handle loading conflicting events and retrying if another transaction overlaps on the same stream)\n    - Folding events into a State, updating as new events are added\n3. Decoding events using codecs and formats\n4. Framework and application integration\n5. Projections and Reactions\n\nDesigning something that supports all of these as a single integrated solution results in an inflexible and difficult to use framework. \nThus, Equinox focuses on two central aspects of event sourcing: items 1 and 2 on the list above. \n\nOf course, the other concerns can't be ignored; thus, they are supported via other libraries that focus on them:\n- [FsCodec](https://github.com/jet/FsCodec) supports encoding and decoding (concern 3)  \n- [Propulsion](https://github.com/jet/propulsion) supports projections and reactions (concern 5)\n\nIntegration with other frameworks (e.g., Equinox wiring into ASP.NET Core) is something that is intentionally avoided; as you build your application, the nature of how you integrate things will naturally evolve.\n\nWe believe the fact Equinox is a library is critical:\n\n  - It gives you the ability to pick your preferred way of supporting your event sourcing system.\n  - There's less coupling to worry about as your application evolves over time.\n\n_If you're looking to learn more about and/or discuss Event Sourcing and it's myriad benefits, trade-offs and pitfalls as you apply it to your Domain, look no further than the thriving 4000+ member community on the [DDD-CQRS-ES Discord](https://github.com/ddd-cqrs-es/community); you'll get patient and impartial world class advice 24x7 (there are [#equinox](https://discord.com/channels/514783899440775168/1002635005429825657), [#eventstore](https://discord.com/channels/514783899440775168/762672037113757746) and [#sql-stream-store](https://discord.com/channels/514783899440775168/762671996550774804) channels for questions or feedback)._ ([invite link](https://discord.gg/sEZGSHNNbH))\n\n# Features\n\n- Designed not to invade application code; your domain tests can be written directly against your models.\n- Core ideas and features of the library are extracted from ideas and lessons learned from existing production software.\n- Test coverage for it's core features. In addition there are baseline and specific tests for each supported storage system and a comprehensive test and benchmarking story\n- Pluggable event serialization. All encoding is specified in terms of the [`FsCodec.IEventCodec` contract](https://github.com/jet/FsCodec#IEventCodec). [FsCodec](https://github.com/jet/FsCodec) provides for pluggable encoding of events based on:\n  - `NewtonsoftJson.Codec`: a [versionable convention-based approach](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores/) (using `Typeshape`'s `UnionContractEncoder` under the covers), providing for serializer-agnostic schema evolution with minimal boilerplate\n  - `SystemTextJson.Codec`: a replacement to support Microsoft's default serializer - [System.Text.Json](https://docs.microsoft.com/en-us/dotnet/api/system.text.json?view=netcore-3.1).  \n  - `Box.Codec`: lightweight [non-serializing substitute equivalent to `NewtonsoftJson.Codec` for use in unit and integration tests](https://github.com/jet/FsCodec#boxcodec)\n  - `Codec`: an explicitly coded pair of `encode` and `tryDecode` functions for when you need to customize\n- Caching using the .NET `MemoryCache` to:\n  - Minimize round trips; consistent implementation across stores :pray: [@DSilence](https://github.com/jet/equinox/pull/161)\n  - Minimize latency and bandwidth / Request Charges by maintaining the folded state, without needing the Domain Model folded state to be serializable\n  - Enable read through caching, coalescing concurrent reads via opt-in `LoadOption.AllowStale`\n- Mature and comprehensive logging (using [Serilog](https://github.com/serilog/serilog) internally), with optimal performance and pluggable integration with your apps hosting context (we ourselves typically feed log info to Splunk and the metrics embedded in the `Serilog.Events.LogEvent` Properties to Prometheus; see relevant tests for examples)\n- OpenTelemetry Integration (presently only implemented in `Equinox.Core` and `Equinox.MessageDb` ... `#help-wanted`)\n- **`Equinox.EventStore`, `Equinox.SqlStreamStore`: In-stream Rolling Snapshots**:\n  - No additional round trips to the store needed at either the Load or Sync points in the flow\n  - Support for multiple co-existing compaction schemas for a given stream (A 'compaction' event/snapshot is an Event). This is done by the [`FsCodec.IEventCodec`](https://github.com/jet/FsCodec#IEventCodec)\n    - Compaction events typically do not get deleted (consistent with how EventStoreDB works), although it is safe to do so in concept (there are no assumptions that the events must be contiguous and/or that the number of events implies a specific version etc)\n  - While snapshotting can deliver excellent performance especially when allied with the Cache, [it's not a panacea, as noted in this EventStore article on the topic](https://eventstore.org/docs/event-sourcing-basics/rolling-snapshots/index.html)\n- **`Equinox.MessageDb`: Adjacent Snapshots**:\n  - Maintains snapshot events in an adjacent, separated `{Category}:snapshot-{StreamId}` stream (in contrast to the EventStoreDb and SqlStreamStore `RollingState` strategy, which embeds the snapshots directly within the stream in question)\n  - Generating \u0026 storing the snapshot takes place subsequent to the normal appending of events, once every `batchSize` events. This means the state of the stream can be reconstructed with exactly 2 round-trips to the database (caching can of course remove the snapshot reads on subsequent calls)\n  - Note there's no logic in the system (or in message-db as a whole) to prune snapshots (although it's safe to remove them at any time, including for the 'current' one - a fresh one will get rewritten upon the next successful event append)\n- **`Equinox.CosmosStore` 'Tip with Unfolds' schema**: \n  - In contrast to `Equinox.EventStore`'s `AccessStrategy.RollingSnapshots`, when using `Equinox.CosmosStore`, optimized command processing is managed via the `Tip` - a document per stream with an identity enabling syncing the read/write position via a single [point-read](https://devblogs.microsoft.com/cosmosdb/point-reads-versus-queries). The `Tip` maintains the following: \n    - It records the current write position for the stream which is used for [optimistic concurrency control](https://en.wikipedia.org/wiki/Optimistic_concurrency_control) - i.e. the index at which the next events will be appended for a given stream (events and the Tip share a common logical partition key)\n    - It maintains the current [unfolds](DOCUMENTATION.md#Cosmos-Storage-Model) / snapshot data which is `deflate+base64` compressed.\n    - It can maintain events in a buffer when the tip accumulation limit is reached. The limit is up to a specified count or `JSON.stringify` length. When the limit is met, events are shifted to a immutable `Batch`. \n  - Has the benefits of the in-stream Rolling Snapshots approach while reducing latency and RU provisioning requirements due to meticulously tuned Request Charge costs:\n    - When the stream is empty, the initial `Load` operation involves a single point read that yields a `404 NotFound` response, costing 1.0 RU\n    - When coupled with the cache, a typical read is a point read [with `IfNoneMatch` on an etag], costing 1.0 RU if in-date [to get the `304 Not Modified` response] (when the stream is empty, a `404 NotFound` response, also costing 1.0 RU)\n    - Writes are a single invocation of the `Sync` stored procedure which:\n        - Does a point read\n        - Performs a concurrency check\n        - Uses that check to apply the write OR returns the conflicting events and unfolds\n    - No additional round trips to the store needed at either the `Load` or `Sync` points in the flow\n  - It should be noted that from a querying perspective, the `Tip` shares the same structure as `Batch` documents (a potential future extension would be to carry some events in the `Tip` as [some interim versions of the implementation once did](https://github.com/jet/equinox/pull/58), see also [#109](https://github.com/jet/equinox/pull/109).\n- **`Equinox.CosmosStore` `RollingState` and `Custom` 'non-event-sourced' modes**: \n    - Uses 'Tip with Unfolds' encoding to avoid having to write event documents at all. This option benefits from the caching and consistency management mechanisms because the cost of writing and storing infinitely increasing events are removed. Search for `transmute` or `RollingState` in the `samples` and/or see [the `Checkpoint` Aggregate in Propulsion](https://github.com/jet/propulsion/blob/master/src/Propulsion.EventStore/Checkpoint.fs). One chief use of this mechanism is for tracking Summary Event feeds in [the `dotnet-templates` `summaryConsumer` template](https://github.com/jet/dotnet-templates/tree/master/propulsion-summary-consumer).\n- **`Equinox.DynamoStore`**:\n    - Most features and behaviors are as per `Equinox.CosmosStore`, with the following key differences:\n      - Instead of using a Stored Procedure as `CosmosStore` does, the implementation involves:\n        - conditional `PutItem` and [`UpdateItem`](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) requests to accumulate events in the Tip (where there is space available).\n        - All event writes are guaranteed to first be inserted or appended to the Tip (to guarantee the DynamoDB Streams output is correctly ordered)\n          [see #401](https://github.com/jet/equinox/pull/401) and [Propulsion #222](https://github.com/jet/propulsion/pull/222) :pray: [@epNickColeman](https://github.com/epNickColeman)\n        - At the point where the Tip exceeds any of the configured and/or implicit limits, a [`TransactWriteItems`](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) request is used (see [implementation in `FSharp.AWS.DynamoDB`](https://github.com/fsprojects/FSharp.AWS.DynamoDB/pull/48)):\n          - maximum event count (not limited by default)\n          - maximum accumulated event size (default 32KiB)\n          - DynamoDB Item Size Limit (hard limit of 400KiB)\n      - DynamoDB does not support an etag-checked Read API, which means a cache hit is not as efficient as it is on CosmosDB (and the data hence travels and is deserialized unnecessarily)\n      - Concurrency conflicts necessitate an additional roundtrip to resync [as the DynamoDB Service does not yield the item in the event of a `ConditionalCheckFailedException`](https://stackoverflow.com/questions/71622525)\n        - NOTE: As of 30 June 2023, DDB now supports returning the conflicting events; TODO implement resync without an extra roundtrip via https://github.com/fsprojects/FSharp.AWS.DynamoDB/issues/68 \n      - `Equinox.Cosmos.Core.Events.appendAtEnd`/`NonIdempotentAppend` has not been ported (there's no obvious clean and efficient way to do a conditional insert/update/split as the CosmosDB stored proc can, and this is a low usage feature)\n      - The implementation uses [the excellent `FSharp.AWS.DynamoDB` library](https://github.com/fsprojects/FSharp.AWS.DynamoDB)) (which wraps the standard AWS `AWSSDK.DynamoDBv2` SDK Package), and leans on [significant preparatory research](https://github.com/pierregoudjo/dynamodb_conditional_writes) :pray: [@pierregoudjo](https://github.com/pierregoudjo)\n      - `CosmosStore` dictates (as of V4) that event bodies be supplied as `System.Text.Json.JsonElement`s (in order that events can be included in the Document/ Items as JSON directly. This is also to underscore the fact that the only reasonable format to use is valid JSON; binary data would need to be base64 encoded. `DynamoStore` accepts and yields event bodies as arbitrary `ReadOnlyMemory\u003cbyte\u003e` BLOBs (the AWS SDK round-trips such blobs as a `MemoryStream` and does not impose any restrictions on the blobs in terms of required format).\n      - `CosmosStore` defaults to compressing (with `System.IO.Compression.DeflateStream`) event bodies for Unfolds; `DynamoStore` round-trips an `encoding: int` value, which enables the `IEventCodec` to manage that concern. Regardless, minimizing Request Charges is imperative when request size directly maps to financial charges, 429s, reduced throughput and a lowered scaling ceiling.\n    - Azure CosmosDB's ChangeFeed API intrinsically supports replays of all the events in a Store, whereas the DynamoDB Streams facility only retains 24h of actions. As a result, there are ancillary components that provide equivalent functionality composed of:\n      - `Propulsion.DynamoStore.Lambda`: an AWS Lambda that is configured via a DynamoDB Streams Trigger to Index the Events (represented as Equinox Streams, typically in a separated `\u003ctableName\u003e-index` Table) as they are appended \n      - `Propulsion.DynamoStore.DynamoStoreSource`: consumes the Index Streams akin to how `Propulsion.CosmosStore.CosmosStoreSource` consumes the CosmosDB Change Feed \n\n# Currently Supported Data Stores\n\n- `MemoryStore`: In-memory store (volatile, for unit or integration test purposes). Fulfils the full contract Equinox imposes on a store, but without I/O costs [(it's ~100 LOC wrapping a `ConcurrentDictionary`)](https://github.com/jet/equinox/blob/master/src/Equinox.MemoryStore/MemoryStore.fs). Also enables [take serialization/deserialization out of the picture](https://github.com/jet/FsCodec#boxcodec) in tests. See also [`Propulsion.MemoryStore` Change Feed Simulator for integration testing of Reactors](https://github.com/jet/dotnet-templates#eqxshipping). \n- [Amazon Dynamo DB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html): Shares most features with `Equinox.CosmosStore` ([from which it was ported in #321](https://github.com/jet/equinox/pull/321)). See above for detailed comparison.\n- [Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db): contains some fragments of code dating back to 2016, however [the storage model](DOCUMENTATION.md#Cosmos-Storage-Model) was arrived at based on intensive benchmarking (squash-merged in [#42](https://github.com/jet/equinox/pull/42)). The V2, 3 and 4 release lines are being used in production systems. (The V3 release provides support for significantly more efficient packing of events ([storing events in the 'Tip'](https://github.com/jet/equinox/pull/251))). :pray: [@dongdongcai](https://github.com/dongdongcai)\n- [EventStoreDB](https://eventstore.org/): this codebase itself has been in production since 2017 (see commit history), with key elements dating back to approx 2016. Current versions require EventStoreDB Server editions `21.10` or later, and communicate over the modern gRPC interface.\n- [MessageDB](http://docs.eventide-project.org/user-guide/message-db): bindings for the `message-db` Postgres event storage system. [See `MessageDB` docs](http://docs.eventide-project.org/user-guide/message-db). :pray: [@nordfjord](https://github.com/nordfjord)\n- [SqlStreamStore](https://github.com/SQLStreamStore/SQLStreamStore): bindings for the powerful and widely used (**but presently unmaintained**) SQL-backed Event Storage system, derived from the EventStoreDB adapter. [See SqlStreamStore docs](https://sqlstreamstore.readthedocs.io/en/latest/#introduction). :pray: [@rajivhost](https://github.com/rajivhost)\n\n  __NOTE: The underlying `SqlStreamStore` project is presently unmaintained; as such its hard to recommend using it for production scenarios__\n  - For SQL Server, the implementation works, people are believed to be using it to varying degrees and there are no obviously better replacements. However it should be pointed out that something as simple as fixing a bug in a SqlStreamStore.SqlServer library is not a paved path - there is no CI system for it, and nobody to call\n\n  - For MySql, it's pretty much the same as for SQL Server, with the proviso that it's likely that it's had (and has) significantly lower adoption and/or proper scrutiny.\n  \n  - For Postgres, _`Equinox.MessageDb`_ is a better choice as the underlying [MessageDB](http://docs.eventide-project.org/user-guide/message-db/) project is actively maintained and has significantly better documentation than SqlStreamStore. The other thing to point out is that `Equinox.SqlStreamStore.Postgres` does not depend on the final version of `SqlStreamStore.Postgres` as there are no equivalent releases of the `.Mysql` and `.SqlServer` variants.\n\n# Components\n\nThe components within this repository are delivered as multi-targeted Nuget packages supporting `net6.0` (F# \u003e= 6) profiles; each of the constituent elements is designed to be easily swappable as dictated by the task at hand. Each of the components can be inlined or customized easily:-\n\n## Core library\n\n- `Equinox` [![NuGet](https://img.shields.io/nuget/v/Equinox.svg)](https://www.nuget.org/packages/Equinox/): Store-agnostic decision flow runner that manages the optimistic concurrency protocol and application-level API surface, together with the default [`System.Runtime.Caching.MemoryCache`-based] `Cache` implementation. ([depends](https://www.fuget.org/packages/Equinox) only on `FSharp.Core` v `6.0.7`, `FsCodec` v `3.0.0`, `System.Runtime.Caching`, `Serilog` (but not specific Serilog sinks, i.e. you configure to emit to `NLog` etc)\n\n## Serialization support\n\n- `FsCodec` [![Codec NuGet](https://img.shields.io/nuget/v/FsCodec.svg)](https://www.nuget.org/packages/FsCodec/): Defines minimal `IEventData`, `ITimelineEvent` and `IEventCodec` contracts, which are the sole aspects the Stores bind to. No dependencies.\n  - [`FsCodec.IEventCodec`](https://github.com/jet/FsCodec/blob/master/src/FsCodec/FsCodec.fs#L31): defines a base interface for a serializer/deserializer.\n  - `FsCodec.Codec`: enables plugging in a serializer and/or Union Encoder of your choice (typically this is used to supply a pair of functions:- `encode` and `tryDecode`)\n  ([depends](https://www.fuget.org/packages/FsCodec) on nothing \n- `FsCodec.NewtonsoftJson` [![Newtonsoft.Json Codec NuGet](https://img.shields.io/nuget/v/FsCodec.NewtonsoftJson.svg)](https://www.nuget.org/packages/FsCodec.NewtonsoftJson/)\n  - As described in [a scheme for the serializing Events modelled as an F# Discriminated Union](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores/), allows tagging of F# Discriminated Union cases in a versionable manner with low-dependencies using[TypeShape](https://github.com/eiriktsarpalis/TypeShape)'s [`UnionContractEncoder`](https://eiriktsarpalis.wordpress.com/2018/10/30/a-contract-pattern-for-schemaless-datastores)\n  - uses Json.net to serialize the event bodies.\n  - `FsCodec.Box.Codec`: Testing substitute for `FsCodec.NewtonsoftJson.Codec`, included in same package.\n  - ([depends](https://www.fuget.org/packages/FsCodec.NewtonsoftJson) on `FsCodec`, `Newtonsoft.Json \u003e= 11.0.2`, `TypeShape`, see [FsCodec repo](https://github.com/jet/FsCodec) for details)\n- `FsCodec.SystemTextJson` [![SystemTextJson Codec NuGet](https://img.shields.io/nuget/v/FsCodec.SystemTextJson.svg)](https://www.nuget.org/packages/FsCodec.SystemTextJson/): Drop in replacement that allows one to target the .NET `System.Text.Json` serializer solely by changing the referenced namespace.\n\n## Data Store libraries\n\n- `Equinox.Core` [![NuGet](https://img.shields.io/nuget/v/Equinox.Core.svg)](https://www.nuget.org/packages/Equinox.Core/): Hosts generic utility types frequently useful alongside Equinox: [`TaskCell`](https://github.com/jet/equinox/blob/master/src/Equinox.Core/TaskCell.fs#L36), [`Batcher`, `BatcherCache`, `BatcherDictionary`](https://github.com/jet/equinox/blob/master/src/Equinox.Core/Batching.fs#L44). ([depends](https://www.fuget.org/packages/Equinox.Core) on `System.Runtime.Caching`)\n- `Equinox.MemoryStore` [![MemoryStore NuGet](https://img.shields.io/nuget/v/Equinox.MemoryStore.svg)](https://www.nuget.org/packages/Equinox.MemoryStore/): In-memory store for integration testing/performance base-lining/providing out-of-the-box zero dependency storage for examples. ([depends](https://www.fuget.org/packages/Equinox.MemoryStore) on `Equinox`)\n- `Equinox.CosmosStore` [![CosmosStore NuGet](https://img.shields.io/nuget/v/Equinox.CosmosStore.svg)](https://www.nuget.org/packages/Equinox.CosmosStore/): Azure CosmosDB Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RU costs, instrumented to meet Jet's production monitoring requirements. ([depends](https://www.fuget.org/packages/Equinox.CosmosStore) on `Equinox`, `Equinox`, `Microsoft.Azure.Cosmos` \u003e= `3.43.1`, `System.Text.Json`, `FSharp.Control.TaskSeq`)\n- `Equinox.CosmosStore.Prometheus` [![CosmosStore.Prometheus NuGet](https://img.shields.io/nuget/v/Equinox.CosmosStore.Prometheus.svg)](https://www.nuget.org/packages/Equinox.CosmosStore.Prometheus/): Integration package providing a `Serilog.Core.ILogEventSink` that extracts detailed metrics information attached to the `LogEvent`s and feeds them to the `prometheus-net`'s `Prometheus.Metrics` static instance. ([depends](https://www.fuget.org/packages/Equinox.CosmosStore.Prometheus) on `Equinox.CosmosStore`, `prometheus-net \u003e= 3.6.0`)\n- `Equinox.DynamoStore` [![DynamoStore NuGet](https://img.shields.io/nuget/v/Equinox.DynamoStore.svg)](https://www.nuget.org/packages/Equinox.DynamoStore/): Amazon DynamoDB Adapter with integrated 'unfolds' feature, facilitating optimal read performance in terms of latency and RC costs, patterned after `Equinox.CosmosStore`. ([depends](https://www.fuget.org/packages/Equinox.DynamoStore) on `Equinox`, `FSharp.AWS.DynamoDB` \u003e= `0.12.3-beta`, `FSharp.Control.TaskSeq`)\n- `Equinox.DynamoStore.Prometheus` [![DynamoStore.Prometheus NuGet](https://img.shields.io/nuget/v/Equinox.DynamoStore.Prometheus.svg)](https://www.nuget.org/packages/Equinox.DynamoStore.Prometheus/): Integration package providing a `Serilog.Core.ILogEventSink` that extracts detailed metrics information attached to the `LogEvent`s and feeds them to the `prometheus-net`'s `Prometheus.Metrics` static instance. ([depends](https://www.fuget.org/packages/Equinox.CosmosStore.Prometheus) on `Equinox.DynamoStore`, `prometheus-net \u003e= 3.6.0`)\n- `Equinox.EventStore` [![EventStore NuGet](https://img.shields.io/nuget/v/Equinox.EventStore.svg)](https://www.nuget.org/packages/Equinox.EventStore/): [EventStoreDB](https://eventstore.org/) Adapter designed to meet Jet's production monitoring requirements. ([depends](https://www.fuget.org/packages/Equinox.EventStore) on `Equinox`, `EventStore.Client \u003e= 22.0.0`, `FSharp.Control.TaskSeq`), EventStore Server version `21.10` or later). **NO NOT use for new projects - the TCP interface to EventStoreDB has long been deprecated, this package is only provided to ease migration scenarios and will be removed in due course**\n- `Equinox.EventStoreDb` [![EventStoreDb NuGet](https://img.shields.io/nuget/v/Equinox.EventStoreDb.svg)](https://www.nuget.org/packages/Equinox.EventStoreDb/): Production-strength [EventStoreDB](https://eventstore.org/) Adapter. ([depends](https://www.fuget.org/packages/Equinox.EventStoreDb) on `Equinox`, `EventStore.Client.Grpc.Streams` \u003e= `22.0.0`, `FSharp.Control.TaskSeq`, EventStore Server version `21.10` or later)\n- `Equinox.MessageDb` [![MessageDb NuGet](https://img.shields.io/nuget/v/Equinox.MessageDb.svg)](https://www.nuget.org/packages/Equinox.MessageDb/): [MessageDb](http://docs.eventide-project.org/user-guide/message-db/) Adapter. ([depends](https://www.fuget.org/packages/Equinox.MessageDb) on `Equinox`, `Npgsql` \u003e= `7.0.7`, `FSharp.Control.TaskSeq`))\n- `Equinox.SqlStreamStore` [![SqlStreamStore NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore/): [SqlStreamStore](https://github.com/SQLStreamStore/SQLStreamStore) Adapter derived from `Equinox.EventStore` - provides core facilities (but does not connect to a specific database; see sibling `SqlStreamStore`.* packages). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore) on `Equinox`, `SqlStreamStore \u003e= 1.2.0-beta.8`, `FSharp.Control.TaskSeq`)\n- `Equinox.SqlStreamStore.MsSql` [![MsSql NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.MsSql.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore.MsSql/): [SqlStreamStore.MsSql](https://sqlstreamstore.readthedocs.io/en/latest/sqlserver) Sql Server `Connector` implementation for `Equinox.SqlStreamStore` package). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore.MsSql) on `Equinox.SqlStreamStore`, `SqlStreamStore.MsSql \u003e= 1.2.0-beta.8`)\n- `Equinox.SqlStreamStore.MySql` [![MySql NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.MySql.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore.MySql/): `SqlStreamStore.MySql` MySQL `Connector` implementation for `Equinox.SqlStreamStore` package). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore.MySql) on `Equinox.SqlStreamStore`, `SqlStreamStore.MySql \u003e= 1.2.0-beta.8`)\n- `Equinox.SqlStreamStore.Postgres` [![Postgres NuGet](https://img.shields.io/nuget/v/Equinox.SqlStreamStore.Postgres.svg)](https://www.nuget.org/packages/Equinox.SqlStreamStore.Postgres/): [SqlStreamStore.Postgres](https://sqlstreamstore.readthedocs.io/en/latest/postgres) PostgreSQL `Connector` implementation for `Equinox.SqlStreamStore` package). ([depends](https://www.fuget.org/packages/Equinox.SqlStreamStore.Postgres) on `Equinox.SqlStreamStore`, `SqlStreamStore.Postgres \u003e= 1.2.0-beta.8`)\n\n## Projection libraries\n\nEquinox does not focus on projection logic - each store brings its own strengths, needs, opportunities and idiosyncrasies. Here's a list of some relevant libraries from sibling projects that get used with regard to this:\n\n- `FsKafka` [![FsKafka NuGet](https://img.shields.io/nuget/v/FsKafka.svg)](https://www.nuget.org/packages/FsKafka/): Wraps `Confluent.Kafka` to provide efficient batched Kafka Producer and Consumer configurations, with basic logging instrumentation. Used in the [`propulsion sync kafka`](https://github.com/jet/propulsion#dotnet-tool-provisioning--projections-test-tool) tool command; see [`dotnet new proProjector -k; dotnet new proConsumer` to generate a sample app](https://github.com/jet/dotnet-templates#propulsion-related) using it (see the `BatchedAsync` and `BatchedSync` modules in `Examples.fs`).\n- `Propulsion` [![Propulsion NuGet](https://img.shields.io/nuget/v/Propulsion.svg)](https://www.nuget.org/packages/Propulsion/): A library that provides an easy way to implement projection logic. It defines `Propulsion.Streams.StreamEvent` used to interop with `Propulsion.*` in processing pipelines for the `proProjector` and `proSync` templates in the [templates repo](https://github.com/jet/dotnet-templates), together with the `Ingestion`, `Streams`, `Progress` and `Parallel` modules that get composed into those processing pipelines. ([depends](https://www.fuget.org/packages/Propulsion) on `Serilog`)\n- `Propulsion.Cosmos` [![Propulsion.Cosmos NuGet](https://img.shields.io/nuget/v/Propulsion.Cosmos.svg)](https://www.nuget.org/packages/Propulsion.Cosmos/): Wraps the [Microsoft .NET `ChangeFeedProcessor` library](https://github.com/Azure/azure-documentdb-changefeedprocessor-dotnet) providing a [processor loop](DOCUMENTATION.md#change-feed-processors) that maintains a continuous query loop per CosmosDB Physical Partition (Range) yielding new or updated documents (optionally unrolling events written by `Equinox.CosmosStore` for processing or forwarding). ([depends](https://www.fuget.org/packages/Propulsion.Cosmos) on `Equinox.Cosmos`, `Microsoft.Azure.DocumentDb.ChangeFeedProcessor \u003e= 2.2.5`)\n- `Propulsion.CosmosStore` [![Propulsion.CosmosStore NuGet](https://img.shields.io/nuget/v/Propulsion.CosmosStore.svg)](https://www.nuget.org/packages/Propulsion.CosmosStore/): Wraps the CosmosDB V3 SDK's Change Feed API, providing a [processor loop](DOCUMENTATION.md#change-feed-processors) that maintains a continuous query loop per CosmosDB Physical Partition (Range) yielding new or updated documents (optionally unrolling events written by `Equinox.CosmosStore` for processing or forwarding). Used in the [`propulsion sync stats from cosmos`](dotnet-tool-provisioning--benchmarking-tool) tool command; see [`dotnet new proProjector` to generate a sample app](#quickstart) using it. ([depends](https://www.fuget.org/packages/Propulsion.CosmosStore) on `Equinox.CosmosStore`)\n- `Propulsion.DynamoStore` [![Propulsion.DynamoStore NuGet](https://img.shields.io/nuget/v/Propulsion.DynamoStore.svg)](https://www.nuget.org/packages/Propulsion.DynamoStore/): Provides a `DynamoStoreSource` that provides equivalent functionality to `Propulsion.CosmosStore` in concert with `Propulsion.DynamoStore.Lambda`; ([depends](https://www.fuget.org/packages/Propulsion.DynamoStore) on `Equinox.DynamoStore`)\n- `Propulsion.DynamoStore.Lambda` [![Propulsion.DynamoStore.Lambda NuGet](https://img.shields.io/nuget/v/Propulsion.DynamoStore.Lambda.svg)](https://www.nuget.org/packages/Propulsion.DynamoStore.Lambda/): Indexes events written by `Equinox.DynamoStore` via a DynamoDB Streams-triggered Lambda. (Depends on `Propulsion.DynamoStore`)\n- `Propulsion.EventStore` [![Propulsion.EventStore NuGet](https://img.shields.io/nuget/v/Propulsion.EventStore.svg)](https://www.nuget.org/packages/Propulsion.EventStore/) Used in the [`propulsion sync stats from es`](dotnet-tool-provisioning--benchmarking-tool) tool command; see [`dotnet new proSync` to generate a sample app](#quickstart) using it. ([depends](https://www.fuget.org/packages/Propulsion.EventStore) on `Equinox.EventStore`)\n- `Propulsion.EventStoreDb` [![Propulsion.EventStoreDb NuGet](https://img.shields.io/nuget/v/Propulsion.EventStoreDb.svg)](https://www.nuget.org/packages/Propulsion.EventStoreDb/) Consumes from `EventStoreDB` v `21.10` or later using the gRPC interface. ([depends](https://www.fuget.org/packages/Propulsion.EventStoreDb) on `Equinox.EventStoreDb`)\n- `Propulsion.MessageDb` [![Propulsion.MessageDb NuGet](https://img.shields.io/nuget/v/Propulsion.MessageDb.svg)](https://www.nuget.org/packages/Propulsion.MessageDb) Consumes from a `MessageDB` store (in Postgres) using `Npgsql`. ([depends](https://www.fuget.org/packages/Propulsion.MessageDb) on `Npgsql`, `Propulsion.Feed`)\n- `Propulsion.Kafka` [![Propulsion.Kafka NuGet](https://img.shields.io/nuget/v/Propulsion.Kafka.svg)](https://www.nuget.org/packages/Propulsion.Kafka/): Provides a canonical `RenderedSpan` that can be used as a default format when projecting events via e.g. the Producer/Consumer pair in `dotnet new proProjector -k; dotnet new proConsumer`. ([depends](https://www.fuget.org/packages/Propulsion.Kafka) on `Newtonsoft.Json \u003e= 11.0.2`, `Propulsion`, `FsKafka`)\n\n## `dotnet tool` provisioning / benchmarking tool\n\n- `Equinox.Tool` [![Tool NuGet](https://img.shields.io/nuget/v/Equinox.Tool.svg)](https://www.nuget.org/packages/Equinox.Tool/)\n\n    - can render events from any of the stores via `eqx dump`.\n    - incorporates a benchmark scenario runner, running load tests composed of transactions in `samples/Store` and `samples/TodoBackend` against any supported store; this allows perf tuning and measurement in terms of both latency and transaction charge aspects. (Install via: `dotnet tool install Equinox.Tool -g`)\n    - can configure indices in Azure CosmosDB for an `Equinox.CosmosStore` Container via `eqx init`. See [here](#store-data-in-azure-cosmosdb).\n    - can search for streams and/or perform a JSON export based on name (`p`) or Uncompressed `u`nfold values for an `Equinox.CosmosStore` Container in Azure CosmosDB`. See [here](#eqx-query).\n    - can create tables in Amazon DynamoDB for `Equinox.DynamoStore` via `eqx initaws`.\n    - can initialize databases for `SqlStreamStore` via `eqx initsql`\n\n## Starter Project Templates and Sample Applications \n\n- `Equinox.Templates` [![Templates NuGet](https://img.shields.io/nuget/v/Equinox.Templates.svg)](https://www.nuget.org/packages/Equinox.Templates/): [The templates repo](https://github.com/jet/dotnet-templates) has C# and F# sample apps. (Install via `dotnet new -i Equinox.Templates \u0026\u0026 dotnet new eqx --list`). See [the quickstart](#quickstart) for examples of how to use it.\n- [`samples/Store` (in this repo)](/samples/Store): Example domain types reflecting examples of how one applies Equinox to a diverse set of stream-based models\n- [`samples/TodoBackend` (in this repo)](/samples/TodoBackend): Standard https://todobackend.com compliant backend\n- [`samples/Tutorial` (in this repo)](/samples/Tutorial): Annotated `.fsx` files with sample Aggregate impls\n\n# Overview\n\n## The Propulsion Perspective\n\nEquinox and Propulsion have a [Yin and yang](https://en.wikipedia.org/wiki/Yin_and_yang) relationship; the use cases for both naturally interlock and overlap. It can be relevant to peruse [the Propulsion Documentation's Overview Diagrams](https://github.com/jet/propulsion/blob/master/DOCUMENTATION.md#overview) for the complementary perspective (TL;DR its largely the same topology, with elements that are central here de-emphasized over there, and vice versa)\n\n## [C4](https://c4model.com) Context diagram\n\nEquinox focuses on the **Consistent Processing** element of building an event-sourced system, offering tailored components that interact with a specific **Consistent Event Store**, as laid out here in this [C4](https://c4model.com) System Context Diagram:\n\n![Equinox c4model.com Context Diagram](http://www.plantuml.com/plantuml/proxy?cache=no\u0026src=https://raw.github.com/jet/equinox/master/diagrams/context.puml\u0026fmt=svg)\n\n:point_up: Propulsion elements (which we consider External to Equinox) support the building of complementary facilities as part of an overall Application:\n\n- **Ingesters**: read stuff from outside the Bounded Context of the System. This kind of service covers aspects such as feeding reference data into **Read Models**, ingesting changes into a consistent model via **Consistent Processing**. _These services are not acting in reaction to events emanating from the **Consistent Event Store**, as opposed to..._\n- **Publishers**: react to events as they are arrive from the **Consistent Event Store** by filtering, rendering and producing to feeds for downstreams (here we label that _Publish Simple Notifications_). _While these services may in some cases rely on synchronous queries via **Consistent Processing**, it's never transacting or driving follow-on work; which brings us to..._\n- **Reactors**: drive reactive actions triggered by either upstream feeds, or events observed in the **Consistent Event Store**. _These services handle anything beyond the duties of **Ingesters** or **Publishers**, and will often drive follow-on processing via Process Managers and/or transacting via **Consistent Processing**. In some cases, a reactor app's function may be to progressively compose a notification for a **Publisher** to eventually publish._\n\n## [C4](https://c4model.com) Container diagram\n\nThe relevant pieces of the above break down as follows, when we emphasize the [Containers](https://c4model.com) aspects relevant to Equinox:\n\n![Equinox c4model.com Container Diagram](http://www.plantuml.com/plantuml/proxy?cache=no\u0026src=https://raw.github.com/jet/equinox/master/diagrams/container.puml\u0026fmt=svg)\n\n**[See Overview section in `DOCUMENTATION`.md for further drill down](https://github.com/jet/equinox/blob/master/DOCUMENTATION.md#overview)**\n\n## TEMPLATES\n\nThe best place to start, sample-wise is with the [QuickStart](#quickstart), which walks you through sample code, tuned for approachability, from `dotnet new` templates stored [in a dedicated repo](https://github.com/jet/dotnet-templates).\n\n## SAMPLES\n\nThe `samples/` folder contains various further examples (some of the templates are derived from these), with the complementary goals of:\n\n- being a starting point to see how one might consume the libraries.\n- acting as [Consumer Driven Contracts](https://martinfowler.com/articles/consumerDrivenContracts.html) to validate new and pin existing API designs.\n- providing outline (not official and complete) guidance as to things that are valid to do in an application consuming Equinox components.\n- to validate that each specific Storage implementation can fulfill the needs of each of the example Services/Aggregates/Applications. (_unfortunately this concern makes a lot of the DI wiring more complex than a real application should be; it's definitely a non-goal for every Equinox app to be able to switch between backends, even though that's very much possible to achieve._)\n- provide sample scripts referenced in the Tutorial\n\n\u003ca name=\"TodoBackend\"\u003e\u003c/a\u003e\n### [TODOBACKEND, see samples/TodoBackend](/samples/TodoBackend)\n\nThe repo contains a vanilla ASP.NET Core implementation of [the well-known TodoBackend Spec](https://www.todobackend.com). **NB the implementation is largely dictated by spec; no architectural guidance expressed or implied ;)**. It can be run via:\n\n    \u0026 dotnet run --project samples/Web -S es # run against eventstore, omit `es` to use in-memory store, or see PROVISIONING EVENTSTORE\n    start https://www.todobackend.com/specs/index.html?https://localhost:5001/todos # for low-level debugging / validation of hosting arrangements\n    start https://www.todobackend.com/client/index.html?https://localhost:5001/todos # standard JavaScript UI\n    start http://localhost:5341/#/events # see logs triggered by `-S` above in https://getseq.net        \n\n### [STORE, see /samples/Store](/samples/Store)\n\nThe core sample in this repo is the `Store` sample, which contains code and tests extracted from real implementations (with minor simplifications in some cases).\n\nThese facts mean that:\n\n- some of the code may be less than approachable for a beginner (e.g. some of the code is in its present form for reasons of efficiency)\n- some of the code may not represent official best practice guidance that the authors would necessarily stand over (e.g., the CQRS pattern is not strictly adhered to in all circumstances; some command designs are not completely correct from an idempotency perspective)\n\nWhile these things can of course be perfected through PRs, this is definitely not top of the work list for the purposes of this repo. (We'd be delighted to place links to other samples, including cleanups / rewrites of these samples written with different testing platforms, web platforms, or DDD/CQRS/ES design flavors right here).\n\n### [m-r](https://github.com/gregoryyoung/m-r/tree/master/SimpleCQRS) port, [see samples/Store/Domain/InventoryItem.fs](samples/Store/Domain/InventoryItem.fs)\n\nFor fun, there's a direct translation of the `InventoryItem` Aggregate and Command Handler from Greg Young's [`m-r`](https://github.com/gregoryyoung/m-r/tree/master/SimpleCQRS) demo project [as one could write it in F# using Equinox](https://github.com/jet/equinox/blob/master/samples/Store/Domain/InventoryItem.fs). NB any typical presentation of this example includes copious provisos and caveats about it being a toy example written almost a decade ago.\n\n### [`samples/Tutorial` (in this repo)](/samples/Tutorial): Annotated `.fsx` files with sample aggregate implementations\n\n### [@ameier38](https://github.com/ameier38)'s Tutorial\n\n[Andrew Meier](https://andrewcmeier.com) has written a very complete tutorial modeling a business domain using Equinox and EventStoreDB; includes Dockerized Suave API, test suite using Expecto, build automation using FAKE, and CI using Codefresh; see [the repo](https://github.com/ameier38/equinox-tutorial) and its [overview blog post](https://andrewcmeier.com/bi-temporal-event-sourcing).\n\n## QuickStart\n\n### Spin up a [TodoBackend](https://www.todobackend.com/) `.fsproj` app ([storing in `Equinox.MemoryStore` Simulator](https://github.com/jet/equinox#store-libraries))\n\n0. Make a scratch area\n\n    ```powershell\n    mkdir ExampleApp\n    cd ExampleApp \n    ```\n\n1. Use a `dotnet new` template to get fresh code in your repo\n\n    ```powershell\n    dotnet new -i Equinox.Templates # see source in https://github.com/jet/dotnet-templates\n    dotnet new eqxweb -t # -t for todos, defaults to memory store (-m) # use --help to see options regarding storage subsystem configuration etc\n    ```\n\n2. Run the `TodoBackend`:\n\n    ```powershell\n    dotnet run --project Web\n    ```\n\n4. Run the standard `TodoMvc` frontend against your locally-hosted, fresh backend (See generated `README.md` for more details)\n    - Todo JavaScript client App: https://www.todobackend.com/client/index.html?https://localhost:5001/todos \n    - Run individual JS specification tests: https://www.todobackend.com/specs/index.html?https://localhost:5001/todos\n\n### Spin up a [TodoBackend](https://www.todobackend.com/) `.csproj` ... with C# code\n\nWhile Equinox is implemented in F#, and F# is a great fit for writing event-sourced domain models, [the APIs are not F#-specific](https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/component-design-guidelines); there's a [C# edition of the template](https://github.com/jet/dotnet-templates/tree/master/equinox-web-csharp). The instructions are identical to the rest, but you need to use the `eqxwebcs` template instead of `eqxweb`.\n\n### Store data in [EventStore](https://eventstore.org)\n\n1. install EventStore locally (requires admin privilege)\n\n    - For Windows, install with Chocolatey:\n    \n      ```powershell\n      cinst eventstore-oss -y # where cinst is an invocation of the Chocolatey Package Installer on Windows\n      ```\n\n\t- For OSX, install with `brew cask install eventstore` \n\n2. start the local EventStore instance on any OS:\n\n    - Check out the github.com/jet/equinox repo\n    - `docker compose up`\n\n    For more complete instructions, follow https://developers.eventstore.com/server/v21.10/installation.html#use-docker-compose\n\n3. generate sample app with EventStore wiring from template and start\n\n    ```powershell\n    dotnet new eqxweb -t -e # -t for todos, -e for eventstore\n    dotnet run --project Web\n    ```\n\n4. browse writes at http://localhost:2113/web/index.html#/streams\n\n### Store data in [Azure CosmosDB](https://docs.microsoft.com/en-us/azure/cosmos-db/introduction)\n\n1. *export 3x env vars* (see [provisioning instructions](#run-cosmosdb-benchmark-when-provisioned))\n\n    ```powershell\n    $env:EQUINOX_COSMOS_CONNECTION=\"AccountEndpoint=https://....;AccountKey=....=;\"\n    $env:EQUINOX_COSMOS_DATABASE=\"equinox-test\"\n    $env:EQUINOX_COSMOS_CONTAINER=\"equinox-test\"\n    ```\n\n2. use the `eqx` tool to initialize the database and/or container (using preceding env vars)\n\n    ```powershell\n    dotnet tool uninstall Equinox.Tool -g\n    dotnet tool install Equinox.Tool -g --prerelease\n    eqx init -ru 400 cosmos # generates a database+container, adds optimized indexes\n    ```\n\n3. generate sample app from template, with CosmosDB wiring\n\n    ```powershell\n    dotnet new eqxweb -t -c # -t for todos, -c for cosmos\n    dotnet run --project Web\n    ```\n\n4. Use the `eqx` tool to dump stats relating the contents of the CosmosDB store\n\n    ```powershell\n    # run queries to determine how many streams, docs, events there are in the container\n    eqx -V stats -P cosmos # -P to run in parallel # -V to show underlying query being used\n    ```\n\n5. Use the `eqx` tool to query streams and/or snapshots in a CosmosDB store\n\n    \u003ca name=\"eqx-query\"\u003e\u003c/a\u003e\n    ```powershell\n    # Add indexing of the `u`nfolds borne by Tip Items: 1) `c` for the case name 2) `d` for fields of uncompressed unfolds \n    eqx init -m serverless --indexunfolds cosmos -d db -c $EQUINOX_COSMOS_VIEWS\n   \n    # query all streams LIKE \"$User-%\" with `Snapshotted2` unfolds. Batches of up to 100,000 events\n    eqx query -cn '$User' -un Snapshotted2 cosmos -d db -c $EQUINOX_COSMOS_VIEWS -b 100000\n    \n    # use a wild card (LIKE) for the stream name \n    eqx query -cl '$Us%' -un Snapshotted cosmos -d db -c $EQUINOX_COSMOS_VIEWS -b 100000\n    # \u003e Querying Default: SELECT c.p, c.u[0].D, c.u[0].d, c._etag FROM c WHERE c.p LIKE \"$Us%\" AND EXISTS (SELECT VALUE u FROM u IN c.u WHERE u.c = \"Snapshotted\") {}\n    # \u003e Page 7166s, 7166u, 0e 320.58RU 3.9s {}\n    # \u003e Page 1608s, 1608u, 0e 68.59RU 0.9s {}\n    # \u003e TOTALS 1c, 8774s, 389.17RU 4.7s {}   \n   \n    # Skip loading the _etag to simulate a query where you will only render the result (not `Transact` against it)\n    eqx query -cn '$User' -m readonly -un Snapshotted cosmos -d db -c $EQUINOX_COSMOS_VIEWS -b 100000\n    # \u003e Querying ReadOnly: SELECT c.u FROM c WHERE c.p LIKE \"$User-%\" AND EXISTS (SELECT VALUE u FROM u IN c.u WHERE u.c = \"Snapshotted\") {}\n    # \u003e Page 8774s, 8774u, 0e 342.33RU 3.8s {}\n    # \u003e TOTALS 0c, 8774s, 342.33RU 3.8s {} # 👈 cheaper and only one batch as no .p or ._etag \n   \n    # add criteria filtering based on an Uncompressed Unfold\n    eqx query -cn '$User' -un EmailIndex -uc 'u.d.email = \"a@b.com\"' cosmos -d db -c $EQUINOX_COSMOS_VIEWS -b 100000\n    # \u003e Querying Default: SELECT c.p, c.u[0].D, c.u[0].d, c._etag FROM c WHERE c.p LIKE \"$User-%\" AND EXISTS (SELECT VALUE u FROM u IN c.u WHERE u.c = \"EmailIndex\" AND u.d.email = \"a@b.com\") {}\n    # \u003e Page 0s, 0u, 0e 2.8RU 0.7s {}\n    # \u003e TOTALS 0c, 0s, 2.80RU 0.7s {} # 👈 only 2.8RU if nothing is returned\n   \n    # DUMP ONE STREAM TO A FILE (equivalent to queries performed by CosmosStore.AccessStrategy.Unoptimized)\n    # Can be imported into another store via `propulsion sync cosmos from json`\n    eqx query -sn 'user-f28fb6feea00550e93ca77b6f29899cd' -o dump-user.json cosmos -d db -c $EQUINOX_COSMOS_CONTAINER -b 9999\n    # \u003e Dumping Raw content to ./dump-user.json {}\n    # \u003e Querying Raw: SELECT * FROM c WHERE c.p = \"user-f28fb6feea00550e93ca77b6f29899cd\" AND 1=1 {}\n    # \u003e Page 9s, 1u, 10e 3.23RU 0.5s 0.0MiB age 0002.10:04:13 {} # 👈 2.80 if no results, adds per KiB charge if there are results \n    # \u003e TOTALS 1c, 9s, 3.23RU R/W 0.0/0.0MiB 3.9s {}\n \n    # DUMP FULL CONTENT OF THE CONTAINER TO A FILE\n    # Can be imported into another store via `propulsion sync cosmos from json`\n    eqx query -o ../dump-240216.json cosmos -d db -c $EQUINOX_COSMOS_CONTAINER -b 9999                             \n    # \u003e Dumping Raw content to ~/dumps/dump-240216.json {}\n    # \u003e No StreamName or CategoryName/CategoryLike specified - Unfold Criteria better be unambiguous {}\n    # \u003e Querying Raw: SELECT * FROM c WHERE 1=1 AND 1=1 {}\n    # \u003e Page 2972s, 748u, 3112e 108.9RU 3.8s 4.0MiB age 0212.18:00:45 {}\n    # \u003e Page 3211s, 777u, 3161e 112.29RU 3.3s 4.0MiB age 0212.09:06:02 {}\n    # \u003e Page 3003s, 663u, 3172e 110.33RU 3.4s 4.0MiB age 0211.04:09:12 {}\n    # \u003cchop\u003e\n    # \u003e Page 2768s, 498u, 3153e 107.46RU 3.0s 4.0MiB age 0016.13:09:02 {}\n    # \u003e Page 2806s, 505u, 3198e 107.17RU 3.0s 4.0MiB age 0010.18:52:45 {}\n    # \u003e Page 2903s, 601u, 3188e 107.53RU 3.1s 4.0MiB age 0004.05:24:51 {}\n    # \u003e Page 2638s, 316u, 3019e 93.09RU 2.5s 3.4MiB age 0000.05:08:38 {}\n    # \u003e TOTALS 11c, 206,356s, 7,886.75RU R/W 290.4/290.4MiB 225.3s {}\n   \n    # Prepare a breakdown of which categories are using the most capacity within the store\n    eqx -Q top cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # Page 3276\u003e3276i 3276s    0e 3991u 4.00\u003e4.00\u003c4.22MiB 103.74RU  3.5s D+M 5.1 C+C 0.00 201ms age 0000.00:33:13 {}\n    # Page 3177\u003e3177i 3177s    0e 4593u 4.00\u003e4.01\u003c4.20MiB 105.22RU  3.2s D+M 4.7 C+C 0.00 146ms age 0000.02:23:48 {}\n    # Page 2708\u003e2708i 2708s    0e 5044u 4.00\u003e4.00\u003c4.19MiB 105.76RU  3.4s D+M 4.5 C+C 0.00  84ms age 0002.23:10:55 {}\n    ...\n    # Page 4334\u003e4334i 4334s    0e 5038u 4.00\u003e4.00\u003c4.19MiB 112.59RU  2.9s D+M 4.2 C+C 0.00 109ms age 0000.00:00:59 {}\n    # Page 1637\u003e1637i 1637s    0e 2939u 2.40\u003e2.41\u003c2.52MiB  64.12RU  1.7s D+M 2.5 C+C 0.00  39ms age 0000.00:18:03 {}\n    # TOTALS 47,200i 9c 47,200s 0e 79,262u read 0.1GiB output 0.1GiB JSON 0.1GiB D+M(inflated) 0.1GiB C+C 0.00MiB Parse 1.516s Total 1,750.73RU 54.2s {}\n    #    24064i   40.75MiB E       0     0.0 U   48128    33.6 D+M   35.0 C+C   0.0 $Friend {}\n    #     6372i   13.18MiB E       0     0.0 U   12744    11.4 D+M   23.9 C+C   0.0 $Tenant {}\n    #     6374i    5.41MiB E       0     0.0 U    6374     3.6 D+M    5.4 C+C   0.0 $Role0 {}\n    #     5992i    5.09MiB E       0     0.0 U    5992     3.4 D+M    5.1 C+C   0.0 $Role {}\n    #     1574i    1.95MiB E       0     0.0 U    1574     1.5 D+M    2.0 C+C   0.0 $Permission {}\n    #     1575i    1.79MiB E       0     0.0 U    3150     1.3 D+M    1.2 C+C   0.0 $User {}\n    #      445i    0.51MiB E       0     0.0 U     483     0.4 D+M    0.8 C+C   0.0 $Invoice3 {}\n    #      410i    0.46MiB E       0     0.0 U     423     0.3 D+M    0.8 C+C   0.0 $Invoice2 {}\n    #      394i    0.44MiB E       0     0.0 U     394     0.3 D+M    0.7 C+C   0.0 $Invoice {}\n   \n    # Drill into the Friend data (different test data to preceding article)\n    eqx top -cn '$Friend' cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # Page 4787\u003e4787i 4787s    0e 4787u 4.00\u003e4.00\u003c4.19MiB 218.54RU  3.6s D+M 4.5 C+C 0.00 259ms age 0013.22:52:15 {}\n    # Page 4955\u003e4955i 4955s    0e 4955u 4.00\u003e4.00\u003c4.19MiB 200.20RU  3.2s D+M 4.1 C+C 0.00 202ms age 0013.22:52:18 {}\n    # Page 4715\u003e4715i 4715s    0e 4715u 4.00\u003e4.00\u003c4.21MiB 201.26RU  3.2s D+M 4.4 C+C 0.00 145ms age 0013.22:52:22 {}\n    # Page 4884\u003e4884i 4884s    0e 4884u 4.00\u003e4.00\u003c4.20MiB 198.97RU  3.2s D+M 4.1 C+C 0.00  95ms age 0013.22:52:31 {}\n    # Page 4620\u003e4620i 4620s    0e 4620u 4.00\u003e4.00\u003c4.20MiB 194.76RU  3.0s D+M 4.7 C+C 0.00 140ms age 0013.22:52:28 {}\n    # Page 4840\u003e4840i 4840s    0e 4840u 4.00\u003e4.00\u003c4.19MiB 198.43RU  3.2s D+M 4.2 C+C 0.00 136ms age 0013.22:52:34 {}\n    # Page 4791\u003e4791i 4791s    0e 4791u 4.00\u003e4.00\u003c4.21MiB 200.20RU  3.0s D+M 4.2 C+C 0.00 137ms age 0014.02:23:24 {}\n    # Page 3906\u003e3906i 3906s    0e 3906u 3.01\u003e3.02\u003c3.15MiB 158.28RU  2.6s D+M 2.9 C+C 0.00 142ms age 0013.23:13:51 {}\n    # TOTALS 37,498i 1c 37,498s 0e 37,498u read 0.0GiB output 0.0GiB JSON 0.0GiB D+M(inflated) 0.0GiB C+C 0.00MiB Parse 1.264s Total 1,570.64RU 30.0s {}\n    #    37498i   32.55MiB E       0     0.1 U   37498    21.7 D+M   33.2 C+C   0.0 $Friend {}\n\n    # DRY RUN of deleting (note no `-f` supplied)\n    eqx destroy -cn '$Friend' cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # W Dry-run of deleting items based on SELECT c.p, c.id, ARRAYLENGTH(c.e) AS es, ARRAYLENGTH(c.u) AS us FROM c WHERE c.p LIKE \"$Friend%\" {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.21\u003e0.76   415.07RRU   1.4s 0.00WRU/s   0.0s {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.48\u003e0.76   404.70RRU   0.8s 0.00WRU/s   0.0s {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.32\u003e0.76   395.36RRU   1.1s 0.00WRU/s   0.0s {}\n    # I Page  7501\u003e 7501i    7501s      0e   7501u    6.01\u003e0.57   299.60RRU   1.0s 0.00WRU/s   0.0s {}\n    # I TOTALS 37,498i 1c 37,498s 0e 37,498u read 31.0MiB output 2.9MiB 1,514.73RRU Avg 0.00WRU/s Delete 0.00WRU Total 7.8s {}\n   \n    # Whack them (note the `--force` supplied)\n    eqx destroy -cn '$Friend' --force cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # W DESTROYING all Items WHERE c.p LIKE \"$ResourceRole%\" {}\n    # I .. Deleted  6347i    6347s      0e   6347u 1,671.52WRU/s   30.0s {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.21\u003e0.76   415.17RRU   1.2s 1,678.54WRU/s  47.2s {}\n    # I .. Deleted  6363i    6363s      0e   6363u 1,703.29WRU/s   30.0s {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.48\u003e0.76   404.70RRU   1.1s 1,685.49WRU/s  47.8s {}\n    # I .. Deleted  6001i    6001s      0e   6001u 1,571.94WRU/s   30.0s {}\n    # I Page  9999\u003e 9999i    9999s      0e   9999u    8.32\u003e0.76   395.36RRU   1.0s 1,582.18WRU/s  50.1s {}\n    ^C           \n   \n    # Get impatient; up the concurrency (-w 192) from the default 32 (note the `--force` supplied)\n    eqx destroy -cn '$Friend' --force -w 192 cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # W DESTROYING all Items WHERE c.p LIKE \"$ResourceRole%\" {}\n    # I Page  3946\u003e 3946i    3946s      0e   3946u    3.05\u003e0.30   176.23RRU   0.8s 5,107.71WRU/s   6.1s {}\n    # I TOTALS 3,946i 1c 3,946s 0e 3,946u read 3.0MiB output 0.3MiB 176.23RRU Avg 3,058.48WRU/s Delete 31,360.10WRU Total 10.3s {}\n\n    # Analyze the largest streams in the '$Permission' category \n    eqx top -S -cl '$Perm%' cosmos -d db -c $EQUINOX_COSMOS_CONTAINER\n    # I Page  254\u003e 254i  254s    0e  254u 4.33\u003e4.33\u003c4.65MiB 349.76RU  3.9s D+M 8.2 C+C 0.00 105ms age 0013.23:34:02 {}\n    # I Page 1671\u003e1671i 1671s    0e 1671u 2.39\u003e2.40\u003c2.54MiB  91.57RU  2.1s D+M 2.9 C+C 0.00  99ms age 0013.23:34:07 {}\n    # I TOTALS 1,925i 1,925c 1,925s 0e 1,925u read 0.0GiB output 0.0GiB JSON 0.0GiB D+M(inflated) 0.0GiB C+C 0.00MiB Parse 0.207s Total 441.33RU 9.4s {}\n    # I     1925i    7.19MiB E       0     0.0 U    1925     6.6 D+M   11.1 C+C   0.0 $Permission {}\n    # I        1i    1.75MiB E       0     0.0 U       1     1.8 D+M    3.1 C+C   0.0 $Permission-5292b7cd524d509bb969bd82abf39461 {}\n    # I        1i    1.38MiB E       0     0.0 U       1     1.4 D+M    2.5 C+C   0.0 $Permission-244b72fb0238595494b5cb3f9bd1abf7 {}\n    # I        1i    0.79MiB E       0     0.0 U       1     0.8 D+M    1.5 C+C   0.0 $Permission-68a13e8398b352c5b8e22ec18ab2bbb6 {}\n    # I        1i    0.57MiB E       0     0.0 U       1     0.6 D+M    1.1 C+C   0.0 $Permission-ea4d1f46014a5bf6bbd97d3ec5723266 {}\n    # I        1i    0.13MiB E       0     0.0 U       1     0.1 D+M    0.2 C+C   0.0 $Permission-65b58d132ff857bb81b08a5bb69732d2 {}\n    # I        1i    0.02MiB E       0     0.0 U       1     0.0 D+M    0.0 C+C   0.0 $Permission-a7bcc3370ad15ae68041745ca55166cf {}\n    # I        1i    0.02MiB E       0     0.0 U       1     0.0 D+M    0.0 C+C   0.0 $Permission-03032ccf597857d9aa9c64b10288af8c {}\n    ```\n\n6. Use `propulsion sync` tool to run a CosmosDB ChangeFeedProcessor\n\n    ```powershell\n    dotnet tool uninstall Propulsion.Tool -g\n    dotnet tool install Propulsion.Tool -g --prerelease\n\n    propulsion init -ru 400 cosmos # generates a -aux container for the ChangeFeedProcessor to maintain consumer group progress within\n    # -V for verbose ChangeFeedProcessor logging\n    # `-g projector1` represents the consumer group - \u003e=1 are allowed, allowing multiple independent projections to run concurrently\n    # stats specifies one only wants stats regarding items (other options include `kafka` to project to Kafka)\n    # cosmos specifies source overrides (using defaults in step 1 in this instance)\n    propulsion -V sync -g projector1 stats from cosmos\n    ```\n\n7. Generate a CosmosDB ChangeFeedProcessor sample `.fsproj` (without Kafka producer/consumer), using `Propulsion.CosmosStore`\n\n    ```powershell\n    dotnet new -i Equinox.Templates\n\n    # note the absence of -k means the projector code will be a skeleton that does no processing besides counting the events\n    dotnet new proProjector\n\n    # start one or more Projectors\n    # `-g projector2` represents the consumer group; \u003e=1 are allowed, allowing multiple independent projections to run concurrently\n    # cosmos specifies source overrides (using defaults in step 1 in this instance)\n    dotnet run -- -g projector2 cosmos\n    ```\n8. Use `propulsion` tool to Run a CosmosDB ChangeFeedProcessor, emitting to a Kafka topic\n\n    ```powershell\t\n    $env:PROPULSION_KAFKA_BROKER=\"instance.kafka.mysite.com:9092\" # or use -b\t\n    # `-V` for verbose logging\t\n    # `projector3` represents the consumer group; \u003e=1 are allowed, allowing multiple independent projections to run concurrently\t\n    # `-l 5` to report ChangeFeed lags every 5 minutes\t\n    # `kafka` specifies one wants to emit to Kafka\t\n    # `temp-topic` is the topic to emit to\t\n    # `cosmos` specifies source overrides (using defaults in step 1 in this instance)\t\n    propulsion -V sync -g projector3 -l 5 kafka temp-topic from cosmos\t\n    ```\t\n\n9. Generate CosmosDB [Kafka Projector and Consumer](https://github.com/jet/propulsion#feeding-to-kafka) `.fsproj`ects (using `Propulsion.Kafka`)\n\n    ```powershell\n    cat readme.md # more complete instructions regarding the code\n\n    # -k requests inclusion of Apache Kafka support\n    md projector | cd\n    dotnet new proProjector -k\n\n    # start one or more Projectors (see above for more examples/info re the Projector.fsproj)\n\n    $env:PROPULSION_KAFKA_BROKER=\"instance.kafka.mysite.com:9092\" # or use -b\n    $env:PROPULSION_KAFKA_TOPIC=\"topic0\" # or use -t\n    dotnet run -- -g projector4 -t topic0 cosmos\n\n    # generate a consumer app\n    md consumer | cd\n    dotnet new proConsumer\n\n    # start one or more Consumers\n    $env:PROPULSION_KAFKA_GROUP=\"consumer1\" # or use -g\n    dotnet run -- -t topic0 -g consumer1\n    ```\n\n10. Generate an Archive container; Generate a ChangeFeedProcessor App to mirror desired streams from the Primary to it\n\n    ```powershell\n    # once\n    eqx init -ru 400 cosmos -c equinox-test-archive\n   \n    md archiver | cd\n   \n    # Generate a template app that'll sync from the Primary (i.e. equinox-test)\n    # to the Archive (i.e. equinox-test-archive)\n    dotnet new proArchiver\n   \n    # TODO edit Handler.fs to add criteria for what to Archive\n    # - Normally you won't want to Archive stuff like e.g. `Sync-` checkppoint streams\n    # - Any other ephemeral application streams can be excluded too\n   \n    # -w 4 # constrain parallel writers in order to leave headroom for readers; Archive container should be cheaper to run\n    # -S -t 40 # emit log messages for Sync calls costing \u003e 40 RU\n    # -md 20 (or lower) is recommended to be nice to the writers - the archiver can afford to lag\n    dotnet run -c Release -- -w 4 -S -t 40 -g ArchiverConsumer `\n      cosmos -md 20 -c equinox-test -a equinox-test-aux `\n      cosmos -c equinox-test-archive \n    ``` \n\n11. Use a ChangeFeedProcessor driven from the Archive Container to Prune the Primary\n\n    ```powershell\n    md pruner | cd\n   \n    # Generate a template app that'll read from the Archive (i.e. equinox-test-archive)\n    # and prune expired events from the Primary (i.e. equinox-test)\n    dotnet new proPruner\n   \n    # TODO edit Handler.fs to add criteria for what to Prune\n    # - While its possible to prune the minute it's archived, normally you'll want to allow a time lag before doing so\n    \n    # -w 2 # constrain parallel pruners in order to not consume RUs excessively on Primary\n    # -md 10 (or lower) is recommended to contrain consumption on the Archive - Pruners lagging is rarely critical\n    dotnet run -c Release -- -w 2 -g PrunerConsumer `\n      cosmos -md 10 -c equinox-test-archive -a equinox-test-aux `\n      cosmos -c equinox-test\n    ``` \n\n\u003ca name=\"sqlstreamstore\"\u003e\u003c/a\u003e\n### Use [SqlStreamStore](https://github.com/SQLStreamStore/SQLStreamStore)\n\nSqlStreamStore is provided in the samples and the `eqx` tool:\n\n- being able to supply `ms`, `my`, `pg` flag to `eqx loadtest`, e.g. `eqx loadtest -t cart -f 50 -d 5 -C -U ms -c \"sqlserverconnectionstring\" -s schema`\n- being able to supply `ms`, `my`, `pg` flag to `eqx dump`, e.g. `eqx dump -CU \"Favorites-ab25cc9f24464d39939000aeb37ea11a\" ms -c \"sqlserverconnectionstring\" -s schema`\n- being able to supply `ms`, `my`, `pg` flag to Web sample, e.g. `dotnet run --project samples/Web/ -- my -c \"mysqlconnectionstring\"`\n- being able to supply `ms`, `my`, `pg` flag to new `eqx initsql` command e.g. `eqx initsql pg -c \"postgresconnectionstring\" -u p \"usercredentialsNotToBeLogged\" -s schema`\n\n```powershell\ncd ~/code/equinox\n\n# set up the DB/schema\ndotnet run --project tools/Equinox.Tool -- initsql pg -c \"connectionstring\" -p \"u=un;p=password\" -s \"schema\"\n\n# run a benchmark\ndotnet run -c Release --project tools/Equinox.Tool -- loadtest -t saveforlater -f 50 -d 5 -C -U pg -c \"connectionstring\" -p \"u=un;p=password\" -s \"schema\"\n\n# run the webserver, -A to autocreate schema on connection\ndotnet run --project samples/Web/ -- my -c \"mysqlconnectionstring\" -A\n\n# set up the DB/schema\neqx initsql pg -c \"connectionstring\" -p \"u=un;p=password\" -s \"schema\"\n\n# run a benchmark\neqx loadtest -t saveforlater -f 50 -d 5 -C -U pg -c \"connectionstring\" -p \"u=un;p=password\" -s \"schema\" \neqx dump \"SavedForLater-ab25cc9f24464d39939000aeb37ea11a\" pg -c \"connectionstring\" -p \"u=un;p=password\" -s \"schema\" # show stored JSON (Guid shown in eqx loadtest output) \n```\n\n\u003ca name=\"message-db\"\u003e\u003c/a\u003e\n### Use [MessageDB](http://docs.eventide-project.org/user-guide/message-db/)\n\nMessageDb support is provided in the samples and the `eqx` tool:\n\n- being able to supply `mdb` flag to `eqx loadtest`, e.g. `eqx loadtest -f 50 -d 5 -C -U mdb -c \"pgconnectionstring\"`\n- being able to supply `mdb` flag to `eqx dump`, e.g. `eqx dump -CU \"Favorites-ab25cc9f24464d39939000aeb37ea11a\" mdb -c \"pgconnectionstring\"`\n- being able to supply `mdb` flag to Web sample, e.g. `dotnet run --project samples/Web/ -- mdb -c \"pgconnectionstring\"`\n\nEquinox does not provide utilities for configuring or installing MessageDB. See [MessageDB's installation documentation](http://docs.eventide-project.org/user-guide/message-db/install.html).\n\nIn addition to the default access strategy of reading the whole stream forwards in batches, the following access strategies are supported in MessageDb:\n\n`AccesStrategy.LatestKnownEvent`\n- Uses message-db's `get_last_stream_message` API to only ever fetch the last event in a stream\n- This is useful for aggregates whose entire state can be constructed from the latest event (e.g. a stream that stores checkpoints)\n- NOTE: The last event should be decodable by the supplied codec. Otherwise you'll receive the initial state.\n\n`AccessStrategy.AdjacentSnapshots`\n- Generates and stores a snapshot event in an adjacent `{Category}:snapshot-{StreamId}` stream\n- The generation happens every `batchSize` events. This means the state of the stream can be reconstructed with exactly 2 round-trips to the database.\n  - The first round-trip fetches the most recent event of type `snapshotEventCaseName` from the snapshot stream.\n  - The second round-trip fetches `batchSize` events from the position of the snapshot\n\n\u003ca name=\"dynamodb\"\u003e\u003c/a\u003e\n### Use [Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html)\n\nDynamoDB is supported in the samples and the `eqx` tool equivalent to the CosmosDB support as described:\n- being able to supply `dynamo` source to `eqx loadtest` wherever `cosmos` works, e.g. `eqx loadtest -t cart -f 50 -d 5 -CU dynamo -s http://localhost:8000 -t TableName`\n- being able to supply `dynamo` flag to `eqx dump`, e.g. `eqx dump -CU \"Favorites-ab25cc9f24464d39939000aeb37ea11a\" dynamo`\n- being able to supply `dynamo` flag to Web sample, e.g. `dotnet run --project samples/Web/ -- dynamo -s http://localhost:8000`\n- being able to supply `dynamo` flag to `eqx initaws` command e.g. `eqx initaws -r 10 -w 10 -s new dynamo -t TableName`\n\n1. The tooling and samples in this repo default to using the following environment variables (see [AWS CLI UserGuide](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html)\n   for more detailed guidance as to specific configuration)\n\n    ```zsh\n    $env:EQUINOX_DYNAMO_SERVICE_URL=\"https://dynamodb.us-west-2.amazonaws.com\" # Simulator: \"http://localhost:8000\"\n    $env:EQUINOX_DYNAMO_ACCESS_KEY_ID=\"AKIAIOSFODNN7EXAMPLE\"\n    $env:EQUINOX_DYNAMO_SECRET_ACCESS_KEY=\"AwJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\"\n    $env:EQUINOX_DYNAMO_TABLE=\"equinox-test\"\n    $env:EQUINOX_DYNAMO_TABLE_ARCHIVE=\"equinox-test-archive\"\n    ```\n\n2. Tour of the tools/samples:\n\n    ```zsh\n    cd ~/code/equinox\n\n    # start the simulator at http://localhost:8000 and an admin console at http://localhost:8001/\n    docker compose up dynamodb-local dynamodb-admin -d\n\n    # Establish the table in us-east-1 - keys come from $EQUINOX_DYNAMO_ACCESS_KEY_ID and $EQUINOX_DYNAMO_SECRET_ACCESS_KEY\n    dotnet run --project tools/Equinox.Tool -- initaws -r 10 -w 10 -s new dynamo -t TableName -su https://dynamodb.us-east-1.amazonaws.com\n\n    # Check the status and get the streams ARN - keys come from AWS SDK config for us-east-1 region\n    dotnet run --project tools/Equinox.Tool -- stats dynamo -t TableName -sr us-east-1\n   \n    # run a benchmark\n    dotnet run -c Release --project tools/Equinox.Tool -- loadtest -t saveforlater -f 50 -d 5 -CU dynamo\n\n    # run the webserver\n    dotnet run --project samples/Web/ -- dynamo -t TableName\n\n    # run a benchmark connecting to the webserver\n    eqx loadtest -t saveforlater -f 50 -d 5 -CU web\n    eqx dump \"SavedForLater-ab25cc9f24464d39939000aeb37ea11a\" dynamo # show stored JSON (Guid shown in eqx loadtest output) \n    ```\n\n3. Useful articles\n\n- [Troubleshooting throttling issues in Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingThrottling.html)\n- [Troubleshooting latency issues in Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/TroubleshootingLatency.html)\n\n### BENCHMARKS\n\nA key facility of this repo is being able to run load tests, either in process against a nominated store, or via HTTP to a nominated instance of `samples/Web` ASP.NET Core host app. The following test suites are implemented at present:\n\n- `Favorite` - Simulate a very enthusiastic user that favorites something once per second\n  - the test generates an ever-growing state that can only be managed efficiently if you apply either caching, snapshotting or both\n  - NB due to being unbounded, `Snapshot` and `MultiSnapshot` etc. (even `RollingState` or `Custom`) will eventually hit the Store's limits (4MB/event for EventStore, 3MB/Item (document) for CosmosDB)\n- `SaveForLater` - Simulate a happy shopper that saves 3 items per second, and empties the Save For Later list whenever it is full (when it hits 50 items)\n  - Snapshotting helps a lot\n  - Caching is not as essential as it is for the `Favorite` test (as long as you have either caching or snapshotting, that is)\n- `Todo` - Keeps a) getting the list b) adding an item c) clearing the list when it hits 1000 items.\n  - the `Cleared` event acts as a natural event to use in the `isOrigin` check. This makes snapshotting less crucial than it is, for example, in the case of the `Favorite` test\n  - the `-s` parameter can be used to adjust the maximum item text length from the default (`100`, implying average length of 50)\n\n## BUILDING\n\nPlease note the [QuickStart](#quickstart) is probably the best way to gain an overview - these instructions are intended to illustrated various facilities of the build script for people making changes.\n\n### build and run\n\nRun, including running the tests that assume you've got a local EventStore and pointers to a CosmosDB database and container prepared (see [PROVISIONING](#provisioning)):\n\n    ./build.ps1\n\n### build, skipping tests that require a Store instance\n\n    ./build -s\n\n### build, skipping all tests\n\n    dotnet pack build.proj\n\n### build, skip EventStore tests\n\n    ./build -se\n\n### build, skip EventStore tests, skip auto-provisioning / de-provisioning CosmosDB\n\n    ./build -se -scp\n\n### Run EventStore benchmark on .NET Core (when provisioned)\n\nAt present, .NET Core seems to show comparable perf under normal load, but becomes very unpredictable under load. The following benchmark should produce pretty consistent levels of reads and writes, and can be used as a baseline for investigation:\n\n    \u0026 dotnet run -c Release --project tools/Equinox.Tool -- loadtest -t saveforlater -f 1000 -d 5 -C -U es\n\n### run Web benchmark\n\nThe CLI can drive the Store and TodoBackend samples in the `samples/Web` ASP.NET Core app. Doing so requires starting a web process with an appropriate store (EventStore in this example, but can be `memory` / omitted etc. as in the other examples)\n\n#### in Window 1\n\n    \u0026 dotnet run -c Release --project samples/Web -- -C -U es\n\n#### in Window 2\n\n    dotnet tool install -g Equinox.Tool --prerelease # only once\n    eqx loadtest -t saveforlater -f 200 web\n\n### run CosmosDB benchmark (when provisioned)\n\n    dotnet run --project tools/Equinox.Tool -- loadtest `\n      cosmos -s $env:EQUINOX_COSMOS_CONNECTION -d $env:EQUINOX_COSMOS_DATABASE -c $env:EQUINOX_COSMOS_CONTAINER\n\n## PROVISIONING\n\n### Provisioning EventStore (when not using -s or -se)\n\nThere's a `docker-compose.yml` file in the root, so installing `docker-compose` and then running `docker-compose up` rigs a local 3-node cluster, which is assumed to be configured for `Equinox.EventStore.Integration` and `Equinox.EventStoreDb.Integration`\n\nFor more complete instructions, follow https://developers.eventstore.com/server/v21.10/installation.html#use-docker-compose\n\n\u003ca name=\"provisioning-cosmosdb\"\u003e\u003c/a\u003e\n### Provisioning CosmosDB (when not using build.ps1 -sc to skip verification)\n\n#### Using Azure Cosmos DB Service\n\n```bash\ndotnet run --project tools/Equinox.Tool -- init -ru 400 `\n    cosmos -s $env:EQUINOX_COSMOS_CONNECTION -d $env:EQUINOX_COSMOS_DATABASE -c $env:EQUINOX_COSMOS_CONTAINER\n# Same for a Archive Container for integration testing of the archive store fallback mechanism\n$env:EQUINOX_COSMOS_CONTAINER_ARCHIVE=\"equinox-test-archive\"\ndotnet run --project tools/Equinox.Tool -- init -ru 400 `\n    cosmos -s $env:EQUINOX_COSMOS_CONNECTION -d $env:EQUINOX_COSMOS_DATABASE -c $env:EQUINOX_COSMOS_CONTAINER_ARCHIVE\n```\n\n#### Using Cosmos Emulator on an Intel Mac \n\nNOTE There's [no Apple Silicon emulator available as yet](https://github.com/Azure/azure-cosmos-db-emulator-docker/issues/54#issuecomment-1399067365).\n\nNOTE Have not tested with the Windows Emulator, but it should work with analogous steps.\n\n```bash\ndocker compose up equinox-cosmos -d\nbash docker-compose-cosmos.sh\n```\n\n### Provisioning SqlStreamStore\n\nThere's a `docker-compose.yml` file in the root, so installing `docker-compose` and then running `docker-compose up` rigs local `equinox-mssql`, `equinox-mysql` and `equinox-postgres` servers and databases at known ports. _NOTE The `Equinox.SqlStreamStore.*.Integration` suites currently assume this is in place and will otherwise fail_.\n\n## DEPROVISIONING\n\n### Deprovisioning (aka nuking) EventStore data resulting from tests to reset baseline\n\nWhile EventStore rarely shows any negative effects from repeated load test runs, it can be useful for various reasons to drop all the data generated by the load tests by casting it to the winds:-\n\n    # requires admin privilege\n    rm $env:ProgramData\\chocolatey\\lib\\eventstore-oss\\tools\\data\n\n### Deprovisioning CosmosDB\n\nThe [provisioning](#provisioning) step spins up RUs in CosmosDB for the Container, which will keep draining your account until you reach a spending limit (if you're lucky!). *When finished running any test, it's critical to drop the RU allocations back down again via some mechanism (either delete the container or reset the RU provision down to the lowest possible value)*.\n\n- Kill the container and/or database\n- Use the portal to change the allocation\n\n# RELEASING\n\n*The perfect is the enemy of the good; [all this should of course be automated, but the elephant will be consumed in small bites rather than waiting till someone does it perfectly](https://github.com/jet/equinox/issues/80). This documents the actual release checklist as it stands right now. Any small helping bites much appreciated :pray: *\n\n## Tagging releases\n\nThis repo uses [MinVer](https://github.com/adamralph/minver); [see here](https://github.com/adamralph/minver#how-it-works) for more information on how it works.\n\nAll non-alpha releases derive from tagged commits on `master` or `vX` branch. The tag defines the nuget package id etc. that the release will bear (`dotnet pack` uses the `MinVer` package to grab the value from the commit)\n\n## Checklist\n\n- :cry: the Azure Pipelines script does not run the integration tests, so these need to be run manually via the following steps:\n\n  - [Provision](#provisioning):\n    - Set environment variables x 4 for a CosmosDB database and container (you might need to `eqx init`)\n    - Add a `EQUINOX_COSMOS_CONTAINER_ARCHIVE` environment variable referencing a separate (`eqx init` initialized) CosmosDB Container that will be used to house fallback events in the [Fallback mechanism's tests](https://github.com/jet/equinox/pull/247)\n    - `docker-compose up` to start\n      - 3 servers for the `SqlStreamStore.*.Integration` test suites (NOTE: manual step for MS SQL)\n      - 3 `EventStoreDB` cluster nodes\n      - DynamoDB local and admin images and (`dynamodb-local`, `dynamodb-admin`) \n  - Run `./build.ps1` in PowerShell (or PowerShell Core on MacOS via `brew install cask pwsh`)\n\n- [CHANGELOG](CHANGELOG.md) should be up to date\n- commit should be tagged (remember to do `git push --tags` when pushing)\n- after the push has resulted in a successful build, click through from the commit on github thru to the Azure Pipelines build state and verify _all_ artifacts bear the correct version suffix (if the tags were not pushed alongside the commit, they can be wrong). Then, and only then, do the Release (which will upload to nuget.org using a nuget API key that has upload permissions for the packages)\n- _When adding new packages_: For safety, the NuGet API Key used by the Azure DevOps Releases step can only upload new versions of existing packages. As a result, the first version of any new package needs to be manually uploaded out of band. (then invite jet.com to become owner so subsequent releases can do an automated upload [after the request has been (manually) accepted])\n\n## FAQ\n\n### What _is_ Equinox?\n\nOK, I've read the README and the tagline. I still don't know what it does! Really, what's the TL;DR ?\n\n- supports storing events in [EventStore](https://eventstore.org), including working with existing data you may have (that's where it got its start)\n- includes a proprietary optimized Store implementation that only needs an empty Azure CosmosDB Account or Amazon DynamoDB Table to get going\n- provides all the necessary infrastructure to build idempotent synchronous command processing against all of the stores; your Domain code intentionally doesn't need to reference *any* Equinox modules whatsoever (although for smaller systems, you'll often group `Events`+`Fold`+`interpret`/`decide`+`Service` in a single `module`, which implies a reference to [the core `Equinox` package](src/Equinox)).\n- following on from the previous point: you just write the unit tests without any Equinox-specific hoops to jump through; this really works very well indeed, assuming you're writing the domain code and the tests in F#. If you're working in a more verbose language, you may end up building some test helpers. We don't envisage Equinox mandating a specific pattern on the unit testing side (consistent naming such as `Events.Event`+`evolve`+`fold`+`Command`+`interpret`/`decide` can help though).\n- it helps with integration testing decision processes by\n  - staying out of your way as much as possible\n  - providing an in-memory store that implements the same interface as the concrete stores (CosmosDB, EventStore, etc.)  stores do\n- There is a projection story, but it's not baked in - any 3 proper architects can come up with at least 3 wrong and 3 right ways of running those:-\n  - For EventStore, you can use its' projections facilities directly. There's also a `Propulsion.EventStore` that serves the needs of `dotnet new proSync`..\n  - for CosmosDB, you use the `Propulsion.CosmosStore` libraries to consume the CosmosDB ChangeFeed using the `Microsoft.Azure.Cosmos` library's change feed support (and, optionally, project to/consume from Kafka) using the sample app templates (`dotnet new proProjector`).\n\n### Should I use Equinox to learn event sourcing ?\n\nYou _could_. However the Equinox codebase itself is not designed to be a tutorial; it's extracted from production systems and optimized; there is no pedagogical mission. [FsUno.Prod](https://github.com/thinkbeforecoding/FsUno.Prod) on the other hand has this specific intention, walking though that is highly recommended. Also [EventStore](https://eventstore.org/), being a widely implemented and well-respected open source system has some excellent learning materials and documentation with a wide usage community (search for `DDD-CQRS-ES` Discord).\n\nHaving said that, we'd love to see a set of tutorials written by people looking from different angles, and over time will likely do one too ... there's no reason why the answer to this question can't become \"**of course!**\"\n\n### Can I use it for really big projects?\n\nYou can. Folks in Jet do; we also have systems where we have no plans to use it, or anything like it. That's OK; there are systems where having precise control over one's data access is critical. And (shush, don't tell anyone!) some find writing this sort of infrastructure to be a very fun design challenge that beats doing domain modelling any day...\n\n### Can I use it for really small projects and tiny microservices?\n\nYou can. Folks in Jet do; but we also have systems where we have no plans to use it, or anything like it as it would be overkill even for people familiar with Equinox.\n\n### OK, but _should_ I use Equinox for a small project ?\n\nYou'll learn a lot from building your own equivalent wrapping layer. Given the array of concerns Equinox is trying to address, there's no doubt that a simpler solution is always possible if you constrain the requirements to specifics of your context with regard to a) scale b) complexity of domain c) degree to which you use or are likely to use \u003e1 data store. You can and should feel free to grab slabs of Equinox's implementation and whack it into an `Infrastructure.fs` in your project too (note you should adhere to the rules of the [Apache 2 license](LICENSE)). If you find there's a particular piece you'd really like isolated or callable as a component and it's causing you pain as [you're using it over and over in ~ \u003e= 3 projects](https://en.wikipedia.org/wiki/Rule_of_three_(computer_programming)), please raise an Issue though ! \n\nHaving said that, getting good logging, some integration tests and getting lots of off-by-one errors off your plate is nice; the point of [DDD-CQRS-ES](https://github.com/ddd-cqrs-es/community) is to get beyond toy examples to the good stuff - Domain Modelling on your actual domain.\n\n### What client languages are supported ?\n\nThe main language in mind for consumption is of course F# - many would say that F# and event sourcing are a dream pairing; little direct effort has been expended polishing it to be comfortable to consume from other .NET languages, the `dotnet new eqxwebcs` template represents the current state. In Equinox V4, the `DeciderCore` interface offers an interface that uses C#-friendly `Task` and `Func` types (compared to `Decider`, which uses `async` and curried function signatures to provide an idiomatic F# experience, which is possible, but cumbersome to use from C#) \n\n## You say I can use volatile memory for integration tests, could this also be used for learning how to get started building event sourcing programs with equinox? \n\nThe `MemoryStore` is intended to implement the complete semantics of a durable store (aside from caching). The main benefit of using it is that any tests using it have zero environment dependencies. In some cases this can be very useful for demo apps or generators (rather than assuming a specific store at a specific endpoint and/or credentials, there is something to point at which does not require configuration or assumptions.). The single problem of course is that it's all in-process; the minute you stop the host, the items on your list will of course disappear. In general, EventStore is also an attractive option for prototyping; the open source edition is trivial to install and has a Web UI that lets you navigate events being produced etc.\n\n### OK, so it supports CosmosDB, DynamoDB, EventStoreDB, MessageDB and SqlStreamStore and might even support more in the future. I really don't intend to shift datastores. Period. Why would I take on this complexity only to get the lowest common denominator ?\n\nYes, you have decisions to make; Equinox is not a panacea - there is no one size fits all. While the philosophy of Equinox is a) provide an opinionated store-neutral [Programming Model](DOCUMENTATION.md#Programming-Model) with a good pull toward a big [pit of success](https://blog.codinghorror.com/falling-into-the-pit-of-success/), while not closing the door to using store-specific features where relevant, having a dedicated interaction is always going to afford you more power and control.\n\n### Is there a guide to building the simplest possible hello world \"counter\" sample, that simply counts with an add and a subtract event? \n\nYes; [`Counter.fsx` in th Tutorial project in this repo](https://github.com/jet/equinox/blob/master/samples/Tutorial/Counter.fsx). It may also be worth starting with the [API Guide in DOCUMENTATION.md](DOCUMENTATION.md#api). An alternate way is to look at the `Todo.fs` files emitted by [`dotnet new equinoxweb`](https://github.com/jet/dotnet-templates) in the [QuickStart](#quickstart).\n\n\u003ca name=\"why-snapshots-in-stream\"\u003e\u003c/a\u003e\n### Why do the snapshots go in the same stream in `Equinox.EventStore` and `Equinox.SqlStreamStore` ? :pray: [@chrisjhoare](https://github.com/chrisjhoare)\n\nI've been looking through the snapshotting code recently. Can see the snapshot events go in the same stream as regular events. Presume this is to save on operations to read/write the streams? and a bit less overhead maintaining two serializers? Are there any other advantages? I quite like it this way but think i saw the [geteventstore](https://eventstore.org) advice was separate streams so just interested in any other reasoning behind it\n\nThe reason GES recommends against is that the entire db is built on writing stuff once in an append only manner (which is a great design from most aspects). This means your choices are:\n  - embed snapshots in the same stream, but do that sparingly, as you can't delete them (implemented in Equinox as `AccessStrategy.RollingSnapshots`)\n  - keep snapshots elsewhere (typically in a sister stream with the max items set to 1\n    - which the EventStoreDB background scavenging process will tidy up (but it ain't free)\n    - which is a separate roundtrip (which is not the end of the world in GES but is still another thing to go wrong)\n    - which can't be written as a transaction, i.e. you'd need to write the snapshot after (and only after) a successful write (and worry about inconsistency)\nThat general advice/trade-offs on snapshotting applies to most systems.\n\nThe answer as to why that strategy is available in in `Equinox.EventStore` is for based on use cases (the second strategy was actually implemented in a bespoke manner initially by [@eiriktsarpalis](https://github.com/eiriktsarpalis):\n- streams like `Favorites` where every event is small (add sku, drop sku), and the snapshot is pretty compact (list of skus) (but note it is ever growing)\n- streams like `SavedForLater` items where the state rolls over regularly - even after 5 years and 1000s of items moving in and out, there's a constraint of max 50 items which makes a snapshot pretty light. (The other trick is that a `Cleared` event counts as a valid starting state for the fold - and we don't write a snapshot if we have one of those)\n\nThe big win is latency in querying contexts - given that access strategy, you're guaranteed to be able to produce the full state of the aggregate with a single roundtrip (if max batch size is 200, the snapshots are written every 200 items so reading backward 200 guarantees a snapshot will be included)\n\nThe secondary benefit is of course that you have an absolute guarantee there will always be a snapshot, and if a given write succeeds, there will definitely be a snapshot in the `maxBatchSize` window (but it still copes if there isn't - i.e. you can add snapshotting after the fact)\n\n`Equinox.SqlStreamStore` implements this scheme too - it's easier to do things like e.g. replace the bodies of snapshot events with `nulls` as a maintenance task in that instance\n\nInitially, `Equinox.CosmosStore` implemented the same strategy as the `Equinox.EventStore` (it started as a cut and paste of the it). However the present implementation takes advantage of the fact that in a Document Store, you can ... update documents - thus, snapshots (termed unfolds) are saved in a custom field (it's an array) in the Tip document - every update includes an updated snapshot (which is zipped to save read and write costs) that overwrites the unfolds entirely. You're currently always guaranteed that the snapshots are in sync with the latest event by virtue of how the stored proc writes. The DynamoDB impl follows the same strategy.\n\nI expand (too much!) on some more of the considerations in https://github.com/jet/equinox/blob/master/DOCUMENTATION.md\n\nThe other thing that should be pointed out is the caching can typically cover a lot of perf stuff as long as stream lengths stay sane - Snapshotting (esp polluting the stream with snapshot events should definitely be toward the bottom of your list of tactics for managing a stream efficiently given long streams are typically a design smell)\n\nNOTE The newer `Equinox.MessageDb` store binding implements snapshotting as separated events in a separate category.\n\n\u003ca name=\"changing-access-strategy\"\u003e\u003c/a\u003e\n### Changing Access / Representation strategies in `Equinox.CosmosStore` - what happens?\n\n\u003e Does Equinox adapt the stream if we start writing with `Equinox.CosmosStore.AccessStrategy.RollingState` and change to `Snapshotted` for instance? It could take the last RollingState writing and make the first snapshot ?\n\n\u003e what about the opposite? It deletes all events and start writing `RollingState` ?\n\nTL;DR yes and no respectively\n\n#### Some context\n\nFirstly, it's recommended to read the [documentation section on Access Strategies](DOCUMENTATION.md#access-strategies)\n\nGeneral rules:\n- Events are the atoms from which state is built, they live forever in immutable Batch documents.\n- There is a special Batch with `id = \"-1\"`, entitled the *Tip*.\n- Snapshots/unfolds live in the `.u` array in the Tip doc.\n loading/build of state is composed of\n- regardless of what happens, Events are _never_ destroyed, updated or touched in any way, ever. Having said that, if your Event DU does not match them, they're also as good as not there from the point of view of how State is established.\n- Reads always get the `Tip` first (one exception: `Unoptimized` mode skips reading the `Tip` as, by definition, you're not using snapshots/unfolds/any tricks), Writes always touch the `Tip` (yes, even in `Unoptimized` mode; there's no such thing as a stream that has ever been written to that does not have a `Tip`).\n- In the current implementation, the calling code in the server figures out everything that's going to go in the ~~snapshots~~ unfolds list if this sync is successful.\n \n The high level skeleton of the loading in a given access strategy is: \n   a) load and decode unfolds from tip (followed by events, if and only if necessary)\n   b) offer the events to an `isOrigin` function to allow us to stop when we've got a start point (a Reset Event, a relevant snapshot, or, failing that, the start of the stream)\n\nIt may be helpful to look at [how an `AccessStrategy` is mapped to `isOrigin`, `toSnapshot` and `transmute` lambdas internally](https://github.com/jet/equinox/blob/master/src/Equinox.CosmosStore/CosmosStore.fs#L1295)\n\n#### Aaand answering the question\n\nWhenever a State is being built, it always loads `Tip` first and shows any ~~events~~ ~~snapshots~~ _unfolds_ in there...\n \nIf `isOrigin` says no to those and/or the `EventType`s of those unfolds are not in the union / event type to which the codec is mapping, the next thing is a query backwards of the Batches of events, in order.\n\nAll those get pushed onto a stack until we either hit the start, or `isOrigin` says - yes, we can start from here (at which point all the decoded events are then passed (in forward order) to the `fold` to make the `'state`).\n\nSo, if you are doing `RollingState` or any other mode, there are still events and unfolds; and they all have `EventType`s - there are just some standard combos of steps that happen.\n\nIf the `EventType` of the Event or Unfold matches, the `fold`/`evolve` will see them and build `'state` from that.\n\nThen, whenever you emit events from a `decide` or `interpret`, the `AccessStrategy` will define what happens next; a mix of:\n- write actual events (not if `RollingState`)\n- write updated unfolds/snapshots\n- remove or adjust events before they get passed down to the `sync` stored procedure (`Custom`, `RollingState`, `LatestKnownEvent` modes)\n\nOuch, not looking forward to reading all that logic :frown: ? [Have a read, it's really not that :scream:](https://github.com/jet/equinox/blob/master/src/Equinox.CosmosStore/CosmosStore.fs#1109).\n\n\u003ca name=\"how-is-expectedVersion-managed\"/\u003e\u003c/a\u003e\n### Help me understand how the `expectedVersion` is used with EventStoreDB - it seems very confusing :pray: [@dharmaturtle](https://github.com/dharmaturtle) \n\n\u003e I'm having some trouble understanding how Equinox+ESDB handles \"expected version\". Most of the examples use `Equinox.Decider.Transact` which is storage agnostic and doesn't offer any obvious concurrency checking. In `Equinox.EventStore.Context`, there's a `Sync` that takes a `Token` which holds a `streamVersion`. Should I be be using that instead of `Transact`?\n\nThe bulk of the implementation is in [`Equinox/Stream.fs`](https://github.com/jet/equinox/blob/master/src/Equinox/Stream.fs#L32), see the `let run` function.\n\nThere are [sequence diagrams in Documentation MD](https://github.com/jet/equinox/blob/master/DOCUMENTATION.md#code-diagrams-for-equinoxeventstore--equinoxsqlstreamstore) but I'll summarize here:\n\n- As you suggest, `Transact` is definitely the API you want to be be using\n- The assumption in Equinox is that you _always_ want to do a version check - if you don't, you can't process idempotently, why incur the cost of an ordered append only store? (there is a lower `Sync` operation which does a blind write to the store in `Equinox.CosmosStore` which allows you to do a non-version-checked write in that context (its implemented and exposed as the stored procedure needs to handle the concept). For EventStoreDB, if you have such a special case, you can use its APIs directly)\n- The inner API with the `Sync` is the 'store interface' which represents the actual processing needed to do a version-checked write (The `Sync` one does not handle retries and is only used for the last attempt, when there are no subsequent retries)\n- The main reason for the separation is that no ephemeral state is held by Equinox in anything like e.g. Unit Of Work during the course of a `decide` function being invoked - the `(token,state)` tuple represents all the things known at the point of loading, and the `Sync` can use anything it stashed in there when it has proposed events passed to it, as the contract involves the caller resupplying that context.\n- Another consideration is that its easy to introduce off by one errors when there's an expectedVersion in play, so encapsulating this is no bad thing (in addition to it being something that you don't want to be passing around in your domain logic)\n\nBut why, you might ask? the API is designed such that the token can store any kind of state relevant to the `Sync` operation.\n\na. for SqlStreamStore and EventStore, when writing rolling snapshots, we need to retain the index of the last Rolling Snapshot that was written, if we encountered it during loading (e.g. if we read V198-100 and there was a snapshot at at V101, then we need to write a new one iff the events we are writing would make event 101 be \u003e batchSize events away, i.e. we need to always include a RollingSnapshot to maintain the \"if you read the last page, it will include a rolling snapshot\" guarantee)\n\nb. for CosmosDB, the `expectedVersion` can actually be an `expectedEtag` - this is how `AccessStrategy.RollingState` works - this allows one to update Unfolds without having to add an event every time just to trigger a change in the version\n\n(The second usage did not necessitate an interface change - i.e. the Token mechanism was introduced to handle the first case, and just happened to fit the second case)\n\n\u003e Alternatively, I'm seeing in `proReactor` that there's a `decide` that does version checking. Is this recommended? [code](https://github.com/jet/dotnet-templates/blob/3329510601450ab77bcc40df7a407c5f0e3c8464/propulsion-reactor/TodoSummary.fs#L30-L52) \n\nIf you need to know the version in your actual handler, QueryEx and other such APIs alongside Transact expose it (e.g. if you want to include a version to accompany a directly rendered piece of data). (Note that doing this - including a version in a rendering of something should not be a goto strategy - i.e. having APIs that pass around expectedVersion is not a good idea in general)\n\nThe typical case for using the version in the output is to be able to publish a versioned summary on a feed, so someone else can build a version-checking idempotent Ingester..... Which brings us to:\n\nFor that particular reactor, a different thing is going on though: the input value is versioned, and we don't write if the value is in date e.g. if you reset the checkpoint on the projector, it can re-run all the work idempotently:\n\na. version 3 of something is never temporarily overwritten with V2 and then V3\n\nb. no redundant writes take place (and no expensive RU costs are incurred in Cosmos)\n\n### What kind of values does `ISyncContext.Version` return; i.e. what numeric value is yielded for an empty stream? :pray: [@ragiano215](https://github.com/ragiano215)\n\nIndependent of the backing store being used, Equinox uses `0`-based versioning, i.e. the version value is equal to the number of events in the stream. Each event's `Index` is `0`-based, akin to how a .NET array is numbered:\n\n* **`1` when the first event is written**\n* _`0` when there's no events_\n\n\u003e Side note: for contrast, EventStoreDB employs a different (`-1`-based) scheme in order to have `-1`/`-2` etc represent various `expectedVersion` conditions \n\u003e\n\u003e * **`0` when the first event is written to the stream**\n\u003e * _`-1` when the stream exists and is empty but has metadata defined_\n\u003e * _`-2` when the stream doesn't exist_\n\nNote that for `Equinox.CosmosStore` with a\n[pruner](https://github.com/jet/dotnet-templates/tree/master/propulsion-pruner)-[archiver](https://github.com/jet/dotnet-templates/tree/master/propulsion-archiver)\npair configured, the primary store may have been stripped of events due to the operation of the pruner.\nIn this case, it will however retain the version of the stream in the tip document, and if that's non-`0`,\nwill attempt to load the archived events from the Archive store.\n\n### What is Equinox's behavior if one does a `Query` on a 'non-existent' stream? :pray: [@ragiano215](https://github.com/ragiano215)\n\nExample: I have an app serving a GET endpoint for a customer order, but the id supplied within the URL is for an order that hasn't yet been created.\n\nNote firstly that Equinox treats a non-existent stream as an empty stream. For the use case stated, it's first\nrecommended that the state is defined to represent this non-existent / uninitialized phase, e.g.: defining a DU with a\nvariant `Initial`, or in some way following the [Null Object Pattern](https://en.wikipedia.org/wiki/Null_object_pattern).\nThis value would thus be used as the `Fold.initial` for the Category. The app will use a `.Query`/`.QueryEx` on the relevan","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjet%2Fequinox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjet%2Fequinox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjet%2Fequinox/lists"}