feat: add peerDependencies

This commit is contained in:
2025-07-07 13:11:16 +02:00
parent 2093dd7097
commit dd1570d52e
36 changed files with 481 additions and 274 deletions

14
.prettierrc Normal file
View File

@@ -0,0 +1,14 @@
{
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 120,
"singleQuote": false,
"overrides": [
{
"files": "*.ts",
"options": {
"parser": "typescript"
}
}
]
}

View File

@@ -1,5 +1,6 @@
{
"deno.enable": true,
"deno.lint": false,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"

View File

@@ -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<EventRecord> {
type Name = {
given: string;
family: string;
}
};
```
### Projectors
@@ -201,7 +208,6 @@ When handling events in a distributed system or during event replay operations,
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<void>)`
This handler tells the projection that an event is only ever processed when the event is originating directly from the

View File

@@ -1,4 +1,4 @@
import z from "zod";
import z from "zod/v4";
import type { CollectionRegistrar } from "../types.ts";

View File

@@ -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];

View File

@@ -1,4 +1,4 @@
import z from "zod";
import z from "zod/v4";
import type { CollectionRegistrar } from "../types.ts";

View File

@@ -1,4 +1,4 @@
import z from "zod";
import z from "zod/v4";
import type { CollectionRegistrar } from "../types.ts";

View File

