archstone 1.0.3 → 1.0.4
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 +96 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,20 +1,51 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# Archstone
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
Stop re-implementing DDD boilerplate. Focus on your domain.
|
|
5
|
+
### The TypeScript foundation for serious backend services.
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
Build on Domain-Driven Design and Clean Architecture — without writing the same boilerplate on every project.
|
|
8
|
+
|
|
9
|
+
<br />
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/archstone)
|
|
12
|
+
[](./LICENSE)
|
|
13
|
+
[](https://www.typescriptlang.org/)
|
|
14
|
+
[](https://bun.sh)
|
|
15
|
+
|
|
16
|
+
<br />
|
|
12
17
|
|
|
13
18
|
</div>
|
|
14
19
|
|
|
15
20
|
---
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
## Why archstone?
|
|
23
|
+
|
|
24
|
+
Every backend project in DDD needs the same structural pieces — and most teams rewrite them from scratch each time. Archstone gives you a **battle-tested, zero-dependency set of base classes and contracts** so you can skip the boilerplate and go straight to modeling your domain.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// ❌ Before — scattered, inconsistent, no error contract
|
|
28
|
+
class User { id: string }
|
|
29
|
+
function createUser() { throw new Error('not found') }
|
|
30
|
+
|
|
31
|
+
// ✅ After — structured, predictable, type-safe
|
|
32
|
+
class User extends AggregateRoot<UserProps> { ... }
|
|
33
|
+
async function createUser(): Promise<Either<NotFoundError, User>> { ... }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **`Either`** — functional error handling; use cases never throw
|
|
41
|
+
- **`Entity` / `AggregateRoot`** — identity-based domain objects with built-in event support
|
|
42
|
+
- **`ValueObject`** — equality by value, not reference
|
|
43
|
+
- **`UniqueEntityId`** — UUID v7 identity, consistent across your entire domain
|
|
44
|
+
- **`WatchedList`** — track additions and removals in collections without overwriting persistence
|
|
45
|
+
- **`UseCase`** — typed contract for application logic
|
|
46
|
+
- **Repository contracts** — define your interface in the domain; implement in infrastructure
|
|
47
|
+
|
|
48
|
+
---
|
|
18
49
|
|
|
19
50
|
## Install
|
|
20
51
|
|
|
@@ -24,43 +55,39 @@ bun add archstone
|
|
|
24
55
|
npm install archstone
|
|
25
56
|
```
|
|
26
57
|
|
|
27
|
-
|
|
58
|
+
> Zero runtime dependencies. Pure TypeScript.
|
|
28
59
|
|
|
29
|
-
|
|
30
|
-
|---|---|
|
|
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 |
|
|
60
|
+
---
|
|
38
61
|
|
|
39
62
|
## Usage
|
|
40
63
|
|
|
41
|
-
### Either —
|
|
64
|
+
### `Either` — stop throwing, start returning
|
|
42
65
|
|
|
43
66
|
```ts
|
|
44
67
|
import { Either, left, right } from 'archstone/core'
|
|
45
68
|
|
|
46
|
-
type
|
|
69
|
+
type FindUserResult = Either<UserNotFoundError, User>
|
|
47
70
|
|
|
48
|
-
async function findUser(id: string): Promise<
|
|
71
|
+
async function findUser(id: string): Promise<FindUserResult> {
|
|
49
72
|
const user = await repo.findById(id)
|
|
73
|
+
|
|
50
74
|
if (!user) return left(new UserNotFoundError(id))
|
|
51
75
|
return right(user)
|
|
52
76
|
}
|
|
53
77
|
|
|
78
|
+
// The caller always handles both cases — no surprises
|
|
54
79
|
const result = await findUser('123')
|
|
55
80
|
|
|
56
81
|
if (result.isLeft()) {
|
|
57
82
|
console.error(result.value) // UserNotFoundError
|
|
58
83
|
} else {
|
|
59
|
-
console.log(result.value) // User
|
|
84
|
+
console.log(result.value) // User ✓
|
|
60
85
|
}
|
|
61
86
|
```
|
|
62
87
|
|
|
63
|
-
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
### `Entity` & `AggregateRoot` — model your domain
|
|
64
91
|
|
|
65
92
|
```ts
|
|
66
93
|
import { AggregateRoot } from 'archstone/domain/enterprise'
|
|
@@ -81,13 +108,17 @@ class Order extends AggregateRoot<OrderProps> {
|
|
|
81
108
|
...props,
|
|
82
109
|
createdAt: props.createdAt ?? new Date(),
|
|
83
110
|
})
|
|
111
|
+
|
|
112
|
+
// Raise domain events from inside the aggregate
|
|
84
113
|
order.addDomainEvent(new OrderCreatedEvent(order))
|
|
85
114
|
return order
|
|
86
115
|
}
|
|
87
116
|
}
|
|
88
117
|
```
|
|
89
118
|
|
|
90
|
-
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### `ValueObject` — equality that makes sense
|
|
91
122
|
|
|
92
123
|
```ts
|
|
93
124
|
import { ValueObject } from 'archstone/core'
|
|
@@ -105,10 +136,13 @@ class Email extends ValueObject<EmailProps> {
|
|
|
105
136
|
|
|
106
137
|
const a = Email.create('user@example.com')
|
|
107
138
|
const b = Email.create('user@example.com')
|
|
108
|
-
|
|
139
|
+
|
|
140
|
+
a.equals(b) // ✅ true — compared by value, not reference
|
|
109
141
|
```
|
|
110
142
|
|
|
111
|
-
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### `WatchedList` — persist only what changed
|
|
112
146
|
|
|
113
147
|
```ts
|
|
114
148
|
import { WatchedList } from 'archstone/core'
|
|
@@ -121,62 +155,74 @@ const tags = new TagList([existingTag])
|
|
|
121
155
|
tags.add(newTag)
|
|
122
156
|
tags.remove(existingTag)
|
|
123
157
|
|
|
124
|
-
|
|
125
|
-
tags.
|
|
158
|
+
// Send only the diff to your repository — not the whole list
|
|
159
|
+
tags.getNewItems() // → [newTag]
|
|
160
|
+
tags.getRemovedItems() // → [existingTag]
|
|
126
161
|
```
|
|
127
162
|
|
|
163
|
+
---
|
|
164
|
+
|
|
128
165
|
### Domain Events — decouple side effects
|
|
129
166
|
|
|
130
167
|
```ts
|
|
131
168
|
import { DomainEvents } from 'archstone/domain/enterprise'
|
|
132
169
|
|
|
133
|
-
// Register
|
|
170
|
+
// Register handlers anywhere in your infrastructure layer
|
|
134
171
|
DomainEvents.register(
|
|
135
172
|
(event) => sendWelcomeEmail(event as UserCreatedEvent),
|
|
136
173
|
UserCreatedEvent.name,
|
|
137
174
|
)
|
|
138
175
|
|
|
139
|
-
// Dispatch after
|
|
176
|
+
// Dispatch after persistence — events stay inside the aggregate until then
|
|
140
177
|
await userRepository.create(user)
|
|
141
178
|
DomainEvents.dispatchEventsForAggregate(user.id)
|
|
142
179
|
```
|
|
143
180
|
|
|
144
|
-
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
### Repository Contracts — keep infrastructure out of your domain
|
|
145
184
|
|
|
146
185
|
```ts
|
|
147
186
|
import { Repository, Creatable } from 'archstone/domain/application'
|
|
148
187
|
|
|
149
|
-
//
|
|
188
|
+
// Define your contract in the application layer
|
|
150
189
|
export interface UserRepository extends Repository<User> {
|
|
151
190
|
findByEmail(email: string): Promise<User | null>
|
|
152
191
|
}
|
|
153
192
|
|
|
154
|
-
//
|
|
193
|
+
// Compose only what you need
|
|
155
194
|
export interface AuditRepository extends Creatable<AuditLog> {}
|
|
195
|
+
|
|
196
|
+
// Implement anywhere in infrastructure — domain stays clean
|
|
156
197
|
```
|
|
157
198
|
|
|
199
|
+
---
|
|
200
|
+
|
|
158
201
|
## Package Exports
|
|
159
202
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
archstone/
|
|
163
|
-
archstone/domain
|
|
164
|
-
archstone/domain/
|
|
165
|
-
|
|
203
|
+
| Import | Contents |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `archstone/core` | `Either`, `ValueObject`, `UniqueEntityId`, `WatchedList`, `Optional` |
|
|
206
|
+
| `archstone/domain` | All domain exports |
|
|
207
|
+
| `archstone/domain/enterprise` | `Entity`, `AggregateRoot`, `DomainEvent`, `DomainEvents`, `EventHandler` |
|
|
208
|
+
| `archstone/domain/application` | `UseCase`, `UseCaseError`, repository contracts |
|
|
209
|
+
|
|
210
|
+
---
|
|
166
211
|
|
|
167
|
-
##
|
|
212
|
+
## Architecture
|
|
168
213
|
|
|
169
214
|
```
|
|
170
215
|
src/
|
|
171
|
-
├── core/
|
|
172
|
-
│ ├── either.ts
|
|
173
|
-
│ ├── value-object.ts
|
|
174
|
-
│ ├── unique-entity-id.ts
|
|
175
|
-
│ ├── watched-list.ts
|
|
176
|
-
│ └── types/
|
|
216
|
+
├── core/ # Zero domain knowledge — pure language utilities
|
|
217
|
+
│ ├── either.ts # Left / Right functional result type
|
|
218
|
+
│ ├── value-object.ts # Value equality base class
|
|
219
|
+
│ ├── unique-entity-id.ts # UUID v7 identity wrapper
|
|
220
|
+
│ ├── watched-list.ts # Change-tracked collection
|
|
221
|
+
│ └── types/
|
|
222
|
+
│ └── optional.ts # Optional<T, K> helper type
|
|
177
223
|
│
|
|
178
224
|
└── domain/
|
|
179
|
-
├── enterprise/
|
|
225
|
+
├── enterprise/ # Pure domain model — zero framework dependencies
|
|
180
226
|
│ ├── entities/
|
|
181
227
|
│ │ ├── entity.ts
|
|
182
228
|
│ │ └── aggregate-root.ts
|
|
@@ -185,7 +231,7 @@ src/
|
|
|
185
231
|
│ ├── domain-events.ts
|
|
186
232
|
│ └── event-handler.ts
|
|
187
233
|
│
|
|
188
|
-
└── application/
|
|
234
|
+
└── application/ # Orchestration — use cases & repository contracts
|
|
189
235
|
├── use-cases/
|
|
190
236
|
│ ├── use-case.ts
|
|
191
237
|
│ └── use-case.error.ts
|
|
@@ -201,6 +247,8 @@ src/
|
|
|
201
247
|
|
|
202
248
|
<div align="center">
|
|
203
249
|
|
|
250
|
+
**Built with care for the TypeScript community.**
|
|
251
|
+
|
|
204
252
|
[Contributing](./CONTRIBUTING.md) · [Code of Conduct](./CODE_OF_CONDUCT.md) · [MIT License](./LICENSE)
|
|
205
253
|
|
|
206
254
|
</div>
|
package/package.json
CHANGED