Skip to content

Commit

Permalink
Add EventStoreDB module (#894)
Browse files Browse the repository at this point in the history
  • Loading branch information
botflux authored Jan 13, 2025
1 parent 934328d commit efe3426
Show file tree
Hide file tree
Showing 10 changed files with 288 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/modules/eventstoredb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# EventStoreDB Module

[EventStoreDB](https://eventstore.com) is an event sourcing database that stores data in streams of immutable events.

## Install

```bash
npm install @testcontainers/eventstoredb --save-dev
```

## Examples

<!--codeinclude-->
[Start container:](../../packages/modules/eventstoredb/src/eventstoredb-container.test.ts) inside_block:startContainer
<!--/codeinclude-->

<!--codeinclude-->
[Subscribe to standard projection:](../../packages/modules/eventstoredb/src/eventstoredb-container.test.ts) inside_block:usingStandardProjections
<!--/codeinclude-->
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ nav:
- ChromaDB: modules/chromadb.md
- Couchbase: modules/couchbase.md
- Elasticsearch: modules/elasticsearch.md
- EventStoreDB: modules/eventstoredb.md
- GCloud: modules/gcloud.md
- HiveMQ: modules/hivemq.md
- K3s: modules/k3s.md
Expand Down
55 changes: 55 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions packages/modules/eventstoredb/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Config } from "jest";
import * as path from "path";

const config: Config = {
preset: "ts-jest",
moduleNameMapper: {
"^testcontainers$": path.resolve(__dirname, "../../testcontainers/src"),
},
};

export default config;
32 changes: 32 additions & 0 deletions packages/modules/eventstoredb/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@testcontainers/eventstoredb",
"version": "10.16.0",
"description": "EventStoreDB module for Testcontainers",
"main": "build/index.js",
"scripts": {
"prepack": "shx cp ../../../README.md . && shx cp ../../../LICENSE .",
"build": "tsc --project tsconfig.build.json"
},
"repository": {
"type": "git",
"url": "git+https://github.com/testcontainers/testcontainers-node.git"
},
"keywords": [
"eventstoredb",
"testing",
"docker",
"testcontainers"
],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/testcontainers/testcontainers-node/issues"
},
"homepage": "https://github.com/testcontainers/testcontainers-node#readme",
"dependencies": {
"testcontainers": "^10.16.0"
},
"devDependencies": {
"@eventstore/db-client": "^6.2.1"
}
}
106 changes: 106 additions & 0 deletions packages/modules/eventstoredb/src/eventstoredb-container.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { EventStoreDBClient, StreamingRead, StreamSubscription } from "@eventstore/db-client";
import { EventStoreDBContainer } from "./eventstoredb-container";

describe("EventStoreDBContainer", () => {
jest.setTimeout(240_000);

// startContainer {
it("should execute write and read", async () => {
const container = await new EventStoreDBContainer().start();

const client = EventStoreDBClient.connectionString(container.getConnectionString());

await client.appendToStream("User-1", [
{
contentType: "application/json",
data: { email: "[email protected]" },
type: "UserCreated",
id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38",
metadata: {
someMetadata: "bar",
},
},
]);

expect(await consumeSteamingRead(client.readStream("User-1"))).toEqual([
expect.objectContaining({
event: expect.objectContaining({
data: {
email: "[email protected]",
},
id: "28ab6bca-d9ae-418b-a1af-eb65dd653c38",
isJson: true,
metadata: {
someMetadata: "bar",
},
revision: 0n,
streamId: "User-1",
type: "UserCreated",
}),
}),
]);

await container.stop();
});
// }

// usingStandardProjections {
it("should use built-in projections", async () => {
const container = await new EventStoreDBContainer().start();
const client = EventStoreDBClient.connectionString(container.getConnectionString());

await client.appendToStream("Todo-1", [
{
contentType: "application/json",
data: { title: "Do something" },
metadata: {},
id: "7eccc3a7-0664-4348-a621-029125741e22",
type: "TodoCreated",
},
]);
const stream = client.subscribeToStream("$ce-Todo", { resolveLinkTos: true });

expect(await getStreamFirstEvent(stream)).toEqual(
expect.objectContaining({
event: expect.objectContaining({
data: { title: "Do something" },
id: "7eccc3a7-0664-4348-a621-029125741e22",
isJson: true,
metadata: {},
revision: 0n,
streamId: "Todo-1",
type: "TodoCreated",
}),
link: expect.objectContaining({
isJson: false,
metadata: expect.objectContaining({
$causedBy: "7eccc3a7-0664-4348-a621-029125741e22",
$o: "Todo-1",
}),
revision: 0n,
streamId: "$ce-Todo",
type: "$>",
}),
})
);
await stream.unsubscribe();
await container.stop();
});
// }
});

async function consumeSteamingRead(read: StreamingRead<unknown>): Promise<unknown[]> {
const events = [];

for await (const event of read) {
events.push(event);
}

return events;
}

async function getStreamFirstEvent(stream: StreamSubscription): Promise<unknown> {
for await (const event of stream) {
return event;
}
}
29 changes: 29 additions & 0 deletions packages/modules/eventstoredb/src/eventstoredb-container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { AbstractStartedContainer, GenericContainer, Wait } from "testcontainers";

export class StartedEventStoreDBContainer extends AbstractStartedContainer {
getConnectionString(): string {
return `esdb://${this.getHost()}:${this.getFirstMappedPort()}?tls=false`;
}
}

const EVENT_STORE_DB_PORT = 2113;

export class EventStoreDBContainer extends GenericContainer {
constructor(image = "eventstore/eventstore:24.10") {
super(image);

this.withExposedPorts(EVENT_STORE_DB_PORT)
.withEnvironment({
EVENTSTORE_CLUSTER_SIZE: "1",
EVENTSTORE_RUN_PROJECTIONS: "All",
EVENTSTORE_START_STANDARD_PROJECTIONS: "true",
EVENTSTORE_INSECURE: "true",
})
.withStartupTimeout(120_000)
.withWaitStrategy(Wait.forHealthCheck());
}

public override async start(): Promise<StartedEventStoreDBContainer> {
return new StartedEventStoreDBContainer(await super.start());
}
}
1 change: 1 addition & 0 deletions packages/modules/eventstoredb/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { StartedEventStoreDBContainer, EventStoreDBContainer } from "./eventstoredb-container";
13 changes: 13 additions & 0 deletions packages/modules/eventstoredb/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"exclude": [
"build",
"jest.config.ts",
"src/**/*.test.ts"
],
"references": [
{
"path": "../../testcontainers"
}
]
}
21 changes: 21 additions & 0 deletions packages/modules/eventstoredb/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "build",
"paths": {
"testcontainers": [
"../../testcontainers/src"
]
}
},
"exclude": [
"build",
"jest.config.ts"
],
"references": [
{
"path": "../../testcontainers"
}
]
}

0 comments on commit efe3426

Please sign in to comment.