@@ -70,7 +70,10 @@ export class MongoEventsProvider implements EventsProvider {
* @param options - Read options for modifying the result.
*/
async getByStreams(streams: string[], options: EventReadOptions = {}): Promise<EventRecord[]> {
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[];

View File

@@ -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<TSchema extends ZodObject>(parser: TSchema): (documents: WithId<object>[]) => z.infer<TSchema>[] {
export function toParsedRecords<TSchema extends ZodObject>(
parser: TSchema,
): (documents: WithId<object>[]) => z.infer<TSchema>[] {
return parser.array().parse;
}
@@ -21,7 +23,9 @@ export function toParsedRecords<TSchema extends ZodObject>(parser: TSchema): (do
*
* @param parser - Zod parser to run the document through.
*/
export function toParsedRecord<TSchema extends ZodObject>(parser: TSchema): (document: WithId<object> | null) => z.infer<TSchema> | undefined {
export function toParsedRecord<TSchema extends ZodObject>(
parser: TSchema,
): (document: WithId<object> | null) => z.infer<TSchema> | undefined {
return function (document) {
if (document === null) {
return undefined;

View File

@@ -1,6 +1,10 @@
import type { Options, Sql } from "postgres";
export type PostgresConnection = [PostgresConnectionUrl, Options<any>?] | [Options<any>] | Sql | PostgresConnectionFactory;
export type PostgresConnection =
| [PostgresConnectionUrl, Options<any>?]
| [Options<any>]
| Sql
| PostgresConnectionFactory;
type PostgresConnectionUrl = `postgres://${string}:${string}@${string}:${number}/${string}`;

View File

@@ -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<EventRecord[]> {
async getByStream(
stream: string,
{ filter, cursor, direction, limit }: EventReadOptions = {},
): Promise<EventRecord[]> {
return this.db.sql<PGEventRecord[]>`
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<EventRecord[]> {
async getByStreams(
streams: string[],
{ filter, cursor, direction, limit }: EventReadOptions = {},
): Promise<EventRecord[]> {
return this.db.sql<PGEventRecord[]>`
SELECT * FROM ${this.table}
WHERE
@@ -112,7 +118,9 @@ export class PostgresEventsProvider implements EventsProvider {
* @param id - Event id.
*/
async getById(id: string): Promise<EventRecord | undefined> {
return this.db.sql<PGEventRecord[]>`SELECT * FROM ${this.table} WHERE id = ${id}`.then(this.#fromDriver).then(([record]) => record);
return this.db.sql<PGEventRecord[]>`SELECT * FROM ${this.table} WHERE id = ${id}`
.then(this.#fromDriver)
.then(([record]) => record);
}
/**

View File

@@ -35,9 +35,11 @@ export class PostgresRelationsProvider implements RelationsProvider {
* @param stream - Stream to add to the key.
*/
async insert(key: string, stream: string): Promise<void> {
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 "))}`;
}
})

View File

@@ -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"

322
deno.lock generated
View File

@@ -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"
]
}
}

View File

@@ -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: "^_",
},
],
},
},
];

View File

@@ -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.`,
);
}
}

View File

@@ -47,7 +47,9 @@ export class EventFactory<const TEvents extends Event[] = Event[]> {
*
* @param type - Event type to retrieve.
*/
get<TType extends TEvents[number]["state"]["type"]>(type: TType): Extract<TEvents[number], { state: { type: TType } }> {
get<TType extends TEvents[number]["state"]["type"]>(
type: TType,
): Extract<TEvents[number], { state: { type: TType } }> {
return this.#index.get(type) as Extract<TEvents[number], { state: { type: TType } }>;
}
}

View File

@@ -118,7 +118,9 @@ export class EventStore<
*
* @param name - Aggregate name to retrieve.
*/
aggregate<TName extends TAggregateFactory["$aggregates"][number]["name"]>(name: TName): Extract<TAggregateFactory["$aggregates"][number], { name: TName }> {
aggregate<TName extends TAggregateFactory["$aggregates"][number]["name"]>(
name: TName,
): Extract<TAggregateFactory["$aggregates"][number], { name: TName }> {
return this.#aggregates.get(name) as Extract<TAggregateFactory["$aggregates"][number], { name: TName }>;
}
@@ -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<TAggregateFactory["$aggregates"][number]>, settings?: EventsInsertSettings): Promise<void> {
async pushAggregate(
aggregate: InstanceType<TAggregateFactory["$aggregates"][number]>,
settings?: EventsInsertSettings,
): Promise<void> {
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<TAggregateFactory["$aggregates"][number]>[], settings?: EventsInsertSettings): Promise<void> {
async pushManyAggregates(
aggregates: InstanceType<TAggregateFactory["$aggregates"][number]>[],
settings?: EventsInsertSettings,
): Promise<void> {
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<void> {
async pushManyEvents(
records: this["$events"][number]["$record"][],
settings: EventsInsertSettings = {},
): Promise<void> {
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<TEventFactory["$events"][number]["$record"][]> {
async getEventsByStreams(
streams: string[],
options?: EventReadOptions,
): Promise<TEventFactory["$events"][number]["$record"][]> {
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<TEventFactory["$events"][number]["$record"][]> {
async getEventsByRelations(
keys: string[],
options?: EventReadOptions,
): Promise<TEventFactory["$events"][number]["$record"][]> {
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<TState extends Unknown>(foldFn: ReducerLeftFold<TState, TEventFactory>, stateFn: ReducerState<TState>): Reducer<TEventFactory, TState> {
makeReducer<TState extends Unknown>(
foldFn: ReducerLeftFold<TState, TEventFactory>,
stateFn: ReducerState<TState>,
): Reducer<TEventFactory, TState> {
return makeReducer<TEventFactory, TState>(foldFn, stateFn);
}
@@ -352,7 +372,9 @@ export class EventStore<
* });
* ```
*/
makeAggregateReducer<TAggregateRoot extends typeof AggregateRoot<TEventFactory>>(aggregate: TAggregateRoot): Reducer<TEventFactory, InstanceType<TAggregateRoot>> {
makeAggregateReducer<TAggregateRoot extends typeof AggregateRoot<TEventFactory>>(
aggregate: TAggregateRoot,
): Reducer<TEventFactory, InstanceType<TAggregateRoot>> {
return makeAggregateReducer<TEventFactory, TAggregateRoot>(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<TReducer extends Reducer>({ name, stream, relation, reducer, ...query }: ReduceQuery<TReducer>): Promise<void> {
async createSnapshot<TReducer extends Reducer>({
name,
stream,
relation,
reducer,
...query
}: ReduceQuery<TReducer>): Promise<void> {
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<TEventFactory extends EventFactory> = Partial<{
* @param records - List of event records inserted.
* @param settings - Event insert settings used.
*/
onEventsInserted(records: TEventFactory["$events"][number]["$record"][], settings: EventsInsertSettings): Promise<void>;
onEventsInserted(
records: TEventFactory["$events"][number]["$record"][],
settings: EventsInsertSettings,
): Promise<void>;
/**
* Triggered when an unhandled exception is thrown during `.pushEvent` and

View File

@@ -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";

View File

@@ -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;

View File

@@ -132,7 +132,10 @@ export class Projector<TEventFactory extends EventFactory = EventFactory> {
*/
once<
TType extends TEventFactory["$events"][number]["$record"]["type"],
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<TEventFactory["$events"][number]["$record"], { type: TType }>,
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<
TEventFactory["$events"][number]["$record"],
{ type: TType }
>,
TSuccessData extends Record<string, any> | void = void,
>(
type: TType,
@@ -175,7 +178,10 @@ export class Projector<TEventFactory extends EventFactory = EventFactory> {
*/
on<
TType extends TEventFactory["$events"][number]["$record"]["type"],
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<TEventFactory["$events"][number]["$record"], { type: TType }>,
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<
TEventFactory["$events"][number]["$record"],
{ type: TType }
>,
>(type: TType, handler: ProjectionHandler<TRecord>): Subscription {
return this.#subscribe(type, FILTER_CONTINUOUS, handler as any);
}
@@ -194,7 +200,10 @@ export class Projector<TEventFactory extends EventFactory = EventFactory> {
*/
all<
TType extends TEventFactory["$events"][number]["$record"]["type"],
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<TEventFactory["$events"][number]["$record"], { type: TType }>,
TRecord extends TEventFactory["$events"][number]["$record"] = Extract<
TEventFactory["$events"][number]["$record"],
{ type: TType }
>,
>(type: TType, handler: ProjectionHandler<TRecord>): Subscription {
return this.#subscribe(type, FILTER_ALL, handler as any);
}

View File

@@ -8,9 +8,10 @@ import { EventFactory } from "./event-factory.ts";
*
* @param aggregate - Aggregate to instantiate and create an instance of.
*/
export function makeAggregateReducer<TEventFactory extends EventFactory, TAggregateRoot extends typeof AggregateRoot<TEventFactory>>(
aggregate: TAggregateRoot,
): Reducer<TEventFactory, InstanceType<TAggregateRoot>> {
export function makeAggregateReducer<
TEventFactory extends EventFactory,
TAggregateRoot extends typeof AggregateRoot<TEventFactory>,
>(aggregate: TAggregateRoot): Reducer<TEventFactory, InstanceType<TAggregateRoot>> {
return {
from(snapshot: Unknown) {
return aggregate.from(snapshot);
@@ -45,7 +46,10 @@ export function makeReducer<TEventFactory extends EventFactory, TState extends U
};
}
export type Reducer<TEventFactory extends EventFactory = EventFactory, TState extends Record<string, unknown> | AggregateRoot<TEventFactory> = any> = {
export type Reducer<
TEventFactory extends EventFactory = EventFactory,
TState extends Record<string, unknown> | AggregateRoot<TEventFactory> = 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<TEventFactory extends EventFactory = EventFactory, TState ex
* })
* ```
*/
export type ReducerLeftFold<TState extends Record<string, unknown> = any, TEventFactory extends EventFactory = EventFactory> = (
state: TState,
event: TEventFactory["$events"][number]["$record"],
) => TState;
export type ReducerLeftFold<
TState extends Record<string, unknown> = any,
TEventFactory extends EventFactory = EventFactory,
> = (state: TState, event: TEventFactory["$events"][number]["$record"]) => TState;
export type ReducerState<TState extends Unknown> = () => TState;

View File

@@ -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[] = [];

View File

@@ -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"
}
}

View File

@@ -1,5 +1,9 @@
export abstract class ServiceError<TData = unknown> extends Error {
constructor(message: string, readonly status: number, readonly data?: TData) {
constructor(
message: string,
readonly status: number,
readonly data?: TData,
) {
super(message);
}

View File

@@ -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(),
}),

View File

@@ -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 () => {

View File

@@ -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<EventStoreFactory> } = {}) => getEventStore(sql, options);
const eventStoreFn = async (options: { hooks?: EventStoreHooks<EventStoreFactory> } = {}) =>
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<EventStoreFactory> }) {
async function getEventStore(
connection: PostgresConnection,
{ hooks = {} }: { hooks?: EventStoreHooks<EventStoreFactory> },
) {
const store = new EventStore({
adapter: new PostgresAdapter(connection, { schema: "event_store" }),
events,

View File

@@ -8,7 +8,11 @@ export default describe<EventStoreFactory>(".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();

View File

@@ -82,7 +82,12 @@ export default describe<EventStoreFactory>(".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<EventStoreFactory>(".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`]);

View File

@@ -25,7 +25,10 @@ export default describe<EventStoreFactory>(".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" } });

View File

@@ -33,10 +33,16 @@ export default describe<EventStoreFactory>(".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" } });

View File

@@ -11,7 +11,9 @@ export function describe<TEventFactory extends EventFactory>(
return (getEventStore: EventStoreFn<TEventFactory>) => desc(name, () => runner(getEventStore));
}
type EventStoreFn<TEventFactory extends EventFactory> = (options?: { hooks?: EventStoreHooks<TEventFactory> }) => Promise<{
type EventStoreFn<TEventFactory extends EventFactory> = (options?: {
hooks?: EventStoreHooks<TEventFactory>;
}) => Promise<{
store: EventStore<TEventFactory, any, any>;
projector: Projector<TEventFactory>;
}>;

View File

@@ -1,8 +1,14 @@
import type { EventRecord } from "../libraries/event.ts";
export type BatchedProjectorListeners<TRecord extends EventRecord = EventRecord> = Record<string, Set<BatchedProjectorListenerFn<TRecord>> | undefined>;
export type BatchedProjectorListeners<TRecord extends EventRecord = EventRecord> = Record<
string,
Set<BatchedProjectorListenerFn<TRecord>> | undefined
>;
export type ProjectorListeners<TRecord extends EventRecord = EventRecord> = Record<string, Set<ProjectorListenerFn<TRecord>> | undefined>;
export type ProjectorListeners<TRecord extends EventRecord = EventRecord> = Record<
string,
Set<ProjectorListenerFn<TRecord>> | undefined
>;
export type ProjectorMessage<TRecord extends EventRecord = EventRecord> = {
record: TRecord;
@@ -11,11 +17,15 @@ export type ProjectorMessage<TRecord extends EventRecord = EventRecord> = {
export type BatchedProjectorListenerFn<TRecord extends EventRecord = EventRecord> = (records: TRecord[]) => void;
export type ProjectorListenerFn<TRecord extends EventRecord = EventRecord> = (record: TRecord, status: ProjectionStatus) => void;
export type ProjectorListenerFn<TRecord extends EventRecord = EventRecord> = (
record: TRecord,
status: ProjectionStatus,
) => void;
export type ProjectionHandler<TRecord extends EventRecord = EventRecord, TSuccessData extends Record<string, any> | void = void> = TSuccessData extends void
? (record: TRecord) => Promise<void>
: (record: TRecord) => Promise<TSuccessData>;
export type ProjectionHandler<
TRecord extends EventRecord = EventRecord,
TSuccessData extends Record<string, any> | void = void,
> = TSuccessData extends void ? (record: TRecord) => Promise<void> : (record: TRecord) => Promise<TSuccessData>;
export type BatchedProjectionHandler<TRecord extends EventRecord = EventRecord> = (records: TRecord[]) => Promise<void>;