archstone 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -0
- package/dist/core/index.d.ts +270 -0
- package/dist/core/index.js +122 -0
- package/dist/domain/application/index.d.ts +204 -0
- package/dist/domain/application/index.js +2 -0
- package/dist/domain/enterprise/index.d.ts +267 -0
- package/dist/domain/enterprise/index.js +12 -0
- package/dist/domain/index.d.ts +470 -0
- package/dist/domain/index.js +13 -0
- package/dist/shared/chunk-9cq8r162.js +88 -0
- package/dist/shared/chunk-wsjyhnpq.js +1 -0
- package/dist/shared/chunk-wzqmg7ms.js +21 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# archstone
|
|
2
|
+
|
|
3
|
+
A TypeScript-first architecture foundation for backend services, built around Domain-Driven Design (DDD) and Clean Architecture principles.
|
|
4
|
+
|
|
5
|
+
Archstone provides the core building blocks — entities, value objects, aggregates, domain events, use cases, and repository contracts — so you can focus on your domain logic instead of re-implementing the same structural patterns across every project.
|
|
6
|
+
|
|
7
|
+
## Layers
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── core/ # Language-level utilities with no domain knowledge
|
|
12
|
+
│ ├── either.ts # Functional error handling (Left / Right)
|
|
13
|
+
│ ├── value-object.ts # Base class for value objects
|
|
14
|
+
│ ├── unique-entity-id.ts # UUID v7 identity wrapper
|
|
15
|
+
│ ├── watched-list.ts # Change-tracked collection for aggregates
|
|
16
|
+
│ └── types/
|
|
17
|
+
│ └── optional.ts # Optional<T, K> utility type
|
|
18
|
+
│
|
|
19
|
+
└── domain/
|
|
20
|
+
├── enterprise/ # Pure domain model — no framework deps
|
|
21
|
+
│ ├── entities/
|
|
22
|
+
│ │ ├── entity.ts # Identity-based base entity
|
|
23
|
+
│ │ └── aggregate-root.ts # Event-raising aggregate base
|
|
24
|
+
│ └── events/
|
|
25
|
+
│ ├── domain-event.ts # DomainEvent interface
|
|
26
|
+
│ ├── domain-events.ts # Singleton registry & dispatcher
|
|
27
|
+
│ └── event-handler.ts # EventHandler interface
|
|
28
|
+
│
|
|
29
|
+
└── application/ # Orchestration — use cases & repository contracts
|
|
30
|
+
├── use-cases/
|
|
31
|
+
│ ├── use-case.ts # UseCase<Input, Output> interface
|
|
32
|
+
│ └── use-case.error.ts # Base error type for use case failures
|
|
33
|
+
└── repositories/
|
|
34
|
+
├── repository.ts # Full CRUD contract (composed)
|
|
35
|
+
├── findabe.ts # Findable<T>
|
|
36
|
+
├── creatable.ts # Creatable<T>
|
|
37
|
+
├── saveble.ts # Saveable<T>
|
|
38
|
+
└── deletable.ts # Deletable<T>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Core Concepts
|
|
42
|
+
|
|
43
|
+
### Either — functional error handling
|
|
44
|
+
|
|
45
|
+
Use cases never throw. They return an `Either<Error, Value>` — left for failure, right for success.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Either, left, right } from "@/core/either"
|
|
49
|
+
|
|
50
|
+
type Result = Either<UserNotFoundError, User>
|
|
51
|
+
|
|
52
|
+
async function findUser(id: string): Promise<Result> {
|
|
53
|
+
const user = await repo.findById(id)
|
|
54
|
+
if (!user) return left(new UserNotFoundError(id))
|
|
55
|
+
return right(user)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = await findUser("123")
|
|
59
|
+
if (result.isLeft()) console.error(result.value) // UserNotFoundError
|
|
60
|
+
else console.log(result.value) // User
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Entity & AggregateRoot
|
|
64
|
+
|
|
65
|
+
Entities are defined by identity. Aggregates extend that with domain event support.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { AggregateRoot } from "@/domain/enterprise/entities/aggregate-root"
|
|
69
|
+
import { UniqueEntityId } from "@/core/unique-entity-id"
|
|
70
|
+
import { Optional } from "@/core/types/optional"
|
|
71
|
+
|
|
72
|
+
interface OrderProps {
|
|
73
|
+
customerId: UniqueEntityId
|
|
74
|
+
total: number
|
|
75
|
+
createdAt: Date
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
class Order extends AggregateRoot<OrderProps> {
|
|
79
|
+
get customerId() { return this.props.customerId }
|
|
80
|
+
get total() { return this.props.total }
|
|
81
|
+
|
|
82
|
+
static create(props: Optional<OrderProps, "createdAt">): Order {
|
|
83
|
+
const order = new Order(
|
|
84
|
+
{ ...props, createdAt: props.createdAt ?? new Date() },
|
|
85
|
+
)
|
|
86
|
+
order.addDomainEvent(new OrderCreatedEvent(order))
|
|
87
|
+
return order
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### ValueObject
|
|
93
|
+
|
|
94
|
+
Value objects are equal by their properties, not by reference.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { ValueObject } from "@/core/value-object"
|
|
98
|
+
|
|
99
|
+
interface EmailProps { value: string }
|
|
100
|
+
|
|
101
|
+
class Email extends ValueObject<EmailProps> {
|
|
102
|
+
get value() { return this.props.value }
|
|
103
|
+
|
|
104
|
+
static create(raw: string): Email {
|
|
105
|
+
if (!raw.includes("@")) throw new Error("Invalid email")
|
|
106
|
+
return new Email({ value: raw.toLowerCase() })
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### WatchedList
|
|
112
|
+
|
|
113
|
+
Track additions and removals in a collection without rewriting the whole thing on save.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
class TagList extends WatchedList<Tag> {
|
|
117
|
+
compareItems(a: Tag, b: Tag) { return a.id.equals(b.id) }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const tags = new TagList([existingTag])
|
|
121
|
+
tags.add(newTag)
|
|
122
|
+
tags.remove(existingTag)
|
|
123
|
+
|
|
124
|
+
tags.getNewItems() // [newTag]
|
|
125
|
+
tags.getRemovedItems() // [existingTag]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Domain Events
|
|
129
|
+
|
|
130
|
+
Events are raised inside aggregates and dispatched by the infrastructure layer after successful persistence.
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
// register a handler
|
|
134
|
+
DomainEvents.register(
|
|
135
|
+
(event) => sendWelcomeEmail(event as UserCreatedEvent),
|
|
136
|
+
UserCreatedEvent.name,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
// infrastructure dispatches after persisting
|
|
140
|
+
await userRepository.create(user)
|
|
141
|
+
DomainEvents.dispatchEventsForAggregate(user.id)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Repository Contracts
|
|
145
|
+
|
|
146
|
+
Repositories are defined as interfaces in the application layer. Implementations live in infrastructure.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
// application/repositories/user-repository.ts
|
|
150
|
+
export interface UserRepository extends Repository<User> {
|
|
151
|
+
findByEmail(email: string): Promise<User | null>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// or compose only what you need
|
|
155
|
+
export interface AuditRepository extends Creatable<AuditLog> {}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Getting Started
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bun install
|
|
162
|
+
bun test
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Tech
|
|
166
|
+
|
|
167
|
+
- [Bun](https://bun.sh) — runtime, bundler, and test runner
|
|
168
|
+
- TypeScript 5 with strict mode
|
|
169
|
+
- Zero runtime dependencies
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
MIT
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the left side of an {@link Either} — conventionally used for
|
|
3
|
+
* failure or error values.
|
|
4
|
+
*
|
|
5
|
+
* @template L - The type of the failure value
|
|
6
|
+
* @template R - The type of the success value
|
|
7
|
+
*/
|
|
8
|
+
declare class Left<
|
|
9
|
+
L,
|
|
10
|
+
R
|
|
11
|
+
> {
|
|
12
|
+
readonly value: L;
|
|
13
|
+
constructor(value: L);
|
|
14
|
+
isRight(): this is Right<L, R>;
|
|
15
|
+
isLeft(): this is Left<L, R>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Represents the right side of an {@link Either} — conventionally used for
|
|
19
|
+
* success values.
|
|
20
|
+
*
|
|
21
|
+
* @template L - The type of the failure value
|
|
22
|
+
* @template R - The type of the success value
|
|
23
|
+
*/
|
|
24
|
+
declare class Right<
|
|
25
|
+
L,
|
|
26
|
+
R
|
|
27
|
+
> {
|
|
28
|
+
readonly value: R;
|
|
29
|
+
constructor(value: R);
|
|
30
|
+
isRight(): this is Right<L, R>;
|
|
31
|
+
isLeft(): this is Left<L, R>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A discriminated union that represents a value of one of two possible types.
|
|
35
|
+
*
|
|
36
|
+
* Commonly used as a type-safe alternative to throwing exceptions — the left
|
|
37
|
+
* side carries an error, and the right side carries a success value.
|
|
38
|
+
*
|
|
39
|
+
* Use the {@link left} and {@link right} helper functions to construct values,
|
|
40
|
+
* and `isLeft()` / `isRight()` to narrow the type.
|
|
41
|
+
*
|
|
42
|
+
* @template L - The type of the failure value
|
|
43
|
+
* @template R - The type of the success value
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* type FindUserResult = Either<NotFoundError, User>
|
|
48
|
+
*
|
|
49
|
+
* function findUser(id: string): FindUserResult {
|
|
50
|
+
* const user = db.find(id)
|
|
51
|
+
* if (!user) return left(new NotFoundError("User", id))
|
|
52
|
+
* return right(user)
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* const result = findUser("123")
|
|
56
|
+
*
|
|
57
|
+
* if (result.isLeft()) {
|
|
58
|
+
* console.error(result.value) // NotFoundError
|
|
59
|
+
* } else {
|
|
60
|
+
* console.log(result.value) // User
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
type Either<
|
|
65
|
+
L,
|
|
66
|
+
R
|
|
67
|
+
> = Left<L, R> | Right<L, R>;
|
|
68
|
+
/**
|
|
69
|
+
* Constructs a {@link Left} value, representing a failure.
|
|
70
|
+
*
|
|
71
|
+
* @param value - The error or failure value
|
|
72
|
+
*/
|
|
73
|
+
declare const left: <
|
|
74
|
+
L,
|
|
75
|
+
R
|
|
76
|
+
>(value: L) => Either<L, R>;
|
|
77
|
+
/**
|
|
78
|
+
* Constructs a {@link Right} value, representing a success.
|
|
79
|
+
*
|
|
80
|
+
* @param value - The success value
|
|
81
|
+
*/
|
|
82
|
+
declare const right: <
|
|
83
|
+
L,
|
|
84
|
+
R
|
|
85
|
+
>(value: R) => Either<L, R>;
|
|
86
|
+
/**
|
|
87
|
+
* Make some property optional on type
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* type Post {
|
|
92
|
+
* id: string;
|
|
93
|
+
* name: string;
|
|
94
|
+
* email: string;
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* Optional<Post, 'id' | 'email'>
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
type Optional<
|
|
101
|
+
T,
|
|
102
|
+
K extends keyof T
|
|
103
|
+
> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
104
|
+
/**
|
|
105
|
+
* Represents a unique identifier for a domain entity.
|
|
106
|
+
*
|
|
107
|
+
* Wraps a UUID v7 string, which is time-sortable and ideal for database
|
|
108
|
+
* indexing. A new identifier is generated automatically if no value is provided.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* // auto-generated
|
|
113
|
+
* const id = new UniqueEntityId()
|
|
114
|
+
*
|
|
115
|
+
* // from existing value (e.g. reconstructing from database)
|
|
116
|
+
* const id = new UniqueEntityId("0195d810-5b3e-7000-8e3e-1a2b3c4d5e6f")
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
declare class UniqueEntityId {
|
|
120
|
+
private readonly value;
|
|
121
|
+
constructor(value?: string);
|
|
122
|
+
/**
|
|
123
|
+
* Returns the identifier as a primitive string.
|
|
124
|
+
* Useful for serialization and database persistence.
|
|
125
|
+
*/
|
|
126
|
+
toValue(): string;
|
|
127
|
+
/**
|
|
128
|
+
* Returns the string representation of the identifier.
|
|
129
|
+
*/
|
|
130
|
+
toString(): string;
|
|
131
|
+
/**
|
|
132
|
+
* Compares this identifier with another by value equality.
|
|
133
|
+
*
|
|
134
|
+
* @param id - The identifier to compare against
|
|
135
|
+
* @returns `true` if both identifiers have the same value
|
|
136
|
+
*/
|
|
137
|
+
equals(id: UniqueEntityId): boolean;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Base class for all value objects in the domain.
|
|
141
|
+
*
|
|
142
|
+
* A value object is an object defined entirely by its attributes — it has no
|
|
143
|
+
* identity. Two value objects are considered equal if all their properties
|
|
144
|
+
* are deeply equal, regardless of reference.
|
|
145
|
+
*
|
|
146
|
+
* Value objects should be immutable. Avoid mutating `props` directly;
|
|
147
|
+
* instead, create a new instance with the updated values.
|
|
148
|
+
*
|
|
149
|
+
* All value objects must implement a static `create` factory method to
|
|
150
|
+
* encapsulate construction and validation logic, keeping the constructor
|
|
151
|
+
* protected from external instantiation.
|
|
152
|
+
*
|
|
153
|
+
* @template Props - The shape of the value object's properties
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```ts
|
|
157
|
+
* interface EmailProps {
|
|
158
|
+
* value: string
|
|
159
|
+
* }
|
|
160
|
+
*
|
|
161
|
+
* class Email extends ValueObject<EmailProps> {
|
|
162
|
+
* get value() { return this.props.value }
|
|
163
|
+
*
|
|
164
|
+
* static create(email: string): Email {
|
|
165
|
+
* if (!email.includes("@")) {
|
|
166
|
+
* throw new ValidationError("Invalid email address.")
|
|
167
|
+
* }
|
|
168
|
+
*
|
|
169
|
+
* return new Email({ value: email })
|
|
170
|
+
* }
|
|
171
|
+
* }
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
declare abstract class ValueObject<Props> {
|
|
175
|
+
protected props: Props;
|
|
176
|
+
protected constructor(props: Props);
|
|
177
|
+
/**
|
|
178
|
+
* Compares this value object with another by deep equality of their properties.
|
|
179
|
+
*
|
|
180
|
+
* @param other - The value object to compare against
|
|
181
|
+
* @returns `true` if both value objects have deeply equal properties
|
|
182
|
+
*/
|
|
183
|
+
equals(other: ValueObject<Props>): boolean;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Tracks additions and removals of items in a collection, enabling
|
|
187
|
+
* efficient persistence of only what changed.
|
|
188
|
+
*
|
|
189
|
+
* Commonly used inside aggregate roots to manage one-to-many relationships
|
|
190
|
+
* without rewriting the entire collection on every save — only new and
|
|
191
|
+
* removed items are persisted.
|
|
192
|
+
*
|
|
193
|
+
* Subclasses must implement {@link compareItems} to define equality between items.
|
|
194
|
+
*
|
|
195
|
+
* @template T - The type of items in the list
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```ts
|
|
199
|
+
* class TagList extends WatchedList<Tag> {
|
|
200
|
+
* compareItems(a: Tag, b: Tag): boolean {
|
|
201
|
+
* return a.id.equals(b.id)
|
|
202
|
+
* }
|
|
203
|
+
*
|
|
204
|
+
* static create(tags: Tag[]): TagList {
|
|
205
|
+
* return new TagList(tags)
|
|
206
|
+
* }
|
|
207
|
+
* }
|
|
208
|
+
*
|
|
209
|
+
* const tags = TagList.create([existingTag])
|
|
210
|
+
*
|
|
211
|
+
* tags.add(newTag) // tracked as new
|
|
212
|
+
* tags.remove(oldTag) // tracked as removed
|
|
213
|
+
*
|
|
214
|
+
* tags.getNewItems() // [newTag]
|
|
215
|
+
* tags.getRemovedItems() // [oldTag]
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
declare abstract class WatchedList<T> {
|
|
219
|
+
protected currentItems: T[];
|
|
220
|
+
protected initial: T[];
|
|
221
|
+
protected new: T[];
|
|
222
|
+
protected removed: T[];
|
|
223
|
+
constructor(initialItems?: T[]);
|
|
224
|
+
/**
|
|
225
|
+
* Returns all current items in the list.
|
|
226
|
+
*/
|
|
227
|
+
getItems(): T[];
|
|
228
|
+
/**
|
|
229
|
+
* Returns items that were added since the list was created.
|
|
230
|
+
*/
|
|
231
|
+
getNewItems(): T[];
|
|
232
|
+
/**
|
|
233
|
+
* Returns items that were removed since the list was created.
|
|
234
|
+
*/
|
|
235
|
+
getRemovedItems(): T[];
|
|
236
|
+
/**
|
|
237
|
+
* Returns whether the given item exists in the current list.
|
|
238
|
+
*
|
|
239
|
+
* @param item - The item to check
|
|
240
|
+
*/
|
|
241
|
+
exists(item: T): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Adds an item to the list, tracking it as new if it wasn't in the initial set.
|
|
244
|
+
* If the item was previously removed, it is restored.
|
|
245
|
+
*
|
|
246
|
+
* @param item - The item to add
|
|
247
|
+
*/
|
|
248
|
+
add(item: T): void;
|
|
249
|
+
/**
|
|
250
|
+
* Removes an item from the list, tracking it as removed if it was in the initial set.
|
|
251
|
+
*
|
|
252
|
+
* @param item - The item to remove
|
|
253
|
+
*/
|
|
254
|
+
remove(item: T): void;
|
|
255
|
+
/**
|
|
256
|
+
* Replaces the entire list with a new set of items, automatically
|
|
257
|
+
* computing what was added and what was removed.
|
|
258
|
+
*
|
|
259
|
+
* @param items - The new set of items
|
|
260
|
+
*/
|
|
261
|
+
update(items: T[]): void;
|
|
262
|
+
private isCurrentItem;
|
|
263
|
+
private isNewItem;
|
|
264
|
+
private isRemovedItem;
|
|
265
|
+
private removeFromNew;
|
|
266
|
+
private removeFromCurrent;
|
|
267
|
+
private removeFromRemoved;
|
|
268
|
+
private wasAddedInitially;
|
|
269
|
+
}
|
|
270
|
+
export { right, left, WatchedList, ValueObject, UniqueEntityId, Optional, Either };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
UniqueEntityId
|
|
4
|
+
} from "../shared/chunk-wzqmg7ms.js";
|
|
5
|
+
|
|
6
|
+
// src/core/either.ts
|
|
7
|
+
class Left {
|
|
8
|
+
value;
|
|
9
|
+
constructor(value) {
|
|
10
|
+
this.value = value;
|
|
11
|
+
}
|
|
12
|
+
isRight() {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
isLeft() {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
class Right {
|
|
21
|
+
value;
|
|
22
|
+
constructor(value) {
|
|
23
|
+
this.value = value;
|
|
24
|
+
}
|
|
25
|
+
isRight() {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
isLeft() {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var left = (value) => new Left(value);
|
|
33
|
+
var right = (value) => new Right(value);
|
|
34
|
+
// src/core/value-object.ts
|
|
35
|
+
class ValueObject {
|
|
36
|
+
props;
|
|
37
|
+
constructor(props) {
|
|
38
|
+
this.props = props;
|
|
39
|
+
}
|
|
40
|
+
equals(other) {
|
|
41
|
+
return JSON.stringify(this.props) === JSON.stringify(other.props);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// src/core/watched-list.ts
|
|
45
|
+
class WatchedList {
|
|
46
|
+
currentItems;
|
|
47
|
+
initial;
|
|
48
|
+
new;
|
|
49
|
+
removed;
|
|
50
|
+
constructor(initialItems) {
|
|
51
|
+
this.currentItems = initialItems ?? [];
|
|
52
|
+
this.initial = initialItems ?? [];
|
|
53
|
+
this.new = [];
|
|
54
|
+
this.removed = [];
|
|
55
|
+
}
|
|
56
|
+
getItems() {
|
|
57
|
+
return this.currentItems;
|
|
58
|
+
}
|
|
59
|
+
getNewItems() {
|
|
60
|
+
return this.new;
|
|
61
|
+
}
|
|
62
|
+
getRemovedItems() {
|
|
63
|
+
return this.removed;
|
|
64
|
+
}
|
|
65
|
+
exists(item) {
|
|
66
|
+
return this.isCurrentItem(item);
|
|
67
|
+
}
|
|
68
|
+
add(item) {
|
|
69
|
+
if (this.isRemovedItem(item)) {
|
|
70
|
+
this.removeFromRemoved(item);
|
|
71
|
+
}
|
|
72
|
+
if (!(this.isNewItem(item) || this.wasAddedInitially(item))) {
|
|
73
|
+
this.new.push(item);
|
|
74
|
+
}
|
|
75
|
+
if (!this.isCurrentItem(item)) {
|
|
76
|
+
this.currentItems.push(item);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
remove(item) {
|
|
80
|
+
this.removeFromCurrent(item);
|
|
81
|
+
if (this.isNewItem(item)) {
|
|
82
|
+
this.removeFromNew(item);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!this.isRemovedItem(item)) {
|
|
86
|
+
this.removed.push(item);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
update(items) {
|
|
90
|
+
this.new = items.filter((a) => !this.getItems().some((b) => this.compareItems(a, b)));
|
|
91
|
+
this.removed = this.getItems().filter((a) => !items.some((b) => this.compareItems(a, b)));
|
|
92
|
+
this.currentItems = items;
|
|
93
|
+
}
|
|
94
|
+
isCurrentItem(item) {
|
|
95
|
+
return this.currentItems.some((v) => this.compareItems(item, v));
|
|
96
|
+
}
|
|
97
|
+
isNewItem(item) {
|
|
98
|
+
return this.new.some((v) => this.compareItems(item, v));
|
|
99
|
+
}
|
|
100
|
+
isRemovedItem(item) {
|
|
101
|
+
return this.removed.some((v) => this.compareItems(item, v));
|
|
102
|
+
}
|
|
103
|
+
removeFromNew(item) {
|
|
104
|
+
this.new = this.new.filter((v) => !this.compareItems(v, item));
|
|
105
|
+
}
|
|
106
|
+
removeFromCurrent(item) {
|
|
107
|
+
this.currentItems = this.currentItems.filter((v) => !this.compareItems(item, v));
|
|
108
|
+
}
|
|
109
|
+
removeFromRemoved(item) {
|
|
110
|
+
this.removed = this.removed.filter((v) => !this.compareItems(item, v));
|
|
111
|
+
}
|
|
112
|
+
wasAddedInitially(item) {
|
|
113
|
+
return this.initial.some((v) => this.compareItems(item, v));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
right,
|
|
118
|
+
left,
|
|
119
|
+
WatchedList,
|
|
120
|
+
ValueObject,
|
|
121
|
+
UniqueEntityId
|
|
122
|
+
};
|