archstone 1.0.2 → 1.0.3
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 +103 -92
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# archstone
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
[](https://www.npmjs.com/package/archstone)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://www.typescriptlang.org/)
|
|
11
|
+
[](https://bun.sh)
|
|
12
|
+
|
|
13
|
+
</div>
|
|
7
14
|
|
|
8
|
-
|
|
15
|
+
---
|
|
9
16
|
|
|
10
|
-
Archstone
|
|
17
|
+
Archstone gives you the structural pieces of **Domain-Driven Design** and **Clean Architecture** — entities, value objects, aggregates, domain events, use cases, and repository contracts — so every project starts from a solid, consistent foundation.
|
|
11
18
|
|
|
12
|
-
##
|
|
19
|
+
## Install
|
|
13
20
|
|
|
14
21
|
```bash
|
|
15
22
|
bun add archstone
|
|
@@ -17,57 +24,24 @@ bun add archstone
|
|
|
17
24
|
npm install archstone
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
##
|
|
27
|
+
## At a Glance
|
|
21
28
|
|
|
22
|
-
|
|
|
29
|
+
| Building block | What it does |
|
|
23
30
|
|---|---|
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
31
|
+
| `Entity` / `AggregateRoot` | Identity-based domain objects; aggregates raise domain events |
|
|
32
|
+
| `ValueObject` | Equality by value, not reference |
|
|
33
|
+
| `UniqueEntityId` | UUID v7 identity wrapper |
|
|
34
|
+
| `WatchedList` | Tracks additions and removals in a collection |
|
|
35
|
+
| `Either` | Functional error handling — no throwing in use cases |
|
|
36
|
+
| `UseCase` | Contract for application use cases returning `Either` |
|
|
37
|
+
| `Repository` | CRUD interface contracts — implementations live in infra |
|
|
28
38
|
|
|
29
|
-
##
|
|
39
|
+
## Usage
|
|
30
40
|
|
|
31
|
-
|
|
32
|
-
src/
|
|
33
|
-
├── core/ # Language-level utilities with no domain knowledge
|
|
34
|
-
│ ├── either.ts # Functional error handling (Left / Right)
|
|
35
|
-
│ ├── value-object.ts # Base class for value objects
|
|
36
|
-
│ ├── unique-entity-id.ts # UUID v7 identity wrapper
|
|
37
|
-
│ ├── watched-list.ts # Change-tracked collection for aggregates
|
|
38
|
-
│ └── types/
|
|
39
|
-
│ └── optional.ts # Optional<T, K> utility type
|
|
40
|
-
│
|
|
41
|
-
└── domain/
|
|
42
|
-
├── enterprise/ # Pure domain model — no framework deps
|
|
43
|
-
│ ├── entities/
|
|
44
|
-
│ │ ├── entity.ts # Identity-based base entity
|
|
45
|
-
│ │ └── aggregate-root.ts # Event-raising aggregate base
|
|
46
|
-
│ └── events/
|
|
47
|
-
│ ├── domain-event.ts # DomainEvent interface
|
|
48
|
-
│ ├── domain-events.ts # Singleton registry & dispatcher
|
|
49
|
-
│ └── event-handler.ts # EventHandler interface
|
|
50
|
-
│
|
|
51
|
-
└── application/ # Orchestration — use cases & repository contracts
|
|
52
|
-
├── use-cases/
|
|
53
|
-
│ ├── use-case.ts # UseCase<Input, Output> interface
|
|
54
|
-
│ └── use-case.error.ts # Base error type for use case failures
|
|
55
|
-
└── repositories/
|
|
56
|
-
├── repository.ts # Full CRUD contract (composed)
|
|
57
|
-
├── findabe.ts # Findable<T>
|
|
58
|
-
├── creatable.ts # Creatable<T>
|
|
59
|
-
├── saveble.ts # Saveable<T>
|
|
60
|
-
└── deletable.ts # Deletable<T>
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Core Concepts
|
|
64
|
-
|
|
65
|
-
### Either — functional error handling
|
|
66
|
-
|
|
67
|
-
Use cases never throw. They return an `Either<Error, Value>` — left for failure, right for success.
|
|
41
|
+
### Either — handle errors without throwing
|
|
68
42
|
|
|
69
43
|
```ts
|
|
70
|
-
import { Either, left, right } from
|
|
44
|
+
import { Either, left, right } from 'archstone/core'
|
|
71
45
|
|
|
72
46
|
type Result = Either<UserNotFoundError, User>
|
|
73
47
|
|
|
@@ -77,18 +51,20 @@ async function findUser(id: string): Promise<Result> {
|
|
|
77
51
|
return right(user)
|
|
78
52
|
}
|
|
79
53
|
|
|
80
|
-
const result = await findUser(
|
|
81
|
-
if (result.isLeft()) console.error(result.value) // UserNotFoundError
|
|
82
|
-
else console.log(result.value) // User
|
|
83
|
-
```
|
|
54
|
+
const result = await findUser('123')
|
|
84
55
|
|
|
85
|
-
|
|
56
|
+
if (result.isLeft()) {
|
|
57
|
+
console.error(result.value) // UserNotFoundError
|
|
58
|
+
} else {
|
|
59
|
+
console.log(result.value) // User
|
|
60
|
+
}
|
|
61
|
+
```
|
|
86
62
|
|
|
87
|
-
|
|
63
|
+
### Entity & AggregateRoot — model your domain
|
|
88
64
|
|
|
89
65
|
```ts
|
|
90
|
-
import { AggregateRoot } from
|
|
91
|
-
import { UniqueEntityId, Optional } from
|
|
66
|
+
import { AggregateRoot } from 'archstone/domain/enterprise'
|
|
67
|
+
import { UniqueEntityId, Optional } from 'archstone/core'
|
|
92
68
|
|
|
93
69
|
interface OrderProps {
|
|
94
70
|
customerId: UniqueEntityId
|
|
@@ -98,24 +74,23 @@ interface OrderProps {
|
|
|
98
74
|
|
|
99
75
|
class Order extends AggregateRoot<OrderProps> {
|
|
100
76
|
get customerId() { return this.props.customerId }
|
|
101
|
-
get total()
|
|
77
|
+
get total() { return this.props.total }
|
|
102
78
|
|
|
103
|
-
static create(props: Optional<OrderProps,
|
|
104
|
-
const order = new Order(
|
|
105
|
-
|
|
106
|
-
|
|
79
|
+
static create(props: Optional<OrderProps, 'createdAt'>): Order {
|
|
80
|
+
const order = new Order({
|
|
81
|
+
...props,
|
|
82
|
+
createdAt: props.createdAt ?? new Date(),
|
|
83
|
+
})
|
|
107
84
|
order.addDomainEvent(new OrderCreatedEvent(order))
|
|
108
85
|
return order
|
|
109
86
|
}
|
|
110
87
|
}
|
|
111
88
|
```
|
|
112
89
|
|
|
113
|
-
### ValueObject
|
|
114
|
-
|
|
115
|
-
Value objects are equal by their properties, not by reference.
|
|
90
|
+
### ValueObject — equality by value
|
|
116
91
|
|
|
117
92
|
```ts
|
|
118
|
-
import { ValueObject } from
|
|
93
|
+
import { ValueObject } from 'archstone/core'
|
|
119
94
|
|
|
120
95
|
interface EmailProps { value: string }
|
|
121
96
|
|
|
@@ -123,18 +98,20 @@ class Email extends ValueObject<EmailProps> {
|
|
|
123
98
|
get value() { return this.props.value }
|
|
124
99
|
|
|
125
100
|
static create(raw: string): Email {
|
|
126
|
-
if (!raw.includes(
|
|
101
|
+
if (!raw.includes('@')) throw new Error('Invalid email')
|
|
127
102
|
return new Email({ value: raw.toLowerCase() })
|
|
128
103
|
}
|
|
129
104
|
}
|
|
130
|
-
```
|
|
131
105
|
|
|
132
|
-
|
|
106
|
+
const a = Email.create('user@example.com')
|
|
107
|
+
const b = Email.create('user@example.com')
|
|
108
|
+
a.equals(b) // true
|
|
109
|
+
```
|
|
133
110
|
|
|
134
|
-
|
|
111
|
+
### WatchedList — track collection changes
|
|
135
112
|
|
|
136
113
|
```ts
|
|
137
|
-
import { WatchedList } from
|
|
114
|
+
import { WatchedList } from 'archstone/core'
|
|
138
115
|
|
|
139
116
|
class TagList extends WatchedList<Tag> {
|
|
140
117
|
compareItems(a: Tag, b: Tag) { return a.id.equals(b.id) }
|
|
@@ -148,48 +125,82 @@ tags.getNewItems() // [newTag]
|
|
|
148
125
|
tags.getRemovedItems() // [existingTag]
|
|
149
126
|
```
|
|
150
127
|
|
|
151
|
-
### Domain Events
|
|
152
|
-
|
|
153
|
-
Events are raised inside aggregates and dispatched by the infrastructure layer after successful persistence.
|
|
128
|
+
### Domain Events — decouple side effects
|
|
154
129
|
|
|
155
130
|
```ts
|
|
156
|
-
import { DomainEvents } from
|
|
131
|
+
import { DomainEvents } from 'archstone/domain/enterprise'
|
|
157
132
|
|
|
158
|
-
//
|
|
133
|
+
// Register a handler
|
|
159
134
|
DomainEvents.register(
|
|
160
135
|
(event) => sendWelcomeEmail(event as UserCreatedEvent),
|
|
161
136
|
UserCreatedEvent.name,
|
|
162
137
|
)
|
|
163
138
|
|
|
164
|
-
//
|
|
139
|
+
// Dispatch after persisting the aggregate
|
|
165
140
|
await userRepository.create(user)
|
|
166
141
|
DomainEvents.dispatchEventsForAggregate(user.id)
|
|
167
142
|
```
|
|
168
143
|
|
|
169
|
-
### Repository Contracts
|
|
170
|
-
|
|
171
|
-
Repositories are defined as interfaces in the application layer. Implementations live in infrastructure.
|
|
144
|
+
### Repository Contracts — keep infra out of your domain
|
|
172
145
|
|
|
173
146
|
```ts
|
|
174
|
-
import { Repository, Creatable } from
|
|
147
|
+
import { Repository, Creatable } from 'archstone/domain/application'
|
|
175
148
|
|
|
176
|
-
//
|
|
149
|
+
// Compose the interface you need
|
|
177
150
|
export interface UserRepository extends Repository<User> {
|
|
178
151
|
findByEmail(email: string): Promise<User | null>
|
|
179
152
|
}
|
|
180
153
|
|
|
181
|
-
//
|
|
154
|
+
// Or only what you need
|
|
182
155
|
export interface AuditRepository extends Creatable<AuditLog> {}
|
|
183
156
|
```
|
|
184
157
|
|
|
185
|
-
##
|
|
158
|
+
## Package Exports
|
|
186
159
|
|
|
187
|
-
|
|
160
|
+
```
|
|
161
|
+
archstone/core → Either, ValueObject, UniqueEntityId, WatchedList, Optional
|
|
162
|
+
archstone/domain → all domain exports
|
|
163
|
+
archstone/domain/enterprise → Entity, AggregateRoot, DomainEvent, DomainEvents, EventHandler
|
|
164
|
+
archstone/domain/application → UseCase, UseCaseError, repository contracts
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Layer Architecture
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
src/
|
|
171
|
+
├── core/ # Zero domain knowledge — pure utilities
|
|
172
|
+
│ ├── either.ts
|
|
173
|
+
│ ├── value-object.ts
|
|
174
|
+
│ ├── unique-entity-id.ts
|
|
175
|
+
│ ├── watched-list.ts
|
|
176
|
+
│ └── types/optional.ts
|
|
177
|
+
│
|
|
178
|
+
└── domain/
|
|
179
|
+
├── enterprise/ # Pure domain model — no framework deps
|
|
180
|
+
│ ├── entities/
|
|
181
|
+
│ │ ├── entity.ts
|
|
182
|
+
│ │ └── aggregate-root.ts
|
|
183
|
+
│ └── events/
|
|
184
|
+
│ ├── domain-event.ts
|
|
185
|
+
│ ├── domain-events.ts
|
|
186
|
+
│ └── event-handler.ts
|
|
187
|
+
│
|
|
188
|
+
└── application/ # Use cases & repository contracts
|
|
189
|
+
├── use-cases/
|
|
190
|
+
│ ├── use-case.ts
|
|
191
|
+
│ └── use-case.error.ts
|
|
192
|
+
└── repositories/
|
|
193
|
+
├── repository.ts
|
|
194
|
+
├── findable.ts
|
|
195
|
+
├── creatable.ts
|
|
196
|
+
├── saveable.ts
|
|
197
|
+
└── deletable.ts
|
|
198
|
+
```
|
|
188
199
|
|
|
189
|
-
|
|
200
|
+
---
|
|
190
201
|
|
|
191
|
-
|
|
202
|
+
<div align="center">
|
|
192
203
|
|
|
193
|
-
|
|
204
|
+
[Contributing](./CONTRIBUTING.md) · [Code of Conduct](./CODE_OF_CONDUCT.md) · [MIT License](./LICENSE)
|
|
194
205
|
|
|
195
|
-
|
|
206
|
+
</div>
|
package/package.json
CHANGED