archstone 1.0.2 → 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.
Files changed (2) hide show
  1. package/README.md +152 -93
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,94 +1,97 @@
1
- # archstone
1
+ <div align="center">
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/archstone?style=flat-square)](https://www.npmjs.com/package/archstone)
4
- [![license: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE)
5
- [![TypeScript](https://img.shields.io/badge/TypeScript-5-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
6
- [![Bun](https://img.shields.io/badge/Bun-runtime-black?style=flat-square&logo=bun)](https://bun.sh)
3
+ # Archstone
7
4
 
8
- > TypeScript architecture foundation for backend services based on Domain-Driven Design (DDD) and Clean Architecture.
5
+ ### The TypeScript foundation for serious backend services.
9
6
 
10
- Archstone provides the core building blocks — entities, value objects, aggregates, domain events, use cases, and repository contractsso you can focus on your domain logic instead of re-implementing the same structural patterns across every project.
7
+ Build on Domain-Driven Design and Clean Architecturewithout writing the same boilerplate on every project.
11
8
 
12
- ## Installation
9
+ <br />
13
10
 
14
- ```bash
15
- bun add archstone
16
- # or
17
- npm install archstone
18
- ```
11
+ [![npm version](https://img.shields.io/npm/v/archstone?style=for-the-badge&logo=npm&color=CB3837&logoColor=white)](https://www.npmjs.com/package/archstone)
12
+ [![license](https://img.shields.io/badge/license-MIT-22C55E?style=for-the-badge)](./LICENSE)
13
+ [![typescript](https://img.shields.io/badge/TypeScript-5-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
14
+ [![bun](https://img.shields.io/badge/Bun-ready-F9F1E1?style=for-the-badge&logo=bun&logoColor=black)](https://bun.sh)
19
15
 
20
- ## Packages
16
+ <br />
21
17
 
22
- | Import path | Contents |
23
- |---|---|
24
- | `archstone/core` | `Either`, `ValueObject`, `UniqueEntityId`, `WatchedList`, `Optional` |
25
- | `archstone/domain` | All domain exports |
26
- | `archstone/domain/enterprise` | `Entity`, `AggregateRoot`, `DomainEvent`, `DomainEvents`, `EventHandler` |
27
- | `archstone/domain/application` | `UseCase`, `UseCaseError`, repository contracts |
18
+ </div>
28
19
 
29
- ## Architecture
20
+ ---
21
+
22
+ ## Why archstone?
30
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>> { ... }
31
34
  ```
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>
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
+ ---
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ bun add archstone
54
+ # or
55
+ npm install archstone
61
56
  ```
62
57
 
63
- ## Core Concepts
58
+ > Zero runtime dependencies. Pure TypeScript.
64
59
 
65
- ### Either — functional error handling
60
+ ---
66
61
 
67
- Use cases never throw. They return an `Either<Error, Value>` — left for failure, right for success.
62
+ ## Usage
63
+
64
+ ### `Either` — stop throwing, start returning
68
65
 
69
66
  ```ts
70
- import { Either, left, right } from "archstone/core"
67
+ import { Either, left, right } from 'archstone/core'
71
68
 
72
- type Result = Either<UserNotFoundError, User>
69
+ type FindUserResult = Either<UserNotFoundError, User>
73
70
 
74
- async function findUser(id: string): Promise<Result> {
71
+ async function findUser(id: string): Promise<FindUserResult> {
75
72
  const user = await repo.findById(id)
73
+
76
74
  if (!user) return left(new UserNotFoundError(id))
77
75
  return right(user)
78
76
  }
79
77
 
80
- const result = await findUser("123")
81
- if (result.isLeft()) console.error(result.value) // UserNotFoundError
82
- else console.log(result.value) // User
78
+ // The caller always handles both cases — no surprises
79
+ const result = await findUser('123')
80
+
81
+ if (result.isLeft()) {
82
+ console.error(result.value) // UserNotFoundError
83
+ } else {
84
+ console.log(result.value) // User ✓
85
+ }
83
86
  ```
84
87
 
85
- ### Entity & AggregateRoot
88
+ ---
86
89
 
87
- Entities are defined by identity. Aggregates extend that with domain event support.
90
+ ### `Entity` & `AggregateRoot` model your domain
88
91
 
89
92
  ```ts
90
- import { AggregateRoot } from "archstone/domain/enterprise"
91
- import { UniqueEntityId, Optional } from "archstone/core"
93
+ import { AggregateRoot } from 'archstone/domain/enterprise'
94
+ import { UniqueEntityId, Optional } from 'archstone/core'
92
95
 
93
96
  interface OrderProps {
94
97
  customerId: UniqueEntityId
@@ -98,24 +101,27 @@ interface OrderProps {
98
101
 
99
102
  class Order extends AggregateRoot<OrderProps> {
100
103
  get customerId() { return this.props.customerId }
101
- get total() { return this.props.total }
104
+ get total() { return this.props.total }
102
105
 
103
- static create(props: Optional<OrderProps, "createdAt">): Order {
104
- const order = new Order(
105
- { ...props, createdAt: props.createdAt ?? new Date() },
106
- )
106
+ static create(props: Optional<OrderProps, 'createdAt'>): Order {
107
+ const order = new Order({
108
+ ...props,
109
+ createdAt: props.createdAt ?? new Date(),
110
+ })
111
+
112
+ // Raise domain events from inside the aggregate
107
113
  order.addDomainEvent(new OrderCreatedEvent(order))
108
114
  return order
109
115
  }
110
116
  }
111
117
  ```
112
118
 
113
- ### ValueObject
119
+ ---
114
120
 
115
- Value objects are equal by their properties, not by reference.
121
+ ### `ValueObject` equality that makes sense
116
122
 
117
123
  ```ts
118
- import { ValueObject } from "archstone/core"
124
+ import { ValueObject } from 'archstone/core'
119
125
 
120
126
  interface EmailProps { value: string }
121
127
 
@@ -123,18 +129,23 @@ class Email extends ValueObject<EmailProps> {
123
129
  get value() { return this.props.value }
124
130
 
125
131
  static create(raw: string): Email {
126
- if (!raw.includes("@")) throw new Error("Invalid email")
132
+ if (!raw.includes('@')) throw new Error('Invalid email')
127
133
  return new Email({ value: raw.toLowerCase() })
128
134
  }
129
135
  }
136
+
137
+ const a = Email.create('user@example.com')
138
+ const b = Email.create('user@example.com')
139
+
140
+ a.equals(b) // ✅ true — compared by value, not reference
130
141
  ```
131
142
 
132
- ### WatchedList
143
+ ---
133
144
 
134
- Track additions and removals in a collection without rewriting the whole thing on save.
145
+ ### `WatchedList` persist only what changed
135
146
 
136
147
  ```ts
137
- import { WatchedList } from "archstone/core"
148
+ import { WatchedList } from 'archstone/core'
138
149
 
139
150
  class TagList extends WatchedList<Tag> {
140
151
  compareItems(a: Tag, b: Tag) { return a.id.equals(b.id) }
@@ -144,52 +155,100 @@ const tags = new TagList([existingTag])
144
155
  tags.add(newTag)
145
156
  tags.remove(existingTag)
146
157
 
147
- tags.getNewItems() // [newTag]
148
- tags.getRemovedItems() // [existingTag]
158
+ // Send only the diff to your repository — not the whole list
159
+ tags.getNewItems() // [newTag]
160
+ tags.getRemovedItems() // → [existingTag]
149
161
  ```
150
162
 
151
- ### Domain Events
163
+ ---
152
164
 
153
- Events are raised inside aggregates and dispatched by the infrastructure layer after successful persistence.
165
+ ### Domain Events decouple side effects
154
166
 
155
167
  ```ts
156
- import { DomainEvents } from "archstone/domain/enterprise"
168
+ import { DomainEvents } from 'archstone/domain/enterprise'
157
169
 
158
- // register a handler
170
+ // Register handlers anywhere in your infrastructure layer
159
171
  DomainEvents.register(
160
172
  (event) => sendWelcomeEmail(event as UserCreatedEvent),
161
173
  UserCreatedEvent.name,
162
174
  )
163
175
 
164
- // infrastructure dispatches after persisting
176
+ // Dispatch after persistence — events stay inside the aggregate until then
165
177
  await userRepository.create(user)
166
178
  DomainEvents.dispatchEventsForAggregate(user.id)
167
179
  ```
168
180
 
169
- ### Repository Contracts
181
+ ---
170
182
 
171
- Repositories are defined as interfaces in the application layer. Implementations live in infrastructure.
183
+ ### Repository Contracts keep infrastructure out of your domain
172
184
 
173
185
  ```ts
174
- import { Repository, Creatable } from "archstone/domain/application"
186
+ import { Repository, Creatable } from 'archstone/domain/application'
175
187
 
176
- // application/repositories/user-repository.ts
188
+ // Define your contract in the application layer
177
189
  export interface UserRepository extends Repository<User> {
178
190
  findByEmail(email: string): Promise<User | null>
179
191
  }
180
192
 
181
- // or compose only what you need
193
+ // Compose only what you need
182
194
  export interface AuditRepository extends Creatable<AuditLog> {}
195
+
196
+ // Implement anywhere in infrastructure — domain stays clean
183
197
  ```
184
198
 
185
- ## Contributing
199
+ ---
200
+
201
+ ## Package Exports
202
+
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
+ ---
211
+
212
+ ## Architecture
213
+
214
+ ```
215
+ src/
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
223
+
224
+ └── domain/
225
+ ├── enterprise/ # Pure domain model — zero framework dependencies
226
+ │ ├── entities/
227
+ │ │ ├── entity.ts
228
+ │ │ └── aggregate-root.ts
229
+ │ └── events/
230
+ │ ├── domain-event.ts
231
+ │ ├── domain-events.ts
232
+ │ └── event-handler.ts
233
+
234
+ └── application/ # Orchestration — use cases & repository contracts
235
+ ├── use-cases/
236
+ │ ├── use-case.ts
237
+ │ └── use-case.error.ts
238
+ └── repositories/
239
+ ├── repository.ts
240
+ ├── findable.ts
241
+ ├── creatable.ts
242
+ ├── saveable.ts
243
+ └── deletable.ts
244
+ ```
186
245
 
187
- Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md) first.
246
+ ---
188
247
 
189
- ## Code of Conduct
248
+ <div align="center">
190
249
 
191
- This project follows the [Contributor Covenant](./CODE_OF_CONDUCT.md).
250
+ **Built with care for the TypeScript community.**
192
251
 
193
- ## License
252
+ [Contributing](./CONTRIBUTING.md) · [Code of Conduct](./CODE_OF_CONDUCT.md) · [MIT License](./LICENSE)
194
253
 
195
- MIT — see [LICENSE](./LICENSE).
254
+ </div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "archstone",
3
3
  "description": "TypeScript architecture foundation for backend services based on Domain-Driven Design and Clean Architecture",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
5
5
  "type": "module",
6
6
  "private": false,
7
7
  "files": [