From cc8c558db64df69cbaa75906efa3b2e578f1b7f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoffer=20R=C3=B8dvik?= Date: Mon, 11 Aug 2025 13:34:28 +0200 Subject: [PATCH] feat: update aggregate implementation --- README.md | 60 -------- adapters/mongo/collections/events.ts | 2 +- adapters/mongo/collections/relations.ts | 2 +- adapters/mongo/collections/snapshots.ts | 2 +- adapters/mongo/utilities.ts | 2 +- deno.json | 2 +- deno.lock | 171 +++++++++++----------- libraries/aggregate-factory.ts | 74 ---------- libraries/aggregate.ts | 61 +++++--- libraries/errors.ts | 45 +++++- libraries/event-store.ts | 183 +++++++++++------------- libraries/event.ts | 2 +- libraries/reducer.ts | 7 +- libraries/zod.ts | 2 +- mod.ts | 1 - package.json | 18 +-- tests/browser-iddb.test.ts | 11 +- tests/browser-memory.test.ts | 11 +- tests/mocks/aggregates.ts | 45 +----- tests/mocks/events.ts | 4 +- tests/mocks/user-posts-reducer.ts | 4 +- tests/mocks/user-reducer.ts | 4 +- tests/mongodb.test.ts | 11 +- tests/postgres.test.ts | 15 +- tests/store/add-event.ts | 4 +- tests/store/add-many-events.ts | 4 +- tests/store/create-snapshot.ts | 4 +- tests/store/make-aggregate-reducer.ts | 14 +- tests/store/make-event.ts | 4 +- tests/store/make-reducer.ts | 4 +- tests/store/once-projection.ts | 4 +- tests/store/providers/relations.ts | 4 +- tests/store/push-aggregate.ts | 30 ++-- tests/store/push-many-aggregates.ts | 51 +++---- tests/store/reduce.ts | 4 +- tests/store/replay-events.ts | 4 +- tests/utilities/describe.ts | 2 +- 37 files changed, 361 insertions(+), 511 deletions(-) delete mode 100644 libraries/aggregate-factory.ts diff --git a/README.md b/README.md index 5b5a7c0..b17182f 100644 --- a/README.md +++ b/README.md @@ -11,66 +11,6 @@ repository to one or more distibuted services. The following provides a quick introduction on how to get started. -### Configs - -Events are defined in `json` configuration files which we print to a generated `events.ts` file that is used by the -event store instance we are using. To do this, start by creating a new folder that will house our event configurations. - -```sh -$ mkdir events -$ cd events -``` - -Now add a new event configuration file. - -```sh -$ touch user-created.json -``` - -Open the file and add the event details. - -```json -{ - "event": { - "type": "user:created", - "data": { - "name": { - "type": "object", - "properties": { - "given": { - "type": "string" - }, - "family": { - "type": "string" - } - } - }, - "email": { - "type": "string" - } - }, - "meta": { - "auditor": { - "type": "string" - } - } - } -} -``` - -### Generate - -To create our `events.ts` file we have to run our configurations through our event printer. - -```ts -import { printEvents } from "@valkyr/event-store"; - -await printEvents({ - inputs: ["./configs/events"], - outputs: ["./generated/events.ts"], -}); -``` - ### Event Store Once we have defined our configs and printed our events we create a new event store instance. Currently we have support diff --git a/adapters/mongo/collections/events.ts b/adapters/mongo/collections/events.ts index be5aa82..98b6239 100644 --- a/adapters/mongo/collections/events.ts +++ b/adapters/mongo/collections/events.ts @@ -1,4 +1,4 @@ -import z from "zod/v4"; +import z from "zod"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/collections/relations.ts b/adapters/mongo/collections/relations.ts index 4fba38d..494eba7 100644 --- a/adapters/mongo/collections/relations.ts +++ b/adapters/mongo/collections/relations.ts @@ -1,4 +1,4 @@ -import z from "zod/v4"; +import z from "zod"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/collections/snapshots.ts b/adapters/mongo/collections/snapshots.ts index af6c372..55d12a8 100644 --- a/adapters/mongo/collections/snapshots.ts +++ b/adapters/mongo/collections/snapshots.ts @@ -1,4 +1,4 @@ -import z from "zod/v4"; +import z from "zod"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/utilities.ts b/adapters/mongo/utilities.ts index 4743970..0b53eb2 100644 --- a/adapters/mongo/utilities.ts +++ b/adapters/mongo/utilities.ts @@ -1,5 +1,5 @@ import type { Db, WithId } from "mongodb"; -import type { z, ZodObject } from "zod/v4"; +import type { z, ZodObject } from "zod"; /** * Take a list of records and run it through the given zod parser. This diff --git a/deno.json b/deno.json index b3257db..3433af3 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@valkyr/event-store", - "version": "2.0.0-beta.4", + "version": "2.0.0-beta.5", "exports": { ".": "./mod.ts", "./browser": "./adapters/browser/adapter.ts", diff --git a/deno.lock b/deno.lock index 313b8a9..81a4804 100644 --- a/deno.lock +++ b/deno.lock @@ -1,23 +1,23 @@ { "version": "5", "specifiers": { - "npm:@jsr/std__assert@1.0.13": "1.0.13", - "npm:@jsr/std__async@1.0.13": "1.0.13", - "npm:@jsr/std__testing@1.0.14": "1.0.14", - "npm:@jsr/valkyr__testcontainers@2": "2.0.1", + "npm:@jsr/std__assert@1": "1.0.13", + "npm:@jsr/std__async@1": "1.0.14", + "npm:@jsr/std__testing@1": "1.0.15", + "npm:@jsr/valkyr__testcontainers@2": "2.0.2", "npm:@valkyr/db@1.0.1": "1.0.1", - "npm:eslint-plugin-simple-import-sort@12.1.1": "12.1.1_eslint@9.30.1", - "npm:eslint@9.30.1": "9.30.1", - "npm:fake-indexeddb@6.0.1": "6.0.1", - "npm:mongodb@6": "6.17.0", + "npm:eslint-plugin-simple-import-sort@12": "12.1.1_eslint@9.33.0", + "npm:eslint@9": "9.33.0", + "npm:fake-indexeddb@6": "6.1.0", + "npm:mongodb@6": "6.18.0", "npm:nanoid@5": "5.1.5", "npm:postgres@3": "3.4.7", - "npm:prettier@3.6.2": "3.6.2", - "npm:typescript-eslint@8.35.1": "8.35.1_eslint@9.30.1_typescript@5.8.3_@typescript-eslint+parser@8.35.1__eslint@9.30.1__typescript@5.8.3", - "npm:zod@3.25": "3.25.75" + "npm:prettier@3": "3.6.2", + "npm:typescript-eslint@8": "8.39.0_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2", + "npm:zod@4": "4.0.17" }, "npm": { - "@eslint-community/eslint-utils@4.7.0_eslint@9.30.1": { + "@eslint-community/eslint-utils@4.7.0_eslint@9.33.0": { "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dependencies": [ "eslint", @@ -35,17 +35,11 @@ "minimatch@3.1.2" ] }, - "@eslint/config-helpers@0.3.0": { - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" + "@eslint/config-helpers@0.3.1": { + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==" }, - "@eslint/core@0.14.0": { - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", - "dependencies": [ - "@types/json-schema" - ] - }, - "@eslint/core@0.15.1": { - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "@eslint/core@0.15.2": { + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dependencies": [ "@types/json-schema" ] @@ -64,16 +58,16 @@ "strip-json-comments" ] }, - "@eslint/js@9.30.1": { - "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==" + "@eslint/js@9.33.0": { + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==" }, "@eslint/object-schema@2.1.6": { "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" }, - "@eslint/plugin-kit@0.3.3": { - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "@eslint/plugin-kit@0.3.5": { + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dependencies": [ - "@eslint/core@0.15.1", + "@eslint/core", "levn" ] }, @@ -103,13 +97,16 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__assert/1.0.13.tgz" }, - "@jsr/std__async@1.0.13": { - "integrity": "sha512-GEApyNtzauJ0kEZ/GxebSkdEN0t29qJtkw+WEvzYTwkL6fHX8cq3YWzRjCqHu+4jMl+rpHiwyr/lfitNInntzA==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.13.tgz" + "@jsr/std__async@1.0.14": { + "integrity": "sha512-aIG8W3TOmW+lKdAJA5w56qASu9EiUmBXbhW6eAlSEUBid+KVESGqQygFFg+awt/c8K+qobVM6M/u3SbIy0NyUQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.14.tgz" }, - "@jsr/std__data-structures@1.0.8": { - "integrity": "sha512-7BHBUlBEJ/9w2zv9sNmyuQOINBTEP1erxLHMpIDBa7GMCV1Nxm6LvgC4R5cgN90FFKpoCFa9PPB66Hkeem9Q2g==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__data-structures/1.0.8.tgz" + "@jsr/std__data-structures@1.0.9": { + "integrity": "sha512-+mT4Nll6fx+CPNqrlC+huhIOYNSMS+KUdJ4B8NujiQrh/bq++ds5PXpEsfV5EPR+YuWcuDGG0P1DE+Rednd7Wg==", + "dependencies": [ + "@jsr/std__assert" + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__data-structures/1.0.9.tgz" }, "@jsr/std__fs@1.0.19": { "integrity": "sha512-TEjyE8g+46jPlu7dJHLrwc8NMGl8zfG+JjWxyNQyDbxP0RtqZ4JmYZfR9vy4RWYWJQbLpw6Kbt2n+K/2zAO/JA==", @@ -119,9 +116,9 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.19.tgz" }, - "@jsr/std__internal@1.0.9": { - "integrity": "sha512-s+f4qrJzZgPAy7XuFOtgaSaxyPLnnEmAfXGLvRXGxPTL76URLVHkF+hOzqXz+bmk8/awybF6BRsasxtAQOV23Q==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.9.tgz" + "@jsr/std__internal@1.0.10": { + "integrity": "sha512-fmD6yKep/sMnB2yPQU/REZG7Z4N9SZwcUBNnceo4QkXk67l3JEfxHoROQ/YHeVSOmq6x55Ra6nuMjz2ib3nj3g==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.10.tgz" }, "@jsr/std__net@1.0.4": { "integrity": "sha512-KJGU8ZpQ70sMW2Zk+wU3wFUkggS9lTLfRFBygnV9VaK8KI+1ggiqtB06rH4a14CNRGM9y46Mn/ZCbQUd4Q45Jg==", @@ -134,8 +131,8 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__path/1.1.1.tgz" }, - "@jsr/std__testing@1.0.14": { - "integrity": "sha512-WQ2ctU3AmV0dcaVEahIUfz4e+3+Y3UMyqFLjCZ6JKeI40zkDpeMFBsTop7e7ptGE4wgHrQj+FETh9XAgEuBlZA==", + "@jsr/std__testing@1.0.15": { + "integrity": "sha512-NgQuXxTEG4ecbh2fzYbkJWJoBgPXwbv6bdsrAYSOeLpX2d+TROEzpErbWQXHi/yxZy/FNn9IF548ZDAqMZxi/g==", "dependencies": [ "@jsr/std__assert", "@jsr/std__async", @@ -144,10 +141,10 @@ "@jsr/std__internal", "@jsr/std__path" ], - "tarball": "https://npm.jsr.io/~/11/@jsr/std__testing/1.0.14.tgz" + "tarball": "https://npm.jsr.io/~/11/@jsr/std__testing/1.0.15.tgz" }, - "@jsr/valkyr__testcontainers@2.0.1": { - "integrity": "sha512-HInqMkCDj1ICrcz+Led/3jyLa70mwncxdlly8v/5WepuPW3gszKftq5U2jbjy2THOYUP7ibBK2o0recg7qhvcw==", + "@jsr/valkyr__testcontainers@2.0.2": { + "integrity": "sha512-YnmfraYFr3msoUGrIFeElm03nbQqXOaPu0QUT6JI3w6/mIYpVfzPxghkB7gn2RIc81QgrqjwKJE/AL3dltlR1w==", "dependencies": [ "@jsr/std__async", "@jsr/std__fs", @@ -155,7 +152,7 @@ "mongodb", "postgres" ], - "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__testcontainers/2.0.1.tgz" + "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__testcontainers/2.0.2.tgz" }, "@mongodb-js/saslprep@1.3.0": { "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", @@ -195,8 +192,8 @@ "@types/webidl-conversions" ] }, - "@typescript-eslint/eslint-plugin@8.35.1_@typescript-eslint+parser@8.35.1__eslint@9.30.1__typescript@5.8.3_eslint@9.30.1_typescript@5.8.3": { - "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==", + "@typescript-eslint/eslint-plugin@8.39.0_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2_eslint@9.33.0_typescript@5.9.2": { + "integrity": "sha512-bhEz6OZeUR+O/6yx9Jk6ohX6H9JSFTaiY0v9/PuKT3oGK0rn0jNplLmyFUGV+a9gfYnVNwGDwS/UkLIuXNb2Rw==", "dependencies": [ "@eslint-community/regexpp", "@typescript-eslint/parser", @@ -212,8 +209,8 @@ "typescript" ] }, - "@typescript-eslint/parser@8.35.1_eslint@9.30.1_typescript@5.8.3": { - "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==", + "@typescript-eslint/parser@8.39.0_eslint@9.33.0_typescript@5.9.2": { + "integrity": "sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==", "dependencies": [ "@typescript-eslint/scope-manager", "@typescript-eslint/types", @@ -224,8 +221,8 @@ "typescript" ] }, - "@typescript-eslint/project-service@8.35.1_typescript@5.8.3": { - "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==", + "@typescript-eslint/project-service@8.39.0_typescript@5.9.2": { + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dependencies": [ "@typescript-eslint/tsconfig-utils", "@typescript-eslint/types", @@ -233,22 +230,23 @@ "typescript" ] }, - "@typescript-eslint/scope-manager@8.35.1": { - "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", + "@typescript-eslint/scope-manager@8.39.0": { + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", "dependencies": [ "@typescript-eslint/types", "@typescript-eslint/visitor-keys" ] }, - "@typescript-eslint/tsconfig-utils@8.35.1_typescript@5.8.3": { - "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==", + "@typescript-eslint/tsconfig-utils@8.39.0_typescript@5.9.2": { + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", "dependencies": [ "typescript" ] }, - "@typescript-eslint/type-utils@8.35.1_eslint@9.30.1_typescript@5.8.3": { - "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==", + "@typescript-eslint/type-utils@8.39.0_eslint@9.33.0_typescript@5.9.2": { + "integrity": "sha512-6B3z0c1DXVT2vYA9+z9axjtc09rqKUPRmijD5m9iv8iQpHBRYRMBcgxSiKTZKm6FwWw1/cI4v6em35OsKCiN5Q==", "dependencies": [ + "@typescript-eslint/types", "@typescript-eslint/typescript-estree", "@typescript-eslint/utils", "debug", @@ -257,11 +255,11 @@ "typescript" ] }, - "@typescript-eslint/types@8.35.1": { - "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==" + "@typescript-eslint/types@8.39.0": { + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==" }, - "@typescript-eslint/typescript-estree@8.35.1_typescript@5.8.3": { - "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", + "@typescript-eslint/typescript-estree@8.39.0_typescript@5.9.2": { + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", "dependencies": [ "@typescript-eslint/project-service", "@typescript-eslint/tsconfig-utils", @@ -276,8 +274,8 @@ "typescript" ] }, - "@typescript-eslint/utils@8.35.1_eslint@9.30.1_typescript@5.8.3": { - "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", + "@typescript-eslint/utils@8.39.0_eslint@9.33.0_typescript@5.9.2": { + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", "dependencies": [ "@eslint-community/eslint-utils", "@typescript-eslint/scope-manager", @@ -287,8 +285,8 @@ "typescript" ] }, - "@typescript-eslint/visitor-keys@8.35.1": { - "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", + "@typescript-eslint/visitor-keys@8.39.0": { + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", "dependencies": [ "@typescript-eslint/types", "eslint-visitor-keys@4.2.1" @@ -407,7 +405,7 @@ "escape-string-regexp@4.0.0": { "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, - "eslint-plugin-simple-import-sort@12.1.1_eslint@9.30.1": { + "eslint-plugin-simple-import-sort@12.1.1_eslint@9.33.0": { "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", "dependencies": [ "eslint" @@ -426,14 +424,14 @@ "eslint-visitor-keys@4.2.1": { "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" }, - "eslint@9.30.1": { - "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", + "eslint@9.33.0": { + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dependencies": [ "@eslint-community/eslint-utils", "@eslint-community/regexpp", "@eslint/config-array", "@eslint/config-helpers", - "@eslint/core@0.14.0", + "@eslint/core", "@eslint/eslintrc", "@eslint/js", "@eslint/plugin-kit", @@ -493,8 +491,8 @@ "esutils@2.0.3": { "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, - "fake-indexeddb@6.0.1": { - "integrity": "sha512-He2AjQGHe46svIFq5+L2Nx/eHDTI1oKgoevBP+TthnjymXiKkeJQ3+ITeWey99Y5+2OaPFbI1qEsx/5RsGtWnQ==" + "fake-indexeddb@6.1.0": { + "integrity": "sha512-gOzajWIhEug/CQHUIxigKT9Zilh5/I6WvUBez6/UdUtT/YVEHM9r572Os8wfvhp7TkmgBtRNdqSM7YoCXWMzZg==" }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" @@ -681,8 +679,8 @@ "whatwg-url" ] }, - "mongodb@6.17.0": { - "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", + "mongodb@6.18.0": { + "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", "dependencies": [ "@mongodb-js/saslprep", "bson", @@ -818,7 +816,7 @@ "punycode" ] }, - "ts-api-utils@2.1.0_typescript@5.8.3": { + "ts-api-utils@2.1.0_typescript@5.9.2": { "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", "dependencies": [ "typescript" @@ -836,18 +834,19 @@ "type-fest@3.13.1": { "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==" }, - "typescript-eslint@8.35.1_eslint@9.30.1_typescript@5.8.3_@typescript-eslint+parser@8.35.1__eslint@9.30.1__typescript@5.8.3": { - "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==", + "typescript-eslint@8.39.0_eslint@9.33.0_typescript@5.9.2_@typescript-eslint+parser@8.39.0__eslint@9.33.0__typescript@5.9.2": { + "integrity": "sha512-lH8FvtdtzcHJCkMOKnN73LIn6SLTpoojgJqDAxPm1jCR14eWSGPX8ul/gggBdPMk/d5+u9V854vTYQ8T5jF/1Q==", "dependencies": [ "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", + "@typescript-eslint/typescript-estree", "@typescript-eslint/utils", "eslint", "typescript" ] }, - "typescript@5.8.3": { - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "typescript@5.9.2": { + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "bin": true }, "uri-js@4.4.1": { @@ -879,27 +878,27 @@ "yocto-queue@0.1.0": { "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, - "zod@3.25.75": { - "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==" + "zod@4.0.17": { + "integrity": "sha512-1PHjlYRevNxxdy2JZ8JcNAw7rX8V9P1AKkP+x/xZfxB0K5FYfuV+Ug6P/6NVSR2jHQ+FzDDoDHS04nYUsOIyLQ==" } }, "workspace": { "packageJson": { "dependencies": [ - "npm:@jsr/std__assert@1.0.13", - "npm:@jsr/std__async@1.0.13", - "npm:@jsr/std__testing@1.0.14", + "npm:@jsr/std__assert@1", + "npm:@jsr/std__async@1", + "npm:@jsr/std__testing@1", "npm:@jsr/valkyr__testcontainers@2", "npm:@valkyr/db@1.0.1", - "npm:eslint-plugin-simple-import-sort@12.1.1", - "npm:eslint@9.30.1", - "npm:fake-indexeddb@6.0.1", + "npm:eslint-plugin-simple-import-sort@12", + "npm:eslint@9", + "npm:fake-indexeddb@6", "npm:mongodb@6", "npm:nanoid@5", "npm:postgres@3", - "npm:prettier@3.6.2", - "npm:typescript-eslint@8.35.1", - "npm:zod@3.25" + "npm:prettier@3", + "npm:typescript-eslint@8", + "npm:zod@4" ] } } diff --git a/libraries/aggregate-factory.ts b/libraries/aggregate-factory.ts deleted file mode 100644 index f0b170b..0000000 --- a/libraries/aggregate-factory.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { AggregateRootClass } from "./aggregate.ts"; -import { EventFactory } from "./event-factory.ts"; -import { AnyEventStore } from "./event-store.ts"; - -/** - * Indexes a list of event factories for use with aggregates and event stores - * when generating or accessing event functionality. - * - * @example - * - * ```ts - * import { AggregateRoot, AggregateFactory } from "@valkyr/event-store"; - * import z from "zod"; - * - * class User extends AggregateRoot {} - * - * const factory = new AggregateFactory([User]); - * - * export type Aggregates = typeof factory.$aggregates; - * ``` - */ -export class AggregateFactory< - const TEventFactory extends EventFactory = EventFactory, - const TAggregates extends AggregateRootClass[] = AggregateRootClass[], -> { - /** - * Optimized aggregate lookup index. - */ - readonly #index = new Map(); - - aggregates: TAggregates; - - /** - * Inferred type of the aggregates registered with the factory. - */ - declare readonly $aggregates: TAggregates; - - /** - * Instantiate a new AggregateFactory with given list of supported aggregates. - * - * @param aggregates - Aggregates to register with the factory. - */ - constructor(aggregates: TAggregates) { - this.aggregates = aggregates; - for (const aggregate of aggregates) { - this.#index.set(aggregate.name, aggregate); - } - } - - /** - * Attaches the given store to all the aggregates registered with this instance. - * - * If the factory is passed into multiple event stores, the aggregates will be - * overriden by the last execution. Its recommended to create individual instances - * for each list of aggregates. - * - * @param store - Event store to attach to the aggregates. - */ - withStore(store: AnyEventStore): this { - for (const aggregate of this.aggregates) { - aggregate.$store = store; - } - return this; - } - - /** - * Get a registered aggregate from the factory. - * - * @param name - Aggregate to retrieve. - */ - get(name: TName): Extract { - return this.#index.get(name) as Extract; - } -} diff --git a/libraries/aggregate.ts b/libraries/aggregate.ts index 9c7526f..a203a82 100644 --- a/libraries/aggregate.ts +++ b/libraries/aggregate.ts @@ -1,6 +1,8 @@ import type { AnyEventStore, EventsInsertSettings } from "../libraries/event-store.ts"; import type { Unknown } from "../types/common.ts"; +import { AggregateSnapshotViolation, AggregateStreamViolation } from "./errors.ts"; import { EventFactory } from "./event-factory.ts"; +import { makeAggregateReducer } from "./reducer.ts"; /** * Represents an aggregate root in an event-sourced system. @@ -18,39 +20,43 @@ export abstract class AggregateRoot { */ static readonly name: string; + readonly #store: AnyEventStore; + /** - * Event store to transact against. + * Primary unique identifier for the stream the aggregate belongs to. */ - protected static _store?: AnyEventStore; + #stream?: string; /** * List of pending records to push to the parent event store. */ #pending: TEventFactory["$events"][number]["$record"][] = []; + /** + * Instantiate a new AggregateRoot with a given event store instance. + * + * @param store - Store this aggregate instance acts against. + */ + constructor(store: AnyEventStore) { + this.#store = store; + } + // ------------------------------------------------------------------------- // Accessors // ------------------------------------------------------------------------- - static get $store(): AnyEventStore { - if (this._store === undefined) { - throw new Error(`Aggregate Root > Failed to retrieve store for '${this.name}', no store has been attached.`); + set id(value: string) { + if (this.#stream !== undefined) { + throw new AggregateStreamViolation(this.constructor.name); } - return this._store; + this.#stream = value; } - static set $store(store: AnyEventStore) { - // if (this._store !== undefined) { - // throw new Error(`Aggregate '${this.constructor.name}' already has store assigned`); - // } - this._store = store; - } - - /** - * Get store instance attached to the static aggregate. - */ - get $store(): AnyEventStore { - return (this.constructor as any).$store; + get id() { + if (this.#stream === undefined) { + this.#stream = crypto.randomUUID(); + } + return this.#stream; } /** @@ -74,9 +80,10 @@ export abstract class AggregateRoot { */ static from>( this: TAggregateRoot, + store: AnyEventStore, snapshot?: Unknown, ): InstanceType { - const instance = new (this as any)(); + const instance = new (this as any)(store); if (snapshot !== undefined) { Object.assign(instance, snapshot); } @@ -109,7 +116,7 @@ export abstract class AggregateRoot { push( record: { type: TType } & Extract["$payload"], ): this { - const pending = this.$store.event(record); + const pending = this.#store.event(record); this.#pending.push(pending); this.with(pending); return this; @@ -136,13 +143,25 @@ export abstract class AggregateRoot { if (this.isDirty === false) { return this; } - await this.$store.pushManyEvents(this.#pending, settings); + await this.#store.pushManyEvents(this.#pending, settings); if (flush === true) { this.flush(); } return this; } + async snapshot() { + const stream = this.#stream; + if (stream === undefined) { + throw new AggregateSnapshotViolation((this.constructor as typeof AggregateRoot).name); + } + await this.#store.createSnapshot({ + name: this.constructor.name, + stream, + reducer: makeAggregateReducer(this.#store, this.constructor as typeof AggregateRoot), + }); + } + /** * Removes all events from the aggregate #pending list. */ diff --git a/libraries/errors.ts b/libraries/errors.ts index 47a1842..b23cf0d 100644 --- a/libraries/errors.ts +++ b/libraries/errors.ts @@ -1,3 +1,42 @@ +/* + |-------------------------------------------------------------------------------- + | Aggregate Errors + |-------------------------------------------------------------------------------- + */ + +/** + * Error thrown when stream assignment on the aggregate has already been set. + * + * @property name - Name of the aggregate throwing the error. + */ +export class AggregateStreamViolation extends Error { + readonly type = "AggregateStreamAlreadySet"; + + constructor(name: string) { + super(`EventStore Error: Aggregate '${name}' already has a stream assigned, overriding not supported.`); + } +} + +/** + * Error thrown when attempting to snapshot an aggregate without a resolved + * stream. + * + * @property name - Name of the aggregate throwing the error. + */ +export class AggregateSnapshotViolation extends Error { + readonly type = "AggregateSnapshotViolation"; + + constructor(name: string) { + super(`EventStore Error: Aggregate '${name}' has no stream assigned, snapshot generation cannot be executed.`); + } +} + +/* + |-------------------------------------------------------------------------------- + | Event Errors + |-------------------------------------------------------------------------------- + */ + /** * Error thrown when an expected event is missing from the event store. * @@ -14,12 +53,6 @@ export class EventMissingError extends Error { } } -/* - |-------------------------------------------------------------------------------- - | Event Errors - |-------------------------------------------------------------------------------- - */ - /** * Error thrown when an event fails validation checks. * diff --git a/libraries/event-store.ts b/libraries/event-store.ts index ebb4716..63f884d 100644 --- a/libraries/event-store.ts +++ b/libraries/event-store.ts @@ -35,8 +35,7 @@ import { EventStoreAdapter } from "../types/adapter.ts"; import type { Unknown } from "../types/common.ts"; import type { EventReadOptions, ReduceQuery } from "../types/query.ts"; -import type { AggregateRoot } from "./aggregate.ts"; -import { AggregateFactory } from "./aggregate-factory.ts"; +import { AggregateRootClass } from "./aggregate.ts"; import { EventInsertionError, EventMissingError, EventValidationError } from "./errors.ts"; import { EventStatus } from "./event.ts"; import { EventFactory } from "./event-factory.ts"; @@ -53,24 +52,21 @@ import { makeAggregateReducer, makeReducer } from "./reducer.ts"; * Provides a common interface to interact with a event storage solution. Its built * on an adapter pattern to allow for multiple different storage drivers. */ -export class EventStore< - TEventFactory extends EventFactory, - TAggregateFactory extends AggregateFactory, - TEventStoreAdapter extends EventStoreAdapter, -> { +export class EventStore> { + readonly uuid: string; + readonly #adapter: TEventStoreAdapter; readonly #events: TEventFactory; - readonly #aggregates: TAggregateFactory; readonly #snapshot: "manual" | "auto"; readonly #hooks: EventStoreHooks; declare readonly $events: TEventFactory["$events"]; declare readonly $records: TEventFactory["$events"][number]["$record"][]; - constructor(config: EventStoreConfig) { + constructor(config: EventStoreConfig) { + this.uuid = crypto.randomUUID(); this.#adapter = config.adapter; this.#events = config.events; - this.#aggregates = config.aggregates.withStore(this); this.#snapshot = config.snapshot ?? "manual"; this.#hooks = config.hooks ?? {}; } @@ -113,55 +109,82 @@ export class EventStore< |-------------------------------------------------------------------------------- */ - /** - * Get aggregate uninstantiated class. - * - * @param name - Aggregate name to retrieve. - */ - aggregate( - name: TName, - ): Extract { - return this.#aggregates.get(name) as Extract; - } + readonly aggregate = { + /** + * Takes a list of aggregates and commits any pending events to the event store. + * Events are committed in order so its important to ensure that the aggregates + * are placed in the correct index position of the array. + * + * This method allows for a simpler way to commit many events over many + * aggregates in a single transaction. Ensuring atomicity of a larger group + * of events. + * + * @param aggregates - Aggregates to push events from. + * @param settings - Event settings which can modify insertion behavior. + */ + push: async ( + aggregates: InstanceType>[], + settings?: EventsInsertSettings, + ): Promise => { + const events: this["$events"][number]["$record"][] = []; + for (const aggregate of aggregates) { + events.push(...aggregate.toPending()); + } + await this.pushManyEvents(events, settings); + for (const aggregate of aggregates) { + aggregate.flush(); + } + }, - /** - * Takes in an aggregate and commits any pending events to the event store. - * - * @param aggregate - Aggregate to push events from. - * @param settings - Event settings which can modify insertion behavior. - */ - async pushAggregate( - aggregate: InstanceType, - settings?: EventsInsertSettings, - ): Promise { - await aggregate.save(settings); - } + /** + * Get a new aggregate instance by a given stream. + * + * @param name - Aggregate to instantiate. + * @param stream - Stream to retrieve snapshot from. + */ + getByStream: async >( + aggregate: TAggregate, + stream: string, + ): Promise | undefined> => { + const reducer = makeAggregateReducer(this, aggregate); + const snapshot = await this.reduce({ name: aggregate.name, stream, reducer }); + if (snapshot === undefined) { + return undefined; + } + return aggregate.from(this, snapshot as Unknown); + }, - /** - * Takes a list of aggregates and commits any pending events to the event store. - * Events are committed in order so its important to ensure that the aggregates - * are placed in the correct index position of the array. - * - * This method allows for a simpler way to commit many events over many - * aggregates in a single transaction. Ensuring atomicity of a larger group - * of events. - * - * @param aggregates - Aggregates to push events from. - * @param settings - Event settings which can modify insertion behavior. - */ - async pushManyAggregates( - aggregates: InstanceType[], - settings?: EventsInsertSettings, - ): Promise { - const events: this["$events"][number]["$record"][] = []; - for (const aggregate of aggregates) { - events.push(...aggregate.toPending()); - } - await this.pushManyEvents(events, settings); - for (const aggregate of aggregates) { - aggregate.flush(); - } - } + /** + * Get a new aggregate instance by a given relation. + * + * @param name - Aggregate to instantiate. + * @param relation - Relation to retrieve snapshot from. + */ + getByRelation: async >( + aggregate: TAggregate, + relation: string, + ): Promise | undefined> => { + const reducer = makeAggregateReducer(this, aggregate); + const snapshot = await this.reduce({ name: aggregate.name, relation, reducer }); + if (snapshot === undefined) { + return undefined; + } + return aggregate.from(this, snapshot as Unknown); + }, + + /** + * Instantiate a new aggreate. + * + * @param aggregate - Aggregate to instantiate. + * @param snapshot - Optional snapshot to instantiate aggregate with. + */ + from: >( + aggregate: TAggregate, + snapshot?: Unknown, + ): InstanceType => { + return aggregate.from(this, snapshot); + }, + }; /* |-------------------------------------------------------------------------------- @@ -341,43 +364,6 @@ export class EventStore< return makeReducer(foldFn, stateFn); } - /** - * Make a new event reducer based on the events registered with the event store. - * - * @param aggregate - Aggregate class to create instance from. - * - * @example - * ```ts - * class Foo extends AggregateRoot { - * name: string = ""; - * - * static #reducer = makeAggregateReducer(Foo); - * - * static async getById(fooId: string): Promise { - * return eventStore.reduce({ - * name: "foo", - * stream: "stream-id", - * reducer: this.#reducer, - * }); - * } - * - * with(event) { - * switch (event.type) { - * case "FooCreated": { - * this.name = event.data.name; - * break; - * } - * } - * } - * }); - * ``` - */ - makeAggregateReducer>( - aggregate: TAggregateRoot, - ): Reducer> { - return makeAggregateReducer(aggregate); - } - /** * Reduce events in the given stream to a entity state. * @@ -540,14 +526,9 @@ export class EventStore< |-------------------------------------------------------------------------------- */ -type EventStoreConfig< - TEventFactory extends EventFactory, - TAggregateFactory extends AggregateFactory, - TEventStoreAdapter extends EventStoreAdapter, -> = { +type EventStoreConfig> = { adapter: TEventStoreAdapter; events: TEventFactory; - aggregates: TAggregateFactory; snapshot?: "manual" | "auto"; hooks?: EventStoreHooks; }; @@ -588,4 +569,4 @@ export type EventStoreHooks = Partial<{ onError(error: unknown): Promise; }>; -export type AnyEventStore = EventStore; +export type AnyEventStore = EventStore; diff --git a/libraries/event.ts b/libraries/event.ts index ab51dc7..7068116 100644 --- a/libraries/event.ts +++ b/libraries/event.ts @@ -1,4 +1,4 @@ -import z, { ZodType } from "zod/v4"; +import z, { ZodType } from "zod"; import { EventValidationError } from "./errors.ts"; import { makeId } from "./nanoid.ts"; diff --git a/libraries/reducer.ts b/libraries/reducer.ts index 462f229..4716b2c 100644 --- a/libraries/reducer.ts +++ b/libraries/reducer.ts @@ -1,6 +1,7 @@ import type { AggregateRoot } from "../libraries/aggregate.ts"; import type { Unknown } from "../types/common.ts"; import { EventFactory } from "./event-factory.ts"; +import type { AnyEventStore } from "./event-store.ts"; /** * Make an event reducer that produces a aggregate instance from resolved @@ -11,13 +12,13 @@ import { EventFactory } from "./event-factory.ts"; export function makeAggregateReducer< TEventFactory extends EventFactory, TAggregateRoot extends typeof AggregateRoot, ->(aggregate: TAggregateRoot): Reducer> { +>(store: AnyEventStore, aggregate: TAggregateRoot): Reducer> { return { from(snapshot: Unknown) { - return aggregate.from(snapshot); + return aggregate.from(store, snapshot); }, reduce(events: TEventFactory["$events"][number]["$record"][], snapshot?: Unknown) { - const instance = aggregate.from(snapshot); + const instance = aggregate.from(store, snapshot); for (const event of events) { instance.with(event); } diff --git a/libraries/zod.ts b/libraries/zod.ts index 86665d6..61d8681 100644 --- a/libraries/zod.ts +++ b/libraries/zod.ts @@ -1,4 +1,4 @@ -import { ZodError } from "zod/v4"; +import { ZodError } from "zod"; export function toPrettyErrorLines(error: ZodError, padding: number = 0): string[] { const lines: string[] = []; diff --git a/mod.ts b/mod.ts index e437b65..81d15d3 100644 --- a/mod.ts +++ b/mod.ts @@ -1,5 +1,4 @@ export * from "./libraries/aggregate.ts"; -export * from "./libraries/aggregate-factory.ts"; export * from "./libraries/errors.ts"; export * from "./libraries/event.ts"; export * from "./libraries/event-factory.ts"; diff --git a/package.json b/package.json index b373bc6..96046f3 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,17 @@ "mongodb": "6", "nanoid": "5", "postgres": "3", - "zod": "3.25" + "zod": "4" }, "devDependencies": { - "@std/async": "npm:@jsr/std__async@1.0.13", - "@std/assert": "npm:@jsr/std__assert@1.0.13", - "@std/testing": "npm:@jsr/std__testing@1.0.14", + "@std/async": "npm:@jsr/std__async@1", + "@std/assert": "npm:@jsr/std__assert@1", + "@std/testing": "npm:@jsr/std__testing@1", "@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2", - "eslint": "9.30.1", - "eslint-plugin-simple-import-sort": "12.1.1", - "fake-indexeddb": "6.0.1", - "prettier": "3.6.2", - "typescript-eslint": "8.35.1" + "eslint": "9", + "eslint-plugin-simple-import-sort": "12", + "fake-indexeddb": "6", + "prettier": "3", + "typescript-eslint": "8" } } diff --git a/tests/browser-iddb.test.ts b/tests/browser-iddb.test.ts index da18990..2462c82 100644 --- a/tests/browser-iddb.test.ts +++ b/tests/browser-iddb.test.ts @@ -6,8 +6,7 @@ import { afterAll, describe } from "@std/testing/bdd"; import { BrowserAdapter } from "../adapters/browser/adapter.ts"; import { EventStore, EventStoreHooks } from "../libraries/event-store.ts"; import { Projector } from "../libraries/projector.ts"; -import { aggregates } from "./mocks/aggregates.ts"; -import { events, EventStoreFactory } from "./mocks/events.ts"; +import { Events, events } from "./mocks/events.ts"; import testAddEvent from "./store/add-event.ts"; import testCreateSnapshot from "./store/create-snapshot.ts"; import testMakeAggregateReducer from "./store/make-aggregate-reducer.ts"; @@ -18,7 +17,7 @@ import testPushManyAggregates from "./store/push-many-aggregates.ts"; import testReduce from "./store/reduce.ts"; import testReplayEvents from "./store/replay-events.ts"; -const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); +const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); /* |-------------------------------------------------------------------------------- @@ -44,7 +43,6 @@ describe("Adapter > Browser (IndexedDb)", () => { testReplayEvents(eventStoreFn); testReduce(eventStoreFn); testOnceProjection(eventStoreFn); - testPushAggregate(eventStoreFn); testPushManyAggregates(eventStoreFn); }); @@ -55,15 +53,14 @@ describe("Adapter > Browser (IndexedDb)", () => { |-------------------------------------------------------------------------------- */ -function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { +function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { const store = new EventStore({ adapter: new BrowserAdapter("indexeddb"), events, - aggregates, hooks, }); - const projector = new Projector(); + const projector = new Projector(); if (hooks.onEventsInserted === undefined) { store.onEventsInserted(async (records, { batch }) => { diff --git a/tests/browser-memory.test.ts b/tests/browser-memory.test.ts index 5696688..9f874a1 100644 --- a/tests/browser-memory.test.ts +++ b/tests/browser-memory.test.ts @@ -5,8 +5,7 @@ import { describe } from "@std/testing/bdd"; import { BrowserAdapter } from "../adapters/browser/adapter.ts"; import { EventStore, EventStoreHooks } from "../libraries/event-store.ts"; import { Projector } from "../libraries/projector.ts"; -import { aggregates } from "./mocks/aggregates.ts"; -import { events, EventStoreFactory } from "./mocks/events.ts"; +import { Events, events } from "./mocks/events.ts"; import testAddEvent from "./store/add-event.ts"; import testCreateSnapshot from "./store/create-snapshot.ts"; import testMakeAggregateReducer from "./store/make-aggregate-reducer.ts"; @@ -17,7 +16,7 @@ import testPushManyAggregates from "./store/push-many-aggregates.ts"; import testReduce from "./store/reduce.ts"; import testReplayEvents from "./store/replay-events.ts"; -const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); +const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); /* |-------------------------------------------------------------------------------- @@ -33,7 +32,6 @@ describe("Adapter > Browser (memory)", () => { testReplayEvents(eventStoreFn); testReduce(eventStoreFn); testOnceProjection(eventStoreFn); - testPushAggregate(eventStoreFn); testPushManyAggregates(eventStoreFn); }); @@ -44,15 +42,14 @@ describe("Adapter > Browser (memory)", () => { |-------------------------------------------------------------------------------- */ -function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { +function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { const store = new EventStore({ adapter: new BrowserAdapter("memorydb"), events, - aggregates, hooks, }); - const projector = new Projector(); + const projector = new Projector(); if (hooks.onEventsInserted === undefined) { store.onEventsInserted(async (records, { batch }) => { diff --git a/tests/mocks/aggregates.ts b/tests/mocks/aggregates.ts index 05cfd2d..64f7bd7 100644 --- a/tests/mocks/aggregates.ts +++ b/tests/mocks/aggregates.ts @@ -1,13 +1,9 @@ import { AggregateRoot } from "../../libraries/aggregate.ts"; -import { AggregateFactory } from "../../libraries/aggregate-factory.ts"; -import { makeId } from "../../libraries/nanoid.ts"; -import { makeAggregateReducer } from "../../libraries/reducer.ts"; -import { EventStoreFactory } from "./events.ts"; +import { Events } from "./events.ts"; -export class User extends AggregateRoot { +export class User extends AggregateRoot { static override readonly name = "user"; - id: string = ""; name: Name = { given: "", family: "", @@ -19,40 +15,12 @@ export class User extends AggregateRoot { count: 0, }; - // ------------------------------------------------------------------------- - // Factories - // ------------------------------------------------------------------------- - - static reducer = makeAggregateReducer(User); - - static create(name: Name, email: string): User { - const user = new User(); - user.push({ - type: "user:created", - stream: makeId(), - data: { name, email }, - meta: { auditor: "foo" }, - }); - return user; - } - - static async getById(userId: string): Promise { - return this.$store.reduce({ name: "user", stream: userId, reducer: this.reducer }); - } - // ------------------------------------------------------------------------- // Reducer // ------------------------------------------------------------------------- - with(event: EventStoreFactory["$events"][number]["$record"]) { + with(event: Events["$events"][number]["$record"]) { switch (event.type) { - case "user:created": { - this.id = event.stream; - this.name.given = event.data.name?.given ?? ""; - this.name.family = event.data.name?.family ?? ""; - this.email = event.data.email; - break; - } case "user:name:given-set": { this.name.given = event.data; break; @@ -107,11 +75,6 @@ export class User extends AggregateRoot { }); } - async snapshot(): Promise { - await this.$store.createSnapshot({ name: "user", stream: this.id, reducer: User.reducer }); - return this; - } - // ------------------------------------------------------------------------- // Helpers // ------------------------------------------------------------------------- @@ -121,8 +84,6 @@ export class User extends AggregateRoot { } } -export const aggregates = new AggregateFactory([User]); - type Name = { given: string; family: string; diff --git a/tests/mocks/events.ts b/tests/mocks/events.ts index 67b4d9b..4964e6c 100644 --- a/tests/mocks/events.ts +++ b/tests/mocks/events.ts @@ -1,4 +1,4 @@ -import z from "zod/v4"; +import z from "zod"; import { event } from "../../libraries/event.ts"; import { EventFactory } from "../../libraries/event-factory.ts"; @@ -32,4 +32,4 @@ export const events = new EventFactory([ event.type("post:removed").meta(auditor), ]); -export type EventStoreFactory = typeof events; +export type Events = typeof events; diff --git a/tests/mocks/user-posts-reducer.ts b/tests/mocks/user-posts-reducer.ts index 6e93f14..98d57bf 100644 --- a/tests/mocks/user-posts-reducer.ts +++ b/tests/mocks/user-posts-reducer.ts @@ -1,7 +1,7 @@ import { makeReducer } from "../../libraries/reducer.ts"; -import { EventStoreFactory } from "./events.ts"; +import { Events } from "./events.ts"; -export const userPostReducer = makeReducer( +export const userPostReducer = makeReducer( (state, event) => { switch (event.type) { case "post:created": { diff --git a/tests/mocks/user-reducer.ts b/tests/mocks/user-reducer.ts index 6641c34..b82b0e8 100644 --- a/tests/mocks/user-reducer.ts +++ b/tests/mocks/user-reducer.ts @@ -1,7 +1,7 @@ import { makeReducer } from "../../libraries/reducer.ts"; -import { EventStoreFactory } from "./events.ts"; +import { Events } from "./events.ts"; -export const userReducer = makeReducer( +export const userReducer = makeReducer( (state, event) => { switch (event.type) { case "user:created": { diff --git a/tests/mongodb.test.ts b/tests/mongodb.test.ts index 0b1cac3..03bcb1c 100644 --- a/tests/mongodb.test.ts +++ b/tests/mongodb.test.ts @@ -4,8 +4,7 @@ import { MongoTestContainer } from "@valkyr/testcontainers/mongodb"; import { MongoAdapter, register } from "../adapters/mongo/adapter.ts"; import { EventStore, type EventStoreHooks } from "../libraries/event-store.ts"; import { Projector } from "../libraries/projector.ts"; -import { aggregates } from "./mocks/aggregates.ts"; -import { events, type EventStoreFactory } from "./mocks/events.ts"; +import { type Events, events } from "./mocks/events.ts"; import testAddEvent from "./store/add-event.ts"; import testAddManyEvents from "./store/add-many-events.ts"; import testCreateSnapshot from "./store/create-snapshot.ts"; @@ -23,7 +22,7 @@ const DB_NAME = "sandbox"; const container = await MongoTestContainer.start(); -const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); +const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(options); /* |-------------------------------------------------------------------------------- @@ -66,7 +65,6 @@ describe("Adapter > MongoDb", () => { testReplayEvents(eventStoreFn); testReduce(eventStoreFn); testOnceProjection(eventStoreFn); - testPushAggregate(eventStoreFn); testPushManyAggregates(eventStoreFn); }); @@ -77,15 +75,14 @@ describe("Adapter > MongoDb", () => { |-------------------------------------------------------------------------------- */ -async function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { +async function getEventStore({ hooks = {} }: { hooks?: EventStoreHooks }) { const store = new EventStore({ adapter: new MongoAdapter(() => container.client, DB_NAME), events, - aggregates, hooks, }); - const projector = new Projector(); + const projector = new Projector(); if (hooks.onEventsInserted === undefined) { store.onEventsInserted(async (records, { batch }) => { diff --git a/tests/postgres.test.ts b/tests/postgres.test.ts index b2ee8f7..d1f4c3b 100644 --- a/tests/postgres.test.ts +++ b/tests/postgres.test.ts @@ -6,8 +6,7 @@ import { PostgresAdapter } from "../adapters/postgres/adapter.ts"; import type { PostgresConnection } from "../adapters/postgres/connection.ts"; import { EventStore, type EventStoreHooks } from "../libraries/event-store.ts"; import { Projector } from "../libraries/projector.ts"; -import { aggregates } from "./mocks/aggregates.ts"; -import { events, EventStoreFactory } from "./mocks/events.ts"; +import { Events, events } from "./mocks/events.ts"; import testAddEvent from "./store/add-event.ts"; import testAddManyEvents from "./store/add-many-events.ts"; import testCreateSnapshot from "./store/create-snapshot.ts"; @@ -26,8 +25,7 @@ const DB_NAME = "sandbox"; const container = await PostgresTestContainer.start("postgres:17"); const sql = postgres(container.url(DB_NAME)); -const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => - getEventStore(sql, options); +const eventStoreFn = async (options: { hooks?: EventStoreHooks } = {}) => getEventStore(sql, options); /* |-------------------------------------------------------------------------------- @@ -103,7 +101,6 @@ describe("Adapter > Postgres", () => { testReplayEvents(eventStoreFn); testReduce(eventStoreFn); testOnceProjection(eventStoreFn); - testPushAggregate(eventStoreFn); testPushManyAggregates(eventStoreFn); }); @@ -114,18 +111,14 @@ describe("Adapter > Postgres", () => { |-------------------------------------------------------------------------------- */ -async function getEventStore( - connection: PostgresConnection, - { hooks = {} }: { hooks?: EventStoreHooks }, -) { +async function getEventStore(connection: PostgresConnection, { hooks = {} }: { hooks?: EventStoreHooks }) { const store = new EventStore({ adapter: new PostgresAdapter(connection, { schema: "event_store" }), events, - aggregates, hooks, }); - const projector = new Projector(); + const projector = new Projector(); if (hooks.onEventsInserted === undefined) { store.onEventsInserted(async (records, { batch }) => { diff --git a/tests/store/add-event.ts b/tests/store/add-event.ts index d3fd26d..c79d035 100644 --- a/tests/store/add-event.ts +++ b/tests/store/add-event.ts @@ -3,10 +3,10 @@ import { it } from "@std/testing/bdd"; import { EventInsertionError, EventValidationError } from "../../libraries/errors.ts"; import { makeId } from "../../libraries/nanoid.ts"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".addEvent", (getEventStore) => { +export default describe(".addEvent", (getEventStore) => { it("should throw a 'EventValidationError' when providing bad event data", async () => { const { store } = await getEventStore(); diff --git a/tests/store/add-many-events.ts b/tests/store/add-many-events.ts index cbb3e1f..8508ff2 100644 --- a/tests/store/add-many-events.ts +++ b/tests/store/add-many-events.ts @@ -3,11 +3,11 @@ import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; import { EventValidationError } from "../../mod.ts"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".addSequence", (getEventStore) => { +export default describe(".addSequence", (getEventStore) => { it("should insert 'user:created', 'user:name:given-set', and 'user:email-set' in a sequence of events", async () => { const { store } = await getEventStore(); const stream = nanoid(); diff --git a/tests/store/create-snapshot.ts b/tests/store/create-snapshot.ts index 2228235..76e75c6 100644 --- a/tests/store/create-snapshot.ts +++ b/tests/store/create-snapshot.ts @@ -2,11 +2,11 @@ import { assertEquals, assertNotEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".createSnapshot", (getEventStore) => { +export default describe(".createSnapshot", (getEventStore) => { it("should create a new snapshot", async () => { const { store } = await getEventStore(); const stream = nanoid(); diff --git a/tests/store/make-aggregate-reducer.ts b/tests/store/make-aggregate-reducer.ts index 4c81a28..8ed32ad 100644 --- a/tests/store/make-aggregate-reducer.ts +++ b/tests/store/make-aggregate-reducer.ts @@ -1,24 +1,26 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import { User } from "../mocks/aggregates.ts"; +import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".makeAggregateReducer", (getEventStore) => { +export default describe(".makeAggregateReducer", (getEventStore) => { it("should reduce a user", async () => { const { store } = await getEventStore(); - const userA = await store - .aggregate("user") - .create({ given: "John", family: "Doe" }, "john.doe@fixture.none") + const userA = await store.aggregate + .from(User) .setGivenName("Jane") + .setFamilyName("Doe") + .setEmail("john.doe@fixture.none", "auditor") .save(); await userA.snapshot(); await userA.setFamilyName("Smith").setEmail("jane.smith@fixture.none", "system").save(); - const userB = await store.aggregate("user").getById(userA.id); + const userB = await store.aggregate.getByStream(User, userA.id); if (userB === undefined) { throw new Error("Expected user to exist"); } diff --git a/tests/store/make-event.ts b/tests/store/make-event.ts index df997ab..98a6a5b 100644 --- a/tests/store/make-event.ts +++ b/tests/store/make-event.ts @@ -2,10 +2,10 @@ import { assertEquals, assertLess } from "@std/assert"; import { it } from "@std/testing/bdd"; import { RelationPayload } from "../../types/adapter.ts"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".makeEvent", (getEventStore) => { +export default describe(".makeEvent", (getEventStore) => { it("should make and performantly batch insert a list of events directly", async () => { const { store } = await getEventStore(); diff --git a/tests/store/make-reducer.ts b/tests/store/make-reducer.ts index ab13922..0b0c616 100644 --- a/tests/store/make-reducer.ts +++ b/tests/store/make-reducer.ts @@ -3,12 +3,12 @@ import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; import { makeId } from "../../libraries/nanoid.ts"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { userPostReducer } from "../mocks/user-posts-reducer.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".makeReducer", (getEventStore) => { +export default describe(".makeReducer", (getEventStore) => { it("should create a 'user' reducer and only reduce filtered events", async () => { const { store } = await getEventStore(); diff --git a/tests/store/once-projection.ts b/tests/store/once-projection.ts index 6becf11..c36484d 100644 --- a/tests/store/once-projection.ts +++ b/tests/store/once-projection.ts @@ -2,10 +2,10 @@ import { assertEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; import { makeId } from "../../libraries/nanoid.ts"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; -export default describe("projector.once", (getEventStore) => { +export default describe("projector.once", (getEventStore) => { it("should handle successfull projection", async () => { const { store, projector } = await getEventStore(); diff --git a/tests/store/providers/relations.ts b/tests/store/providers/relations.ts index 4fe6f00..522d9bd 100644 --- a/tests/store/providers/relations.ts +++ b/tests/store/providers/relations.ts @@ -2,10 +2,10 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; -import type { EventStoreFactory } from "../../mocks/events.ts"; +import type { Events } from "../../mocks/events.ts"; import { describe } from "../../utilities/describe.ts"; -export default describe("relations", (getEventStore) => { +export default describe("relations", (getEventStore) => { it("should create a new relation", async () => { const { store } = await getEventStore(); diff --git a/tests/store/push-aggregate.ts b/tests/store/push-aggregate.ts index 7ff8cd3..7546b5b 100644 --- a/tests/store/push-aggregate.ts +++ b/tests/store/push-aggregate.ts @@ -1,36 +1,38 @@ import { assertEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import { User } from "../mocks/aggregates.ts"; +import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".pushAggregate", (getEventStore) => { +export default describe(".pushAggregate", (getEventStore) => { it("should successfully commit pending aggregate events to the event store", async () => { const { store } = await getEventStore(); - const user = store - .aggregate("user") - .create({ given: "Jane", family: "Doe" }, "jane.doe@fixture.none") + const user = store.aggregate + .from(User) + .setGivenName("Jane") + .setFamilyName("Doe") + .setEmail("jane.doe@fixture.none", "admin") .setGivenName("John") .setEmail("john.doe@fixture.none", "admin"); - assertEquals(user.toPending().length, 3); + assertEquals(user.toPending().length, 5); - await store.pushAggregate(user); + await user.save(); assertEquals(user.toPending().length, 0); const records = await store.getEventsByStreams([user.id]); - assertEquals(records.length, 3); + assertEquals(records.length, 5); - assertObjectMatch(records[0], { - stream: user.id, - data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" }, - }); - assertObjectMatch(records[1], { stream: user.id, data: "John" }); - assertObjectMatch(records[2], { stream: user.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[0], { stream: user.id, data: "Jane" }); + assertObjectMatch(records[1], { stream: user.id, data: "Doe" }); + assertObjectMatch(records[2], { stream: user.id, data: "jane.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[3], { stream: user.id, data: "John" }); + assertObjectMatch(records[4], { stream: user.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } }); const state = await store.reduce({ name: "user", stream: user.id, reducer: userReducer }); diff --git a/tests/store/push-many-aggregates.ts b/tests/store/push-many-aggregates.ts index ad2c184..9059ef4 100644 --- a/tests/store/push-many-aggregates.ts +++ b/tests/store/push-many-aggregates.ts @@ -1,50 +1,53 @@ import { assertEquals, assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import { User } from "../mocks/aggregates.ts"; +import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".pushManyAggregates", (getEventStore) => { +export default describe(".pushManyAggregates", (getEventStore) => { it("should successfully commit pending aggregates events to the event store", async () => { const { store } = await getEventStore(); - const userA = store - .aggregate("user") - .create({ given: "Jane", family: "Doe" }, "jane.doe@fixture.none") + const userA = store.aggregate + .from(User) + .setGivenName("Jane") + .setFamilyName("Doe") + .setEmail("jane.doe@fixture.none", "admin") .setGivenName("John") .setEmail("john.doe@fixture.none", "admin"); - const userB = store - .aggregate("user") - .create({ given: "Peter", family: "Doe" }, "peter.doe@fixture.none") + const userB = store.aggregate + .from(User) + .setGivenName("Peter") + .setFamilyName("Doe") + .setEmail("peter.doe@fixture.none", "admin") .setGivenName("Barry") .setEmail("barry.doe@fixture.none", "admin"); - assertEquals(userA.toPending().length, 3); - assertEquals(userB.toPending().length, 3); + assertEquals(userA.toPending().length, 5); + assertEquals(userB.toPending().length, 5); - await store.pushManyAggregates([userA, userB]); + await store.aggregate.push([userA, userB]); assertEquals(userA.toPending().length, 0); assertEquals(userB.toPending().length, 0); const records = await store.getEventsByStreams([userA.id, userB.id]); - assertEquals(records.length, 6); + assertEquals(records.length, 10); - assertObjectMatch(records[0], { - stream: userA.id, - data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" }, - }); - assertObjectMatch(records[1], { stream: userA.id, data: "John" }); - assertObjectMatch(records[2], { stream: userA.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } }); - assertObjectMatch(records[3], { - stream: userB.id, - data: { name: { given: "Peter", family: "Doe" }, email: "peter.doe@fixture.none" }, - }); - assertObjectMatch(records[4], { stream: userB.id, data: "Barry" }); - assertObjectMatch(records[5], { stream: userB.id, data: "barry.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[0], { stream: userA.id, data: "Jane" }); + assertObjectMatch(records[1], { stream: userA.id, data: "Doe" }); + assertObjectMatch(records[2], { stream: userA.id, data: "jane.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[3], { stream: userA.id, data: "John" }); + assertObjectMatch(records[4], { stream: userA.id, data: "john.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[5], { stream: userB.id, data: "Peter" }); + assertObjectMatch(records[6], { stream: userB.id, data: "Doe" }); + assertObjectMatch(records[7], { stream: userB.id, data: "peter.doe@fixture.none", meta: { auditor: "admin" } }); + assertObjectMatch(records[8], { stream: userB.id, data: "Barry" }); + assertObjectMatch(records[9], { stream: userB.id, data: "barry.doe@fixture.none", meta: { auditor: "admin" } }); const stateA = await store.reduce({ name: "user", stream: userA.id, reducer: userReducer }); const stateB = await store.reduce({ name: "user", stream: userB.id, reducer: userReducer }); diff --git a/tests/store/reduce.ts b/tests/store/reduce.ts index a7efc74..bf48b04 100644 --- a/tests/store/reduce.ts +++ b/tests/store/reduce.ts @@ -2,11 +2,11 @@ import { assertEquals } from "@std/assert"; import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; -import type { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { userReducer } from "../mocks/user-reducer.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".reduce", (getEventStore) => { +export default describe(".reduce", (getEventStore) => { it("should return reduced state", async () => { const { store } = await getEventStore(); const stream = nanoid(); diff --git a/tests/store/replay-events.ts b/tests/store/replay-events.ts index 39eec73..333cc1e 100644 --- a/tests/store/replay-events.ts +++ b/tests/store/replay-events.ts @@ -2,10 +2,10 @@ import { assertObjectMatch } from "@std/assert"; import { it } from "@std/testing/bdd"; import { nanoid } from "nanoid"; -import { EventStoreFactory } from "../mocks/events.ts"; +import type { Events } from "../mocks/events.ts"; import { describe } from "../utilities/describe.ts"; -export default describe(".replayEvents", (getEventStore) => { +export default describe(".replayEvents", (getEventStore) => { it("should replay events", async () => { const { store, projector } = await getEventStore(); const stream = nanoid(); diff --git a/tests/utilities/describe.ts b/tests/utilities/describe.ts index 3ed27f2..f1aa44a 100644 --- a/tests/utilities/describe.ts +++ b/tests/utilities/describe.ts @@ -14,6 +14,6 @@ export function describe( type EventStoreFn = (options?: { hooks?: EventStoreHooks; }) => Promise<{ - store: EventStore; + store: EventStore; projector: Projector; }>;