@vytches/ddd-aggregates 0.26.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 VytchesDDD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LLMGUIDE.md ADDED
@@ -0,0 +1,185 @@
1
+ # @vytches/ddd-aggregates - LLM Guide
2
+
3
+ ## Purpose
4
+
5
+ Provides the `AggregateRoot` base class and a capability system for composing
6
+ event sourcing, snapshots, audit logging, and versioning into aggregates without
7
+ inheritance chains.
8
+
9
+ ## Quick Start
10
+
11
+ ```typescript
12
+ import { AggregateRoot, AggregateBuilder } from '@vytches/ddd-aggregates';
13
+ import { EntityId } from '@vytches/ddd-value-objects';
14
+ import type { IAggregateConstructorParams } from '@vytches/ddd-aggregates';
15
+
16
+ interface OrderCreatedPayload {
17
+ customerId: string;
18
+ amount: number;
19
+ }
20
+
21
+ class Order extends AggregateRoot<string> {
22
+ private customerId = '';
23
+ private amount = 0;
24
+
25
+ constructor(params: IAggregateConstructorParams<string>) {
26
+ super(params);
27
+ this.registerEventHandler<OrderCreatedPayload>('OrderCreated', payload => {
28
+ this.customerId = payload!.customerId;
29
+ this.amount = payload!.amount;
30
+ });
31
+ }
32
+
33
+ static create(customerId: string, amount: number): Order {
34
+ const id = EntityId.create();
35
+ const order = new Order({ id, version: 0 });
36
+ order.apply('OrderCreated', { customerId, amount });
37
+ return order;
38
+ }
39
+ }
40
+
41
+ // Usage
42
+ const order = Order.create('c-1', 500);
43
+ const events = order.getDomainEvents(); // [{ eventName: 'OrderCreated', ... }]
44
+ order.commit(); // clear pending events after saving
45
+ ```
46
+
47
+ ## Key API
48
+
49
+ | Export | Kind | Description |
50
+ | -------------------------------------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `AggregateRoot<TId>` | class | Base aggregate root; manages version, events, capabilities — the _transactional_ root |
52
+ | `Entity<TId>` | abstract class | Base for **non-root** domain entities (VF-CANON-001) — identity-based equality, no version/event machinery. Use for `OrderLine`, inner aggregate entities |
53
+ | `AggregateBuilder<TId>` | class | Fluent builder for constructing aggregates with capabilities |
54
+ | `aggregateBuilder(params)` | function | Shorthand factory for `AggregateBuilder.create(params)` |
55
+ | `AggregateError` | class | Domain error for aggregate-level failures |
56
+ | `SnapshotCapability` | class | Enables snapshot creation and restoration |
57
+ | `AuditCapability` | class | Attaches an audit log to every event applied |
58
+ | `VersioningCapability` | class | Registers event upcasters for schema evolution |
59
+ | `EventSourcingCapability` | class | Integrates with `IEventStore` for persistence |
60
+ | `asSnapshotAggregate(agg)` | function | Casts aggregate, throws if no `SnapshotCapability` |
61
+ | `asAuditAggregate(agg)` | function | Casts aggregate, throws if no `AuditCapability` |
62
+ | `hasAllCapabilities(agg, [...])` | function | Returns `true` if aggregate has every listed capability |
63
+ | `getAggregateCapabilities(agg)` | function | Returns string array of capability type names |
64
+ | `AggregateWithSnapshotCapability<TId>` | type | Type-safe alias for snapshot-capable aggregates |
65
+ | `IAggregateRoot<TId>` | interface | Full contract for aggregates |
66
+
67
+ ### `AggregateRoot` method reference
68
+
69
+ | Method | Description |
70
+ | --------------------------------------- | ------------------------------------------------------------------------- |
71
+ | `apply(eventType, payload?, metadata?)` | Record a domain event, increment version, call handler |
72
+ | `loadFromHistory(events)` | Replay historical events to restore state without accumulating new events |
73
+ | `getDomainEvents()` | Return readonly array of uncommitted events since last `commit()` |
74
+ | `commit()` | Clear uncommitted events; call after persisting to storage |
75
+ | `hasChanges()` | `true` if there are uncommitted events |
76
+ | `getVersion()` | Current version after all applied events |
77
+ | `getInitialVersion()` | Version when last loaded from storage (for optimistic locking) |
78
+ | `addCapability(cap)` | Attach a capability; calls `cap.attach(this)` |
79
+ | `getCapability(CapClass)` | Retrieve capability by constructor, returns `undefined` if absent |
80
+ | `hasCapability(CapClass)` | Type-safe capability presence check |
81
+ | `registerEventHandler(type, fn)` | Protected; wire event name to state mutation function |
82
+
83
+ ## Patterns
84
+
85
+ ### Event sourcing reconstitution
86
+
87
+ ```typescript
88
+ // In your repository, after loading events from the store:
89
+ const order = new Order({ id: EntityId.fromUUID(rawId), version: 0 });
90
+ order['loadFromHistory'](storedEvents); // protected; call from within the class or a static factory
91
+ // If loadFromHistory must be called externally, expose a static fromEvents():
92
+ class Order extends AggregateRoot<string> {
93
+ static fromEvents(id: EntityId<string>, events: IDomainEvent[]): Order {
94
+ const order = new Order({ id, version: 0 });
95
+ order.loadFromHistory(events);
96
+ return order;
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Using the builder with capabilities
102
+
103
+ ```typescript
104
+ import {
105
+ AggregateBuilder,
106
+ SnapshotCapability,
107
+ AuditCapability,
108
+ } from '@vytches/ddd-aggregates';
109
+ import { EntityId } from '@vytches/ddd-value-objects';
110
+
111
+ const order = AggregateBuilder.create({ id: EntityId.create() })
112
+ .withSnapshots()
113
+ .withAudit()
114
+ .build(Order);
115
+
116
+ // Now safe to cast:
117
+ import { asSnapshotAggregate } from '@vytches/ddd-aggregates';
118
+ const snapshotOrder = asSnapshotAggregate(order);
119
+ const snap = snapshotOrder
120
+ .getCapability(SnapshotCapability)!
121
+ .createSnapshot(() => ({ amount: order.getAmount() }));
122
+ ```
123
+
124
+ ### Capability type guard before use
125
+
126
+ ```typescript
127
+ import {
128
+ hasAllCapabilities,
129
+ asAuditAggregate,
130
+ AuditCapability,
131
+ } from '@vytches/ddd-aggregates';
132
+
133
+ if (hasAllCapabilities(order, [AuditCapability])) {
134
+ const auditable = asAuditAggregate(order);
135
+ const log = auditable.getCapability(AuditCapability)!.getAuditLog();
136
+ }
137
+ ```
138
+
139
+ ## Anti-Patterns
140
+
141
+ **Mutating state directly instead of using `apply()`.** Assigning
142
+ `this.status = 'paid'` bypasses event recording and version tracking. All state
143
+ changes must go through `apply()` so they are captured as events.
144
+
145
+ **Not registering event handlers before calling `apply()`.**
146
+ `registerEventHandler` must be called in the constructor before any `apply()`
147
+ calls. If handlers are missing, events are recorded but state is not updated —
148
+ no error is thrown.
149
+
150
+ **Forgetting to call `super(params)` in subclass constructor.** Without the
151
+ super call, `_id`, `_version`, and internal maps are never initialized.
152
+ TypeScript will not always catch this at compile time.
153
+
154
+ **Using `loadFromHistory` on a live aggregate that already has uncommitted
155
+ events.** `loadFromHistory` resets `_domainEvents` to an empty array and replays
156
+ from `_initialVersion`. Calling it after `apply()` discards pending events
157
+ silently.
158
+
159
+ **Adding capabilities after `apply()` calls.** Capabilities like
160
+ `AuditCapability` only audit events applied after they are attached. Add all
161
+ capabilities in the constructor or builder before any business methods are
162
+ called.
163
+
164
+ ## Hidden Features
165
+
166
+ `apply()` accepts either a plain string event type or a full `IDomainEvent`
167
+ object — pass a class-based event instance and the prototype chain is preserved,
168
+ enabling `instanceof` checks on events after enrichment.
169
+
170
+ `getAggregateInfo(aggregate)` (from utilities) returns a single diagnostic
171
+ object with `id`, `type`, `version`, `hasChanges`, `capabilities`, and `events`
172
+ count — useful for logging without exposing internals.
173
+
174
+ `aggregateBuilder(...).buildWithAllCapabilities(MyClass)` attaches all four
175
+ built-in capabilities (snapshot, versioning, audit, event sourcing) in one call.
176
+
177
+ `_internal_setState` exists on `AggregateRoot` and is intended only for
178
+ capability implementations — never call it from domain code.
179
+
180
+ ## Package Dependencies
181
+
182
+ **Depends on:** `@vytches/ddd-contracts`, `@vytches/ddd-logging`.
183
+
184
+ **Depended on by:** `@vytches/ddd-enterprise` (re-exports), consumer domain
185
+ packages.