feat: set beta version
This commit is contained in:
134
tests/mocks/aggregates.ts
Normal file
134
tests/mocks/aggregates.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { AggregateRoot } from "../../libraries/aggregate.ts";
|
||||
import { AggregateFactory } from "../../libraries/aggregate-factory.ts";
|
||||
import { makeId } from "../../libraries/nanoid.ts";
|
||||
import { makeAggregateReducer } from "../../libraries/reducer.ts";
|
||||
import { EventStoreFactory } from "./events.ts";
|
||||
|
||||
export class User extends AggregateRoot<EventStoreFactory> {
|
||||
static override readonly name = "user";
|
||||
|
||||
id: string = "";
|
||||
name: Name = {
|
||||
given: "",
|
||||
family: "",
|
||||
};
|
||||
email: string = "";
|
||||
active: boolean = true;
|
||||
posts: UserPosts = {
|
||||
list: [],
|
||||
count: 0,
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Factories
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static reducer = makeAggregateReducer(User);
|
||||
|
||||
static create(name: Name, email: string): User {
|
||||
const user = new User();
|
||||
user.push({
|
||||
type: "user:created",
|
||||
stream: makeId(),
|
||||
data: { name, email },
|
||||
meta: { auditor: "foo" },
|
||||
});
|
||||
return user;
|
||||
}
|
||||
|
||||
static async getById(userId: string): Promise<User | undefined> {
|
||||
return this.$store.reduce({ name: "user", stream: userId, reducer: this.reducer });
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Reducer
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
with(event: EventStoreFactory["$events"][number]["$record"]) {
|
||||
switch (event.type) {
|
||||
case "user:created": {
|
||||
this.id = event.stream;
|
||||
this.name.given = event.data.name?.given ?? "";
|
||||
this.name.family = event.data.name?.family ?? "";
|
||||
this.email = event.data.email;
|
||||
break;
|
||||
}
|
||||
case "user:name:given-set": {
|
||||
this.name.given = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:name:family-set": {
|
||||
this.name.family = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:email-set": {
|
||||
this.email = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:activated": {
|
||||
this.active = true;
|
||||
break;
|
||||
}
|
||||
case "user:deactivated": {
|
||||
this.active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Actions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
setGivenName(given: string): this {
|
||||
return this.push({
|
||||
type: "user:name:given-set",
|
||||
stream: this.id,
|
||||
data: given,
|
||||
meta: { auditor: "foo" },
|
||||
});
|
||||
}
|
||||
|
||||
setFamilyName(family: string): this {
|
||||
return this.push({
|
||||
type: "user:name:family-set",
|
||||
stream: this.id,
|
||||
data: family,
|
||||
meta: { auditor: "foo" },
|
||||
});
|
||||
}
|
||||
|
||||
setEmail(email: string, auditor: string): this {
|
||||
return this.push({
|
||||
type: "user:email-set",
|
||||
stream: this.id,
|
||||
data: email,
|
||||
meta: { auditor },
|
||||
});
|
||||
}
|
||||
|
||||
async snapshot(): Promise<this> {
|
||||
await this.$store.createSnapshot({ name: "user", stream: this.id, reducer: User.reducer });
|
||||
return this;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
fullName(): string {
|
||||
return `${this.name.given} ${this.name.family}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const aggregates = new AggregateFactory([User]);
|
||||
|
||||
type Name = {
|
||||
given: string;
|
||||
family: string;
|
||||
};
|
||||
|
||||
type UserPosts = {
|
||||
list: string[];
|
||||
count: number;
|
||||
};
|
||||
19
tests/mocks/errors.ts
Normal file
19
tests/mocks/errors.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export abstract class ServiceError<TData = unknown> extends Error {
|
||||
constructor(message: string, readonly status: number, readonly data?: TData) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
status: this.status,
|
||||
message: this.message,
|
||||
data: this.data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class CustomServiceError<TData = unknown> extends ServiceError<TData> {
|
||||
constructor(message = "Custom Error", data?: TData) {
|
||||
super(message, 400, data);
|
||||
}
|
||||
}
|
||||
32
tests/mocks/events.ts
Normal file
32
tests/mocks/events.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import z from "zod";
|
||||
|
||||
import { event } from "../../libraries/event.ts";
|
||||
import { EventFactory } from "../../libraries/event-factory.ts";
|
||||
|
||||
export const auditor = z.strictObject({ auditor: z.string() });
|
||||
|
||||
export const events = new EventFactory([
|
||||
event
|
||||
.type("user:created")
|
||||
.data(
|
||||
z.strictObject({
|
||||
name: z
|
||||
.union([z.strictObject({ given: z.string(), family: z.string().optional() }), z.strictObject({ given: z.string().optional(), family: z.string() })])
|
||||
.optional(),
|
||||
email: z.string(),
|
||||
}),
|
||||
)
|
||||
.meta(auditor),
|
||||
event.type("user:name:given-set").data(z.string()).meta(auditor),
|
||||
event.type("user:name:family-set").data(z.string()).meta(auditor),
|
||||
event.type("user:email-set").data(z.email()).meta(auditor),
|
||||
event.type("user:activated").meta(auditor),
|
||||
event.type("user:deactivated").meta(auditor),
|
||||
event
|
||||
.type("post:created")
|
||||
.data(z.strictObject({ title: z.string(), body: z.string() }))
|
||||
.meta(auditor),
|
||||
event.type("post:removed").meta(auditor),
|
||||
]);
|
||||
|
||||
export type EventStoreFactory = typeof events;
|
||||
32
tests/mocks/user-posts-reducer.ts
Normal file
32
tests/mocks/user-posts-reducer.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { makeReducer } from "../../libraries/reducer.ts";
|
||||
import { EventStoreFactory } from "./events.ts";
|
||||
|
||||
export const userPostReducer = makeReducer<EventStoreFactory, UserPostState>(
|
||||
(state, event) => {
|
||||
switch (event.type) {
|
||||
case "post:created": {
|
||||
state.posts.push({ id: event.stream, author: event.meta.auditor });
|
||||
state.count += 1;
|
||||
break;
|
||||
}
|
||||
case "post:removed": {
|
||||
state.posts = state.posts.filter(({ id }) => id !== event.stream);
|
||||
state.count -= 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
},
|
||||
() => ({
|
||||
posts: [],
|
||||
count: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
type UserPostState = {
|
||||
posts: {
|
||||
id: string;
|
||||
author: string;
|
||||
}[];
|
||||
count: number;
|
||||
};
|
||||
61
tests/mocks/user-reducer.ts
Normal file
61
tests/mocks/user-reducer.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { makeReducer } from "../../libraries/reducer.ts";
|
||||
import { EventStoreFactory } from "./events.ts";
|
||||
|
||||
export const userReducer = makeReducer<EventStoreFactory, UserState>(
|
||||
(state, event) => {
|
||||
switch (event.type) {
|
||||
case "user:created": {
|
||||
state.name.given = event.data.name?.given ?? "";
|
||||
state.name.family = event.data.name?.family ?? "";
|
||||
state.email = event.data.email;
|
||||
break;
|
||||
}
|
||||
case "user:name:given-set": {
|
||||
state.name.given = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:name:family-set": {
|
||||
state.name.family = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:email-set": {
|
||||
state.email = event.data;
|
||||
break;
|
||||
}
|
||||
case "user:activated": {
|
||||
state.active = true;
|
||||
break;
|
||||
}
|
||||
case "user:deactivated": {
|
||||
state.active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return state;
|
||||
},
|
||||
() => ({
|
||||
name: {
|
||||
given: "",
|
||||
family: "",
|
||||
},
|
||||
email: "",
|
||||
active: true,
|
||||
posts: {
|
||||
list: [],
|
||||
count: 0,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
type UserState = {
|
||||
name: {
|
||||
given: string;
|
||||
family: string;
|
||||
};
|
||||
email: string;
|
||||
active: boolean;
|
||||
posts: {
|
||||
list: string[];
|
||||
count: number;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user