diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 64a9cd2..033e0a4 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -43,4 +43,4 @@ jobs: - uses: actions/checkout@v4 - name: Publish package - run: npx jsr publish \ No newline at end of file + run: npx jsr publish diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb101be..0fc0bd5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,4 +35,4 @@ jobs: - run: deno install - run: deno task test - - run: deno task test:publish \ No newline at end of file + - run: deno task test:publish diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b5afb12 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,14 @@ +{ + "trailingComma": "all", + "tabWidth": 2, + "printWidth": 120, + "singleQuote": false, + "overrides": [ + { + "files": "*.ts", + "options": { + "parser": "typescript" + } + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index b2bb4f6..395c084 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,6 @@ { "deno.enable": true, + "deno.lint": false, "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit" diff --git a/README.md b/README.md index 414d818..5b5a7c0 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ Once we have defined our configs and printed our events we create a new event st for `sqlite`, `postgres`, and `valkyr/db` which all works the same way. So for this example we will use the `sqlite` store. - - Browser _(TODO)_ - - Mongo _(TODO)_ - - [Postgres](./adapters/postgres) +- Browser _(TODO)_ +- Mongo _(TODO)_ +- [Postgres](./adapters/postgres) ### Reducers @@ -92,26 +92,33 @@ import { makeReducer } from "@valkyr/event-store"; import type { EventRecord } from "./generated/events.ts"; -const reducer = makeReducer<{ - name: string; - email: string; -}, EventRecord>((state, event) => { - switch (event.type) { - case "user:created": { - state.name = `${event.data.name.given} ${event.data.name.family}`; - state.email = event.data.email; - break; +const reducer = makeReducer< + { + name: string; + email: string; + }, + EventRecord +>( + (state, event) => { + switch (event.type) { + case "user:created": { + state.name = `${event.data.name.given} ${event.data.name.family}`; + state.email = event.data.email; + break; + } + case "user:email-set": { + state.email = event.data.email; + break; + } } - case "user:email-set": { - state.email = event.data.email; - break; - } - } - return state; -}, "user", () => ({ - name: "", - email: "", -})); + return state; + }, + "user", + () => ({ + name: "", + email: "", + }), +); ``` ### Aggreates @@ -172,7 +179,7 @@ export class User extends AggregateRoot { type Name = { given: string; family: string; -} +}; ``` ### Projectors @@ -194,14 +201,13 @@ projector.on("user:created", async (record) => { ### Hydration in Event Processing -When handling events in a distributed system or during event replay operations, it is important to differentiate between **new events** and **rehydrated events**. +When handling events in a distributed system or during event replay operations, it is important to differentiate between **new events** and **rehydrated events**. -- **New Events (`hydrate: false`)**: These events are being processed for the first time. They will trigger all projection handlers, including `.once()`, `.on()`, and `.all()`. -- **Rehydrated Events (`hydrate: true`)**: These events are being replayed, either as part of a stream synchronization, system recovery, or reprocessing in a distributed environment. They **will not trigger** `.once()` handlers to avoid redundant side effects but will still be processed by `.on()` and `.all()` handlers where applicable. +- **New Events (`hydrate: false`)**: These events are being processed for the first time. They will trigger all projection handlers, including `.once()`, `.on()`, and `.all()`. +- **Rehydrated Events (`hydrate: true`)**: These events are being replayed, either as part of a stream synchronization, system recovery, or reprocessing in a distributed environment. They **will not trigger** `.once()` handlers to avoid redundant side effects but will still be processed by `.on()` and `.all()` handlers where applicable. This mechanism ensures that critical one-time operations (such as sending emails or initiating external API calls) are **not repeated** unnecessarily while still allowing stateful projections to update their read models correctly. - #### `.once("user:created", (event) => Promise)` This handler tells the projection that an event is only ever processed when the event is originating directly from the diff --git a/adapters/mongo/collections/events.ts b/adapters/mongo/collections/events.ts index 98b6239..be5aa82 100644 --- a/adapters/mongo/collections/events.ts +++ b/adapters/mongo/collections/events.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import z from "zod/v4"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/collections/mod.ts b/adapters/mongo/collections/mod.ts index 8b96aa1..28e8136 100644 --- a/adapters/mongo/collections/mod.ts +++ b/adapters/mongo/collections/mod.ts @@ -3,8 +3,4 @@ import { registrar as events } from "./events.ts"; import { registrar as relations } from "./relations.ts"; import { registrar as snapshots } from "./snapshots.ts"; -export const registrars: CollectionRegistrar[] = [ - events, - relations, - snapshots, -]; +export const registrars: CollectionRegistrar[] = [events, relations, snapshots]; diff --git a/adapters/mongo/collections/relations.ts b/adapters/mongo/collections/relations.ts index 494eba7..4fba38d 100644 --- a/adapters/mongo/collections/relations.ts +++ b/adapters/mongo/collections/relations.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import z from "zod/v4"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/collections/snapshots.ts b/adapters/mongo/collections/snapshots.ts index 55d12a8..af6c372 100644 --- a/adapters/mongo/collections/snapshots.ts +++ b/adapters/mongo/collections/snapshots.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import z from "zod/v4"; import type { CollectionRegistrar } from "../types.ts"; diff --git a/adapters/mongo/providers/events.ts b/adapters/mongo/providers/events.ts index ab9fed2..c665558 100644 --- a/adapters/mongo/providers/events.ts +++ b/adapters/mongo/providers/events.ts @@ -70,7 +70,10 @@ export class MongoEventsProvider implements EventsProvider { * @param options - Read options for modifying the result. */ async getByStreams(streams: string[], options: EventReadOptions = {}): Promise { - return (await this.#withReadOptions(this.collection.find({ stream: { $in: streams }, ...this.#withFilters(options) }), options) + return (await this.#withReadOptions( + this.collection.find({ stream: { $in: streams }, ...this.#withFilters(options) }), + options, + ) .sort({ created: 1 }) .toArray() .then(toParsedRecords(schema))) as EventRecord[]; diff --git a/adapters/mongo/utilities.ts b/adapters/mongo/utilities.ts index 9a4835a..4743970 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"; +import type { z, ZodObject } from "zod/v4"; /** * Take a list of records and run it through the given zod parser. This @@ -9,7 +9,9 @@ import type { z, ZodObject } from "zod"; * * @param parser - Zod parser to run the documents through. */ -export function toParsedRecords(parser: TSchema): (documents: WithId[]) => z.infer[] { +export function toParsedRecords( + parser: TSchema, +): (documents: WithId[]) => z.infer[] { return parser.array().parse; } @@ -21,7 +23,9 @@ export function toParsedRecords(parser: TSchema): (do * * @param parser - Zod parser to run the document through. */ -export function toParsedRecord(parser: TSchema): (document: WithId | null) => z.infer | undefined { +export function toParsedRecord( + parser: TSchema, +): (document: WithId | null) => z.infer | undefined { return function (document) { if (document === null) { return undefined; diff --git a/adapters/postgres/connection.ts b/adapters/postgres/connection.ts index 16de9af..4537135 100644 --- a/adapters/postgres/connection.ts +++ b/adapters/postgres/connection.ts @@ -1,6 +1,10 @@ import type { Options, Sql } from "postgres"; -export type PostgresConnection = [PostgresConnectionUrl, Options?] | [Options] | Sql | PostgresConnectionFactory; +export type PostgresConnection = + | [PostgresConnectionUrl, Options?] + | [Options] + | Sql + | PostgresConnectionFactory; type PostgresConnectionUrl = `postgres://${string}:${string}@${string}:${number}/${string}`; diff --git a/adapters/postgres/providers/event.ts b/adapters/postgres/providers/event.ts index 29a0693..274f49e 100644 --- a/adapters/postgres/providers/event.ts +++ b/adapters/postgres/providers/event.ts @@ -76,7 +76,10 @@ export class PostgresEventsProvider implements EventsProvider { * @param stream - Stream to fetch events for. * @param options - Read options for modifying the result. */ - async getByStream(stream: string, { filter, cursor, direction, limit }: EventReadOptions = {}): Promise { + async getByStream( + stream: string, + { filter, cursor, direction, limit }: EventReadOptions = {}, + ): Promise { return this.db.sql` SELECT * FROM ${this.table} WHERE @@ -94,7 +97,10 @@ export class PostgresEventsProvider implements EventsProvider { * @param streams - Stream to get events for. * @param options - Read options for modifying the result. */ - async getByStreams(streams: string[], { filter, cursor, direction, limit }: EventReadOptions = {}): Promise { + async getByStreams( + streams: string[], + { filter, cursor, direction, limit }: EventReadOptions = {}, + ): Promise { return this.db.sql` SELECT * FROM ${this.table} WHERE @@ -112,7 +118,9 @@ export class PostgresEventsProvider implements EventsProvider { * @param id - Event id. */ async getById(id: string): Promise { - return this.db.sql`SELECT * FROM ${this.table} WHERE id = ${id}`.then(this.#fromDriver).then(([record]) => record); + return this.db.sql`SELECT * FROM ${this.table} WHERE id = ${id}` + .then(this.#fromDriver) + .then(([record]) => record); } /** diff --git a/adapters/postgres/providers/relations.ts b/adapters/postgres/providers/relations.ts index f85c1f4..e827c1c 100644 --- a/adapters/postgres/providers/relations.ts +++ b/adapters/postgres/providers/relations.ts @@ -35,9 +35,11 @@ export class PostgresRelationsProvider implements RelationsProvider { * @param stream - Stream to add to the key. */ async insert(key: string, stream: string): Promise { - await this.db.sql`INSERT INTO ${this.table} (key, stream) VALUES (${key}, ${stream}) ON CONFLICT DO NOTHING`.catch((error) => { - throw new Error(`EventStore > 'relations.insert' failed with postgres error: ${error.message}`); - }); + await this.db.sql`INSERT INTO ${this.table} (key, stream) VALUES (${key}, ${stream}) ON CONFLICT DO NOTHING`.catch( + (error) => { + throw new Error(`EventStore > 'relations.insert' failed with postgres error: ${error.message}`); + }, + ); } /** @@ -107,7 +109,9 @@ export class PostgresRelationsProvider implements RelationsProvider { await this.db.sql .begin(async (sql) => { for (let i = 0; i < relations.length; i += batchSize) { - const conditions = relations.slice(i, i + batchSize).map(({ key, stream }) => `(key = '${key}' AND stream = '${stream}')`); + const conditions = relations + .slice(i, i + batchSize) + .map(({ key, stream }) => `(key = '${key}' AND stream = '${stream}')`); await sql`DELETE FROM ${this.table} WHERE ${this.db.sql.unsafe(conditions.join(" OR "))}`; } }) diff --git a/deno.json b/deno.json index dfd3e6a..cae28dd 100644 --- a/deno.json +++ b/deno.json @@ -8,16 +8,12 @@ "./postgres": "./adapters/postgres/adapter.ts" }, "publish": { - "exclude": [ - ".github", - ".vscode", - ".gitignore", - "tests" - ] + "exclude": [".github", ".vscode", ".gitignore", "tests"] }, "tasks": { "check": "deno check ./mod.ts", "lint": "npx eslint -c eslint.config.mjs .", + "fmt": "npx prettier --write .", "test": "deno test --allow-all", "test:publish": "deno publish --dry-run", "ncu": "npx ncu -u -p npm" diff --git a/deno.lock b/deno.lock index a3eaa41..156ea64 100644 --- a/deno.lock +++ b/deno.lock @@ -1,26 +1,24 @@ { - "version": "4", + "version": "5", "specifiers": { - "npm:@jsr/std__assert@1.0.12": "1.0.12", "npm:@jsr/std__assert@1.0.13": "1.0.13", - "npm:@jsr/std__async@1.0.12": "1.0.12", - "npm:@jsr/std__testing@1.0.11": "1.0.11", - "npm:@jsr/valkyr__testcontainers@2.0.0": "2.0.0", - "npm:@valkyr/db@1.0.1": "1.0.1", - "npm:eslint-plugin-simple-import-sort@12.1.1": "12.1.1_eslint@9.25.1", - "npm:eslint@9.25.1": "9.25.1", - "npm:fake-indexeddb@6.0.0": "6.0.0", - "npm:mongodb@6.15.0": "6.15.0", + "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.0.1": "2.0.1", + "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.17.0": "6.17.0", "npm:nanoid@5.1.5": "5.1.5", - "npm:postgres@3.4.5": "3.4.5", - "npm:postgres@^3.4.5": "3.4.5", - "npm:prettier@3.5.3": "3.5.3", - "npm:typescript-eslint@8.31.0": "8.31.0_eslint@9.25.1_typescript@5.8.3_@typescript-eslint+parser@8.31.0__eslint@9.25.1__typescript@5.8.3", - "npm:zod@next": "4.0.0-beta.20250420T053007" + "npm:postgres@3.4.7": "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.75": "3.25.75" }, "npm": { - "@eslint-community/eslint-utils@4.6.1_eslint@9.25.1": { - "integrity": "sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==", + "@eslint-community/eslint-utils@4.7.0_eslint@9.30.1": { + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", "dependencies": [ "eslint", "eslint-visitor-keys@3.4.3" @@ -29,19 +27,25 @@ "@eslint-community/regexpp@4.12.1": { "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" }, - "@eslint/config-array@0.20.0": { - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "@eslint/config-array@0.21.0": { + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dependencies": [ "@eslint/object-schema", "debug", "minimatch@3.1.2" ] }, - "@eslint/config-helpers@0.2.1": { - "integrity": "sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==" + "@eslint/config-helpers@0.3.0": { + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==" }, - "@eslint/core@0.13.0": { - "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "@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==", "dependencies": [ "@types/json-schema" ] @@ -53,23 +57,23 @@ "debug", "espree", "globals", - "ignore", + "ignore@5.3.2", "import-fresh", "js-yaml", "minimatch@3.1.2", "strip-json-comments" ] }, - "@eslint/js@9.25.1": { - "integrity": "sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==" + "@eslint/js@9.30.1": { + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==" }, "@eslint/object-schema@2.1.6": { "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==" }, - "@eslint/plugin-kit@0.2.8": { - "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", + "@eslint/plugin-kit@0.3.3": { + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dependencies": [ - "@eslint/core", + "@eslint/core@0.15.1", "levn" ] }, @@ -89,65 +93,72 @@ "@humanwhocodes/retry@0.3.1": { "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==" }, - "@humanwhocodes/retry@0.4.2": { - "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==" - }, - "@jsr/std__assert@1.0.12": { - "integrity": "sha512-9pmgjJhuljZCmLlbvsRV6aLT5+YCmhX/yIjaWYav7R7Vup2DOLAgpUOs4JkzRbwn7fdKYrwHT8+DjqPr7Ti8mg==", - "dependencies": [ - "@jsr/std__internal" - ] + "@humanwhocodes/retry@0.4.3": { + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==" }, "@jsr/std__assert@1.0.13": { "integrity": "sha512-rZ44REoi2/p+gqu8OfkcNeaTOSiG1kD6v8gyA0YjkXsOkDsiGw9g8h7JuGC/OD7GgOVgTEY+9Cih49Y18rkrCQ==", "dependencies": [ "@jsr/std__internal" - ] + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__assert/1.0.13.tgz" }, - "@jsr/std__async@1.0.12": { - "integrity": "sha512-NUaSOcwMetVeVkIqet2Ammy2A5YxG8ViFxryBbTaC4h7l/cgAkU59U3zF58ek4Y8HZ0Nx5De7qBptPfp62kcgw==" + "@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__data-structures@1.0.6": { - "integrity": "sha512-Ejc8mHLuoYxXLu2zPquvqijdgQ19OV+1DdVDrLc/Cg+tiuGh4Dq2FSnLiPINh4lO1AJ3XcZcYPx38RxdsZcCOg==" + "@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__fs@1.0.16": { - "integrity": "sha512-xnqp8XqEFN+ttkERg9GG+AxyipSd+rfCquLPviF5ZSwN6oCV1TM0ZNoKHXNk/EJAsz28YjF4sfgdJt8XwTV2UQ==", + "@jsr/std__fs@1.0.19": { + "integrity": "sha512-TEjyE8g+46jPlu7dJHLrwc8NMGl8zfG+JjWxyNQyDbxP0RtqZ4JmYZfR9vy4RWYWJQbLpw6Kbt2n+K/2zAO/JA==", "dependencies": [ + "@jsr/std__internal", "@jsr/std__path" - ] + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__fs/1.0.19.tgz" }, - "@jsr/std__internal@1.0.6": { - "integrity": "sha512-1NLtCx9XAL44nt56gzmRSCgXjIthHVzK62fTkJdq8/XsP7eN9a21AZDpc0EGJ/cgvmmOB52UGh46OuKrrY7eVg==" + "@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__net@1.0.4": { - "integrity": "sha512-KJGU8ZpQ70sMW2Zk+wU3wFUkggS9lTLfRFBygnV9VaK8KI+1ggiqtB06rH4a14CNRGM9y46Mn/ZCbQUd4Q45Jg==" + "integrity": "sha512-KJGU8ZpQ70sMW2Zk+wU3wFUkggS9lTLfRFBygnV9VaK8KI+1ggiqtB06rH4a14CNRGM9y46Mn/ZCbQUd4Q45Jg==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__net/1.0.4.tgz" }, - "@jsr/std__path@1.0.8": { - "integrity": "sha512-eNBGlh/8ZVkMxtFH4bwIzlAeKoHYk5in4wrBZhi20zMdOiuX4QozP4+19mIXBT2lzHDjhuVLyECbhFeR304iDg==" - }, - "@jsr/std__testing@1.0.11": { - "integrity": "sha512-pqQDYtIsaDf+x4NHQ+WiixRJ8DfhgFQRdlHWWssFAzIYwleR+VHLTNlgsgg+AH3mIIR+gTkBmKk21hTkM/WbMQ==", + "@jsr/std__path@1.1.1": { + "integrity": "sha512-+x5LgcNUSpMzOZIRmFSjqrMTCxHlgXjWzK8ZFr7lwgHfWZxoVXeis3MFQlkR5mN5uQ61Y1P30Li1PU0yx9uluA==", "dependencies": [ - "@jsr/std__assert@1.0.12", + "@jsr/std__internal" + ], + "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==", + "dependencies": [ + "@jsr/std__assert", "@jsr/std__async", "@jsr/std__data-structures", "@jsr/std__fs", "@jsr/std__internal", "@jsr/std__path" - ] + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/std__testing/1.0.14.tgz" }, - "@jsr/valkyr__testcontainers@2.0.0": { - "integrity": "sha512-aK78hRoVyQm3M0aFucuUV7Ghfx4295fJ6Q3fSjtJizYnu10VuktKfcIh5xHhOVAISk1Zh0y3SYGIiuZiKr57vw==", + "@jsr/valkyr__testcontainers@2.0.1": { + "integrity": "sha512-HInqMkCDj1ICrcz+Led/3jyLa70mwncxdlly8v/5WepuPW3gszKftq5U2jbjy2THOYUP7ibBK2o0recg7qhvcw==", "dependencies": [ "@jsr/std__async", "@jsr/std__fs", "@jsr/std__net", "mongodb", "postgres" - ] + ], + "tarball": "https://npm.jsr.io/~/11/@jsr/valkyr__testcontainers/2.0.1.tgz" }, - "@mongodb-js/saslprep@1.2.2": { - "integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==", + "@mongodb-js/saslprep@1.3.0": { + "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", "dependencies": [ "sparse-bitfield" ] @@ -169,8 +180,8 @@ "fastq" ] }, - "@types/estree@1.0.7": { - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, "@types/json-schema@7.0.15": { "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" @@ -184,8 +195,8 @@ "@types/webidl-conversions" ] }, - "@typescript-eslint/eslint-plugin@8.31.0_@typescript-eslint+parser@8.31.0__eslint@9.25.1__typescript@5.8.3_eslint@9.25.1_typescript@5.8.3": { - "integrity": "sha512-evaQJZ/J/S4wisevDvC1KFZkPzRetH8kYZbkgcTRyql3mcKsf+ZFDV1BVWUGTCAW5pQHoqn5gK5b8kn7ou9aFQ==", + "@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==", "dependencies": [ "@eslint-community/regexpp", "@typescript-eslint/parser", @@ -195,14 +206,14 @@ "@typescript-eslint/visitor-keys", "eslint", "graphemer", - "ignore", + "ignore@7.0.5", "natural-compare", "ts-api-utils", "typescript" ] }, - "@typescript-eslint/parser@8.31.0_eslint@9.25.1_typescript@5.8.3": { - "integrity": "sha512-67kYYShjBR0jNI5vsf/c3WG4u+zDnCTHTPqVMQguffaWWFs7artgwKmfwdifl+r6XyM5LYLas/dInj2T0SgJyw==", + "@typescript-eslint/parser@8.35.1_eslint@9.30.1_typescript@5.8.3": { + "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==", "dependencies": [ "@typescript-eslint/scope-manager", "@typescript-eslint/types", @@ -213,15 +224,30 @@ "typescript" ] }, - "@typescript-eslint/scope-manager@8.31.0": { - "integrity": "sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==", + "@typescript-eslint/project-service@8.35.1_typescript@5.8.3": { + "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==", + "dependencies": [ + "@typescript-eslint/tsconfig-utils", + "@typescript-eslint/types", + "debug", + "typescript" + ] + }, + "@typescript-eslint/scope-manager@8.35.1": { + "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", "dependencies": [ "@typescript-eslint/types", "@typescript-eslint/visitor-keys" ] }, - "@typescript-eslint/type-utils@8.31.0_eslint@9.25.1_typescript@5.8.3": { - "integrity": "sha512-DJ1N1GdjI7IS7uRlzJuEDCgDQix3ZVYVtgeWEyhyn4iaoitpMBX6Ndd488mXSx0xah/cONAkEaYyylDyAeHMHg==", + "@typescript-eslint/tsconfig-utils@8.35.1_typescript@5.8.3": { + "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==", + "dependencies": [ + "typescript" + ] + }, + "@typescript-eslint/type-utils@8.35.1_eslint@9.30.1_typescript@5.8.3": { + "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==", "dependencies": [ "@typescript-eslint/typescript-estree", "@typescript-eslint/utils", @@ -231,12 +257,14 @@ "typescript" ] }, - "@typescript-eslint/types@8.31.0": { - "integrity": "sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==" + "@typescript-eslint/types@8.35.1": { + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==" }, - "@typescript-eslint/typescript-estree@8.31.0_typescript@5.8.3": { - "integrity": "sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==", + "@typescript-eslint/typescript-estree@8.35.1_typescript@5.8.3": { + "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", "dependencies": [ + "@typescript-eslint/project-service", + "@typescript-eslint/tsconfig-utils", "@typescript-eslint/types", "@typescript-eslint/visitor-keys", "debug", @@ -248,8 +276,8 @@ "typescript" ] }, - "@typescript-eslint/utils@8.31.0_eslint@9.25.1_typescript@5.8.3": { - "integrity": "sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==", + "@typescript-eslint/utils@8.35.1_eslint@9.30.1_typescript@5.8.3": { + "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", "dependencies": [ "@eslint-community/eslint-utils", "@typescript-eslint/scope-manager", @@ -259,11 +287,11 @@ "typescript" ] }, - "@typescript-eslint/visitor-keys@8.31.0": { - "integrity": "sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==", + "@typescript-eslint/visitor-keys@8.35.1": { + "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", "dependencies": [ "@typescript-eslint/types", - "eslint-visitor-keys@4.2.0" + "eslint-visitor-keys@4.2.1" ] }, "@valkyr/db@1.0.1": { @@ -278,17 +306,15 @@ "rxjs" ] }, - "@zod/core@0.8.1": { - "integrity": "sha512-djj8hPhxIHcG8ptxITaw/Bout5HJZ9NyRbKr95Eilqwt9R0kvITwUQGDU+n+MVdsBIka5KwztmZSLti22F+P0A==" - }, - "acorn-jsx@5.3.2_acorn@8.14.1": { + "acorn-jsx@5.3.2_acorn@8.15.0": { "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dependencies": [ "acorn" ] }, - "acorn@8.14.1": { - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==" + "acorn@8.15.0": { + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "bin": true }, "ajv@6.12.6": { "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", @@ -311,15 +337,15 @@ "balanced-match@1.0.2": { "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, - "brace-expansion@1.1.11": { - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "brace-expansion@1.1.12": { + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dependencies": [ "balanced-match", "concat-map" ] }, - "brace-expansion@2.0.1": { - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "brace-expansion@2.0.2": { + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dependencies": [ "balanced-match" ] @@ -330,8 +356,8 @@ "fill-range" ] }, - "bson@6.10.3": { - "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==" + "bson@6.10.4": { + "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==" }, "callsites@3.1.0": { "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" @@ -363,8 +389,8 @@ "which" ] }, - "debug@4.4.0": { - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "debug@4.4.1": { + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dependencies": [ "ms" ] @@ -381,14 +407,14 @@ "escape-string-regexp@4.0.0": { "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, - "eslint-plugin-simple-import-sort@12.1.1_eslint@9.25.1": { + "eslint-plugin-simple-import-sort@12.1.1_eslint@9.30.1": { "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", "dependencies": [ "eslint" ] }, - "eslint-scope@8.3.0": { - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "eslint-scope@8.4.0": { + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dependencies": [ "esrecurse", "estraverse" @@ -397,23 +423,23 @@ "eslint-visitor-keys@3.4.3": { "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" }, - "eslint-visitor-keys@4.2.0": { - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==" + "eslint-visitor-keys@4.2.1": { + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==" }, - "eslint@9.25.1": { - "integrity": "sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==", + "eslint@9.30.1": { + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", "dependencies": [ "@eslint-community/eslint-utils", "@eslint-community/regexpp", "@eslint/config-array", "@eslint/config-helpers", - "@eslint/core", + "@eslint/core@0.14.0", "@eslint/eslintrc", "@eslint/js", "@eslint/plugin-kit", "@humanfs/node", "@humanwhocodes/module-importer", - "@humanwhocodes/retry@0.4.2", + "@humanwhocodes/retry@0.4.3", "@types/estree", "@types/json-schema", "ajv", @@ -422,7 +448,7 @@ "debug", "escape-string-regexp", "eslint-scope", - "eslint-visitor-keys@4.2.0", + "eslint-visitor-keys@4.2.1", "espree", "esquery", "esutils", @@ -430,7 +456,7 @@ "file-entry-cache", "find-up", "glob-parent@6.0.2", - "ignore", + "ignore@5.3.2", "imurmurhash", "is-glob", "json-stable-stringify-without-jsonify", @@ -438,14 +464,15 @@ "minimatch@3.1.2", "natural-compare", "optionator" - ] + ], + "bin": true }, - "espree@10.3.0_acorn@8.14.1": { - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "espree@10.4.0_acorn@8.15.0": { + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dependencies": [ "acorn", "acorn-jsx", - "eslint-visitor-keys@4.2.0" + "eslint-visitor-keys@4.2.1" ] }, "esquery@1.6.0": { @@ -466,8 +493,8 @@ "esutils@2.0.3": { "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, - "fake-indexeddb@6.0.0": { - "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==" + "fake-indexeddb@6.0.1": { + "integrity": "sha512-He2AjQGHe46svIFq5+L2Nx/eHDTI1oKgoevBP+TthnjymXiKkeJQ3+ITeWey99Y5+2OaPFbI1qEsx/5RsGtWnQ==" }, "fast-deep-equal@3.1.3": { "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" @@ -553,6 +580,9 @@ "ignore@5.3.2": { "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" }, + "ignore@7.0.5": { + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==" + }, "import-fresh@3.3.1": { "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dependencies": [ @@ -582,7 +612,8 @@ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": [ "argparse" - ] + ], + "bin": true }, "json-buffer@3.0.1": { "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" @@ -634,13 +665,13 @@ "minimatch@3.1.2": { "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": [ - "brace-expansion@1.1.11" + "brace-expansion@1.1.12" ] }, "minimatch@9.0.5": { "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dependencies": [ - "brace-expansion@2.0.1" + "brace-expansion@2.0.2" ] }, "mongodb-connection-string-url@3.0.2": { @@ -650,8 +681,8 @@ "whatwg-url" ] }, - "mongodb@6.15.0": { - "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", + "mongodb@6.17.0": { + "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", "dependencies": [ "@mongodb-js/saslprep", "bson", @@ -662,10 +693,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "nanoid@5.0.2": { - "integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==" + "integrity": "sha512-2ustYUX1R2rL/Br5B/FMhi8d5/QzvkJ912rBYxskcpu0myTHzSZfTr1LAS2Sm7jxRUObRrSBFoyzwAhL49aVSg==", + "bin": true }, "nanoid@5.1.5": { - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==" + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "bin": true }, "natural-compare@1.4.0": { "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" @@ -708,14 +741,15 @@ "picomatch@2.3.1": { "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, - "postgres@3.4.5": { - "integrity": "sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==" + "postgres@3.4.7": { + "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==" }, "prelude-ls@1.2.1": { "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, - "prettier@3.5.3": { - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==" + "prettier@3.6.2": { + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "bin": true }, "punycode@2.3.1": { "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" @@ -744,8 +778,9 @@ "tslib" ] }, - "semver@7.7.1": { - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==" + "semver@7.7.2": { + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": true }, "shebang-command@2.0.0": { "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", @@ -801,8 +836,8 @@ "type-fest@3.13.1": { "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==" }, - "typescript-eslint@8.31.0_eslint@9.25.1_typescript@5.8.3_@typescript-eslint+parser@8.31.0__eslint@9.25.1__typescript@5.8.3": { - "integrity": "sha512-u+93F0sB0An8WEAPtwxVhFby573E8ckdjwUUQUj9QA4v8JAvgtoDdIyYR3XFwFHq2W1KJ1AurwJCO+w+Y1ixyQ==", + "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==", "dependencies": [ "@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", @@ -812,7 +847,8 @@ ] }, "typescript@5.8.3": { - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==" + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "bin": true }, "uri-js@4.4.1": { "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", @@ -834,7 +870,8 @@ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dependencies": [ "isexe" - ] + ], + "bin": true }, "word-wrap@1.2.5": { "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" @@ -842,30 +879,27 @@ "yocto-queue@0.1.0": { "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" }, - "zod@4.0.0-beta.20250420T053007": { - "integrity": "sha512-5pp8Q0PNDaNcUptGiBE9akyioJh3RJpagIxrLtAVMR9IxwcSZiOsJD/1/98CyhItdTlI2H91MfhhLzRlU+fifA==", - "dependencies": [ - "@zod/core" - ] + "zod@3.25.75": { + "integrity": "sha512-OhpzAmVzabPOL6C3A3gpAifqr9MqihV/Msx3gor2b2kviCgcb+HM9SEOpMWwwNp9MRunWnhtAKUoo0AHhjyPPg==" } }, "workspace": { "packageJson": { "dependencies": [ "npm:@jsr/std__assert@1.0.13", - "npm:@jsr/std__async@1.0.12", - "npm:@jsr/std__testing@1.0.11", - "npm:@jsr/valkyr__testcontainers@2.0.0", - "npm:@valkyr/db@1.0.1", + "npm:@jsr/std__async@1.0.13", + "npm:@jsr/std__testing@1.0.14", + "npm:@jsr/valkyr__testcontainers@2.0.1", + "npm:@valkyr/db@^1.0.1", "npm:eslint-plugin-simple-import-sort@12.1.1", - "npm:eslint@9.25.1", - "npm:fake-indexeddb@6.0.0", - "npm:mongodb@6.15.0", + "npm:eslint@9.30.1", + "npm:fake-indexeddb@6.0.1", + "npm:mongodb@6.17.0", "npm:nanoid@5.1.5", - "npm:postgres@3.4.5", - "npm:prettier@3.5.3", - "npm:typescript-eslint@8.31.0", - "npm:zod@next" + "npm:postgres@3.4.7", + "npm:prettier@3.6.2", + "npm:typescript-eslint@8.35.1", + "npm:zod@3.25.75" ] } } diff --git a/eslint.config.mjs b/eslint.config.mjs index ff5356c..503f24c 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -15,16 +15,22 @@ export default [ { files: ["**/*.ts"], rules: { - "@typescript-eslint/ban-ts-comment": ["error", { - "ts-expect-error": "allow-with-description", - minimumDescriptionLength: 10, - }], + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": "allow-with-description", + minimumDescriptionLength: 10, + }, + ], "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unused-vars": ["error", { - argsIgnorePattern: "^_", - varsIgnorePattern: "^_", - }], + "@typescript-eslint/no-unused-vars": [ + "error", + { + argsIgnorePattern: "^_", + varsIgnorePattern: "^_", + }, + ], }, }, ]; diff --git a/libraries/errors.ts b/libraries/errors.ts index 9372bc5..47a1842 100644 --- a/libraries/errors.ts +++ b/libraries/errors.ts @@ -75,7 +75,9 @@ export class HLCForwardJumpError extends Error { readonly timejump: number, readonly tolerance: number, ) { - super(`HLC Violation: Detected a forward time jump of ${timejump}ms, which exceed the allowed tolerance of ${tolerance}ms.`); + super( + `HLC Violation: Detected a forward time jump of ${timejump}ms, which exceed the allowed tolerance of ${tolerance}ms.`, + ); } } @@ -96,7 +98,9 @@ export class HLCClockOffsetError extends Error { readonly offset: number, readonly maxOffset: number, ) { - super(`HLC Violation: Received time is ${offset}ms ahead of the wall time, exceeding the 'maxOffset' limit of ${maxOffset}ms.`); + super( + `HLC Violation: Received time is ${offset}ms ahead of the wall time, exceeding the 'maxOffset' limit of ${maxOffset}ms.`, + ); } } diff --git a/libraries/event-factory.ts b/libraries/event-factory.ts index ee6cff1..cdaaca9 100644 --- a/libraries/event-factory.ts +++ b/libraries/event-factory.ts @@ -47,7 +47,9 @@ export class EventFactory { * * @param type - Event type to retrieve. */ - get(type: TType): Extract { + get( + type: TType, + ): Extract { return this.#index.get(type) as Extract; } } diff --git a/libraries/event-store.ts b/libraries/event-store.ts index 7954d8f..ebb4716 100644 --- a/libraries/event-store.ts +++ b/libraries/event-store.ts @@ -118,7 +118,9 @@ export class EventStore< * * @param name - Aggregate name to retrieve. */ - aggregate(name: TName): Extract { + aggregate( + name: TName, + ): Extract { return this.#aggregates.get(name) as Extract; } @@ -128,7 +130,10 @@ export class EventStore< * @param aggregate - Aggregate to push events from. * @param settings - Event settings which can modify insertion behavior. */ - async pushAggregate(aggregate: InstanceType, settings?: EventsInsertSettings): Promise { + async pushAggregate( + aggregate: InstanceType, + settings?: EventsInsertSettings, + ): Promise { await aggregate.save(settings); } @@ -144,7 +149,10 @@ export class EventStore< * @param aggregates - Aggregates to push events from. * @param settings - Event settings which can modify insertion behavior. */ - async pushManyAggregates(aggregates: InstanceType[], settings?: EventsInsertSettings): Promise { + async pushManyAggregates( + aggregates: InstanceType[], + settings?: EventsInsertSettings, + ): Promise { const events: this["$events"][number]["$record"][] = []; for (const aggregate of aggregates) { events.push(...aggregate.toPending()); @@ -209,7 +217,10 @@ export class EventStore< * @param records - List of event records to insert. * @param settings - Event settings which can modify insertion behavior. */ - async pushManyEvents(records: this["$events"][number]["$record"][], settings: EventsInsertSettings = {}): Promise { + async pushManyEvents( + records: this["$events"][number]["$record"][], + settings: EventsInsertSettings = {}, + ): Promise { const events: this["$events"][number]["$record"][] = []; for (const record of records) { const event = this.#events.get(record.type); @@ -270,7 +281,10 @@ export class EventStore< * @param streams - Streams to retrieve events for. * @param options - Read options to pass to the provider. (Optional) */ - async getEventsByStreams(streams: string[], options?: EventReadOptions): Promise { + async getEventsByStreams( + streams: string[], + options?: EventReadOptions, + ): Promise { return this.events.getByStreams(streams, options); } @@ -280,7 +294,10 @@ export class EventStore< * @param keys - Relational keys to retrieve events for. * @param options - Relational logic options. (Optional) */ - async getEventsByRelations(keys: string[], options?: EventReadOptions): Promise { + async getEventsByRelations( + keys: string[], + options?: EventReadOptions, + ): Promise { const streamIds = await this.relations.getByKeys(keys); if (streamIds.length === 0) { return []; @@ -317,7 +334,10 @@ export class EventStore< * const state = await eventStore.reduce({ name: "foo:reducer", stream: "stream-id", reducer }); * ``` */ - makeReducer(foldFn: ReducerLeftFold, stateFn: ReducerState): Reducer { + makeReducer( + foldFn: ReducerLeftFold, + stateFn: ReducerState, + ): Reducer { return makeReducer(foldFn, stateFn); } @@ -352,7 +372,9 @@ export class EventStore< * }); * ``` */ - makeAggregateReducer>(aggregate: TAggregateRoot): Reducer> { + makeAggregateReducer>( + aggregate: TAggregateRoot, + ): Reducer> { return makeAggregateReducer(aggregate); } @@ -392,7 +414,9 @@ export class EventStore< } const events = ( - stream !== undefined ? await this.getEventsByStreams([id], { ...query, cursor }) : await this.getEventsByRelations([id], { ...query, cursor }) + stream !== undefined + ? await this.getEventsByStreams([id], { ...query, cursor }) + : await this.getEventsByRelations([id], { ...query, cursor }) ).concat(pending); if (events.length === 0) { @@ -430,9 +454,16 @@ export class EventStore< * await eventStore.createSnapshot({ relation: `foo:${foo}:bars`, reducer }); * ``` */ - async createSnapshot({ name, stream, relation, reducer, ...query }: ReduceQuery): Promise { + async createSnapshot({ + name, + stream, + relation, + reducer, + ...query + }: ReduceQuery): Promise { const id = stream ?? relation; - const events = stream !== undefined ? await this.getEventsByStreams([id], query) : await this.getEventsByRelations([id], query); + const events = + stream !== undefined ? await this.getEventsByStreams([id], query) : await this.getEventsByRelations([id], query); if (events.length === 0) { return undefined; } @@ -543,7 +574,10 @@ export type EventStoreHooks = Partial<{ * @param records - List of event records inserted. * @param settings - Event insert settings used. */ - onEventsInserted(records: TEventFactory["$events"][number]["$record"][], settings: EventsInsertSettings): Promise; + onEventsInserted( + records: TEventFactory["$events"][number]["$record"][], + settings: EventsInsertSettings, + ): Promise; /** * Triggered when an unhandled exception is thrown during `.pushEvent` and diff --git a/libraries/event.ts b/libraries/event.ts index 7068116..ab51dc7 100644 --- a/libraries/event.ts +++ b/libraries/event.ts @@ -1,4 +1,4 @@ -import z, { ZodType } from "zod"; +import z, { ZodType } from "zod/v4"; import { EventValidationError } from "./errors.ts"; import { makeId } from "./nanoid.ts"; diff --git a/libraries/hlc.ts b/libraries/hlc.ts index 8e32c51..17792e0 100644 --- a/libraries/hlc.ts +++ b/libraries/hlc.ts @@ -12,9 +12,13 @@ export class HLC { last: Timestamp; - constructor( - { time = getTime, maxOffset = 0, timeUpperBound = 0, toleratedForwardClockJump = 0, last }: Options = {}, - ) { + constructor({ + time = getTime, + maxOffset = 0, + timeUpperBound = 0, + toleratedForwardClockJump = 0, + last, + }: Options = {}) { this.time = time; this.maxTime = timeUpperBound > 0 ? timeUpperBound : Number.MAX_SAFE_INTEGER; this.maxOffset = maxOffset; diff --git a/libraries/projector.ts b/libraries/projector.ts index 6707fe0..315f127 100644 --- a/libraries/projector.ts +++ b/libraries/projector.ts @@ -132,7 +132,10 @@ export class Projector { */ once< TType extends TEventFactory["$events"][number]["$record"]["type"], - TRecord extends TEventFactory["$events"][number]["$record"] = Extract, + TRecord extends TEventFactory["$events"][number]["$record"] = Extract< + TEventFactory["$events"][number]["$record"], + { type: TType } + >, TSuccessData extends Record | void = void, >( type: TType, @@ -175,7 +178,10 @@ export class Projector { */ on< TType extends TEventFactory["$events"][number]["$record"]["type"], - TRecord extends TEventFactory["$events"][number]["$record"] = Extract, + TRecord extends TEventFactory["$events"][number]["$record"] = Extract< + TEventFactory["$events"][number]["$record"], + { type: TType } + >, >(type: TType, handler: ProjectionHandler): Subscription { return this.#subscribe(type, FILTER_CONTINUOUS, handler as any); } @@ -194,7 +200,10 @@ export class Projector { */ all< TType extends TEventFactory["$events"][number]["$record"]["type"], - TRecord extends TEventFactory["$events"][number]["$record"] = Extract, + TRecord extends TEventFactory["$events"][number]["$record"] = Extract< + TEventFactory["$events"][number]["$record"], + { type: TType } + >, >(type: TType, handler: ProjectionHandler): Subscription { return this.#subscribe(type, FILTER_ALL, handler as any); } diff --git a/libraries/reducer.ts b/libraries/reducer.ts index cbc1e49..462f229 100644 --- a/libraries/reducer.ts +++ b/libraries/reducer.ts @@ -8,9 +8,10 @@ import { EventFactory } from "./event-factory.ts"; * * @param aggregate - Aggregate to instantiate and create an instance of. */ -export function makeAggregateReducer>( - aggregate: TAggregateRoot, -): Reducer> { +export function makeAggregateReducer< + TEventFactory extends EventFactory, + TAggregateRoot extends typeof AggregateRoot, +>(aggregate: TAggregateRoot): Reducer> { return { from(snapshot: Unknown) { return aggregate.from(snapshot); @@ -45,7 +46,10 @@ export function makeReducer | AggregateRoot = any> = { +export type Reducer< + TEventFactory extends EventFactory = EventFactory, + TState extends Record | AggregateRoot = any, +> = { /** * Return result directly from a snapshot that does not have any subsequent * events to fold onto a state. @@ -80,10 +84,10 @@ export type Reducer = any, TEventFactory extends EventFactory = EventFactory> = ( - state: TState, - event: TEventFactory["$events"][number]["$record"], -) => TState; +export type ReducerLeftFold< + TState extends Record = any, + TEventFactory extends EventFactory = EventFactory, +> = (state: TState, event: TEventFactory["$events"][number]["$record"]) => TState; export type ReducerState = () => TState; diff --git a/libraries/zod.ts b/libraries/zod.ts index 61d8681..86665d6 100644 --- a/libraries/zod.ts +++ b/libraries/zod.ts @@ -1,4 +1,4 @@ -import { ZodError } from "zod"; +import { ZodError } from "zod/v4"; export function toPrettyErrorLines(error: ZodError, padding: number = 0): string[] { const lines: string[] = []; diff --git a/package.json b/package.json index ff3327d..eb29f01 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,26 @@ { "dependencies": { - "@std/async": "npm:@jsr/std__async@1.0.12", - "@valkyr/db": "1.0.1", - "mongodb": "6.15.0", - "nanoid": "5.1.5", - "postgres": "3.4.5", - "zod": "next" + "nanoid": "5.1.5" + }, + "peerDependencies": { + "@valkyr/db": "^1.0.1", + "mongodb": "^6.0.0", + "postgres": "^3.0.0", + "zod": "^3.25.0" }, "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.11", - "@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2.0.0", - "eslint": "9.25.1", + "@std/testing": "npm:@jsr/std__testing@1.0.14", + "@valkyr/db": "^1.0.1", + "@valkyr/testcontainers": "npm:@jsr/valkyr__testcontainers@2.0.1", + "eslint": "9.30.1", "eslint-plugin-simple-import-sort": "12.1.1", - "fake-indexeddb": "6.0.0", - "prettier": "3.5.3", - "typescript-eslint": "8.31.0" + "fake-indexeddb": "6.0.1", + "mongodb": "6.17.0", + "postgres": "3.4.7", + "prettier": "3.6.2", + "typescript-eslint": "8.35.1", + "zod": "3.25.75" } } diff --git a/tests/mocks/errors.ts b/tests/mocks/errors.ts index 17fabf7..43666b3 100644 --- a/tests/mocks/errors.ts +++ b/tests/mocks/errors.ts @@ -1,5 +1,9 @@ export abstract class ServiceError extends Error { - constructor(message: string, readonly status: number, readonly data?: TData) { + constructor( + message: string, + readonly status: number, + readonly data?: TData, + ) { super(message); } diff --git a/tests/mocks/events.ts b/tests/mocks/events.ts index de25684..67b4d9b 100644 --- a/tests/mocks/events.ts +++ b/tests/mocks/events.ts @@ -1,4 +1,4 @@ -import z from "zod"; +import z from "zod/v4"; import { event } from "../../libraries/event.ts"; import { EventFactory } from "../../libraries/event-factory.ts"; @@ -11,7 +11,10 @@ export const events = new EventFactory([ .data( z.strictObject({ name: z - .union([z.strictObject({ given: z.string(), family: z.string().optional() }), z.strictObject({ given: z.string().optional(), family: z.string() })]) + .union([ + z.strictObject({ given: z.string(), family: z.string().optional() }), + z.strictObject({ given: z.string().optional(), family: z.string() }), + ]) .optional(), email: z.string(), }), diff --git a/tests/mongodb.test.ts b/tests/mongodb.test.ts index 62d35d8..0b1cac3 100644 --- a/tests/mongodb.test.ts +++ b/tests/mongodb.test.ts @@ -2,10 +2,10 @@ import { afterAll, afterEach, beforeAll, describe } from "@std/testing/bdd"; import { MongoTestContainer } from "@valkyr/testcontainers/mongodb"; import { MongoAdapter, register } from "../adapters/mongo/adapter.ts"; -import { EventStore, EventStoreHooks } from "../libraries/event-store.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, type EventStoreFactory } 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"; @@ -38,7 +38,11 @@ beforeAll(async () => { afterEach(async () => { const db = container.client.db(DB_NAME); - await Promise.all([db.collection("events").deleteMany({}), db.collection("relations").deleteMany({}), db.collection("snapshots").deleteMany({})]); + await Promise.all([ + db.collection("events").deleteMany({}), + db.collection("relations").deleteMany({}), + db.collection("snapshots").deleteMany({}), + ]); }); afterAll(async () => { diff --git a/tests/postgres.test.ts b/tests/postgres.test.ts index 1b97b84..b2ee8f7 100644 --- a/tests/postgres.test.ts +++ b/tests/postgres.test.ts @@ -26,7 +26,8 @@ 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); /* |-------------------------------------------------------------------------------- @@ -76,7 +77,9 @@ beforeAll(async () => { }); afterEach(async () => { - await container.client(DB_NAME)`TRUNCATE "event_store"."relations","event_store"."events","event_store"."snapshots" CASCADE`; + await container.client( + DB_NAME, + )`TRUNCATE "event_store"."relations","event_store"."events","event_store"."snapshots" CASCADE`; }); afterAll(async () => { @@ -111,7 +114,10 @@ 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, diff --git a/tests/store/make-aggregate-reducer.ts b/tests/store/make-aggregate-reducer.ts index 8a4e4e1..4c81a28 100644 --- a/tests/store/make-aggregate-reducer.ts +++ b/tests/store/make-aggregate-reducer.ts @@ -8,7 +8,11 @@ export default describe(".makeAggregateReducer", (getEventSto 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").setGivenName("Jane").save(); + const userA = await store + .aggregate("user") + .create({ given: "John", family: "Doe" }, "john.doe@fixture.none") + .setGivenName("Jane") + .save(); await userA.snapshot(); diff --git a/tests/store/make-reducer.ts b/tests/store/make-reducer.ts index 377b513..ab13922 100644 --- a/tests/store/make-reducer.ts +++ b/tests/store/make-reducer.ts @@ -82,7 +82,12 @@ export default describe(".makeReducer", (getEventStore) => { }), ); - const state = await store.reduce({ name: "user", stream: streamA, reducer: userReducer, filter: { types: ["user:created", "user:email-set"] } }); + const state = await store.reduce({ + name: "user", + stream: streamA, + reducer: userReducer, + filter: { types: ["user:created", "user:email-set"] }, + }); assertEquals(state?.name, { given: "John", family: "Doe" }); assertEquals(state?.email, "jane.doe@fixture.none"); @@ -100,10 +105,31 @@ export default describe(".makeReducer", (getEventStore) => { const post2 = makeId(); const post3 = makeId(); - await store.pushEvent(store.event({ stream: post1, type: "post:created", data: { title: "Post #1", body: "Sample #1" }, meta: { auditor } })); - await store.pushEvent(store.event({ stream: post2, type: "post:created", data: { title: "Post #2", body: "Sample #2" }, meta: { auditor } })); + await store.pushEvent( + store.event({ + stream: post1, + type: "post:created", + data: { title: "Post #1", body: "Sample #1" }, + meta: { auditor }, + }), + ); + await store.pushEvent( + store.event({ + stream: post2, + type: "post:created", + data: { title: "Post #2", body: "Sample #2" }, + meta: { auditor }, + }), + ); await store.pushEvent(store.event({ stream: post2, type: "post:removed", meta: { auditor } })); - await store.pushEvent(store.event({ stream: post3, type: "post:created", data: { title: "Post #3", body: "Sample #3" }, meta: { auditor } })); + await store.pushEvent( + store.event({ + stream: post3, + type: "post:created", + data: { title: "Post #3", body: "Sample #3" }, + meta: { auditor }, + }), + ); const events = await store.getEventsByRelations([`user:${auditor}:posts`]); diff --git a/tests/store/push-aggregate.ts b/tests/store/push-aggregate.ts index 97044d0..7ff8cd3 100644 --- a/tests/store/push-aggregate.ts +++ b/tests/store/push-aggregate.ts @@ -25,7 +25,10 @@ export default describe(".pushAggregate", (getEventStore) => assertEquals(records.length, 3); - assertObjectMatch(records[0], { stream: user.id, data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" } }); + 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" } }); diff --git a/tests/store/push-many-aggregates.ts b/tests/store/push-many-aggregates.ts index 68c4cc7..ad2c184 100644 --- a/tests/store/push-many-aggregates.ts +++ b/tests/store/push-many-aggregates.ts @@ -33,10 +33,16 @@ export default describe(".pushManyAggregates", (getEventStore assertEquals(records.length, 6); - assertObjectMatch(records[0], { stream: userA.id, data: { name: { given: "Jane", family: "Doe" }, email: "jane.doe@fixture.none" } }); + 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[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" } }); diff --git a/tests/utilities/describe.ts b/tests/utilities/describe.ts index e012462..3ed27f2 100644 --- a/tests/utilities/describe.ts +++ b/tests/utilities/describe.ts @@ -11,7 +11,9 @@ export function describe( return (getEventStore: EventStoreFn) => desc(name, () => runner(getEventStore)); } -type EventStoreFn = (options?: { hooks?: EventStoreHooks }) => Promise<{ +type EventStoreFn = (options?: { + hooks?: EventStoreHooks; +}) => Promise<{ store: EventStore; projector: Projector; }>; diff --git a/types/projector.ts b/types/projector.ts index 273d2c8..f57829c 100644 --- a/types/projector.ts +++ b/types/projector.ts @@ -1,8 +1,14 @@ import type { EventRecord } from "../libraries/event.ts"; -export type BatchedProjectorListeners = Record> | undefined>; +export type BatchedProjectorListeners = Record< + string, + Set> | undefined +>; -export type ProjectorListeners = Record> | undefined>; +export type ProjectorListeners = Record< + string, + Set> | undefined +>; export type ProjectorMessage = { record: TRecord; @@ -11,11 +17,15 @@ export type ProjectorMessage = { export type BatchedProjectorListenerFn = (records: TRecord[]) => void; -export type ProjectorListenerFn = (record: TRecord, status: ProjectionStatus) => void; +export type ProjectorListenerFn = ( + record: TRecord, + status: ProjectionStatus, +) => void; -export type ProjectionHandler | void = void> = TSuccessData extends void - ? (record: TRecord) => Promise - : (record: TRecord) => Promise; +export type ProjectionHandler< + TRecord extends EventRecord = EventRecord, + TSuccessData extends Record | void = void, +> = TSuccessData extends void ? (record: TRecord) => Promise : (record: TRecord) => Promise; export type BatchedProjectionHandler = (records: TRecord[]) => Promise;