@xeno-js/shared 0.2.0 → 0.2.1
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 +18 -12
- package/README.md +652 -268
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
3
|
Copyright (c) 2026 Xeno
|
|
4
4
|
|
|
5
|
-
Permission
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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/README.md
CHANGED
|
@@ -1,268 +1,652 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<img src="logo/logo.png" alt="Xeno Shared Logo" width="140" />
|
|
3
|
-
|
|
4
|
-
<h1
|
|
5
|
-
|
|
6
|
-
<p><
|
|
7
|
-
|
|
8
|
-
<p>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="logo/logo.png" alt="Xeno Shared Logo" width="140" />
|
|
3
|
+
|
|
4
|
+
<h1>@xeno-js/shared</h1>
|
|
5
|
+
|
|
6
|
+
<p><strong>Domain primitives and application contracts for TypeScript.</strong></p>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
Define your domain model, application contracts, and architectural boundaries
|
|
10
|
+
without coupling them to a transport or framework.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p>
|
|
14
|
+
<a href="https://github.com/xeno-js/xeno-shared">
|
|
15
|
+
<img src="https://img.shields.io/github/stars/xeno-js/xeno-shared?style=flat-square" alt="GitHub Stars" />
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://www.npmjs.com/package/@xeno-js/shared">
|
|
18
|
+
<img src="https://img.shields.io/npm/v/@xeno-js/shared?style=flat-square" alt="npm version" />
|
|
19
|
+
</a>
|
|
20
|
+
<a href="https://github.com/xeno-js/xeno-shared/blob/develop/LICENSE">
|
|
21
|
+
<img src="https://img.shields.io/npm/l/@xeno-js/shared?style=flat-square" alt="License: ISC" />
|
|
22
|
+
</a>
|
|
23
|
+
<a href="https://buymeacoffee.com/xenojs">
|
|
24
|
+
<img src="https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-FFdd00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee" />
|
|
25
|
+
</a>
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What is `@xeno-js/shared`?
|
|
32
|
+
|
|
33
|
+
`@xeno-js/shared` provides the **domain primitives and framework-neutral
|
|
34
|
+
contracts** used across the Xeno ecosystem.
|
|
35
|
+
|
|
36
|
+
It gives TypeScript applications explicit building blocks for:
|
|
37
|
+
|
|
38
|
+
- Domain-Driven Design
|
|
39
|
+
- aggregates and value objects
|
|
40
|
+
- domain events
|
|
41
|
+
- entities and domain errors
|
|
42
|
+
- Result-based application flows
|
|
43
|
+
- CQRS contracts
|
|
44
|
+
- repositories and data sources
|
|
45
|
+
- application services and policies
|
|
46
|
+
- request and execution context
|
|
47
|
+
- transactions and infrastructure boundaries
|
|
48
|
+
|
|
49
|
+
The goal is simple:
|
|
50
|
+
|
|
51
|
+
> **Keep the meaning of your application explicit in code.**
|
|
52
|
+
|
|
53
|
+
`@xeno-js/shared` defines the contracts.
|
|
54
|
+
|
|
55
|
+
`@xeno-js/core` provides the runtime architecture that executes them.
|
|
56
|
+
|
|
57
|
+
Your HTTP framework, CLI, worker, or other transport remains outside that
|
|
58
|
+
boundary.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## The Xeno architecture
|
|
63
|
+
|
|
64
|
+
Xeno separates **what an application means** from **how the application runs**.
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
┌─────────────────────────────────────────────┐
|
|
68
|
+
│ Transport / Host │
|
|
69
|
+
│ HTTP · CLI · Worker · gRPC · Scheduler │
|
|
70
|
+
└──────────────────────┬──────────────────────┘
|
|
71
|
+
│
|
|
72
|
+
▼
|
|
73
|
+
┌─────────────────────────────────────────────┐
|
|
74
|
+
│ @xeno-js/core │
|
|
75
|
+
│ │
|
|
76
|
+
│ DI · scopes · request context · pipelines │
|
|
77
|
+
│ CQRS execution · modules · infrastructure │
|
|
78
|
+
└──────────────────────┬──────────────────────┘
|
|
79
|
+
│
|
|
80
|
+
▼
|
|
81
|
+
┌─────────────────────────────────────────────┐
|
|
82
|
+
│ @xeno-js/shared │
|
|
83
|
+
│ │
|
|
84
|
+
│ Domain model · contracts · Result · errors │
|
|
85
|
+
│ aggregates · value objects · domain events │
|
|
86
|
+
│ application interfaces │
|
|
87
|
+
└─────────────────────────────────────────────┘
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This separation lets the domain and application contracts remain independent
|
|
91
|
+
from the transport hosting them.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Why Shared?
|
|
96
|
+
|
|
97
|
+
Most application frameworks start from the transport:
|
|
98
|
+
|
|
99
|
+
```text
|
|
100
|
+
HTTP request
|
|
101
|
+
↓
|
|
102
|
+
controller
|
|
103
|
+
↓
|
|
104
|
+
service
|
|
105
|
+
↓
|
|
106
|
+
database
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Xeno starts from the application model instead:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
Domain
|
|
113
|
+
↓
|
|
114
|
+
Application contracts
|
|
115
|
+
↓
|
|
116
|
+
Execution model
|
|
117
|
+
↓
|
|
118
|
+
Transport
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
That distinction matters when an application grows.
|
|
122
|
+
|
|
123
|
+
The HTTP layer should not define your domain model.
|
|
124
|
+
|
|
125
|
+
Your database should not define your application contracts.
|
|
126
|
+
|
|
127
|
+
And your infrastructure should not become the place where business rules live.
|
|
128
|
+
|
|
129
|
+
`@xeno-js/shared` provides the primitives and contracts that make those
|
|
130
|
+
boundaries explicit.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
# Domain primitives
|
|
135
|
+
|
|
136
|
+
## Aggregate roots
|
|
137
|
+
|
|
138
|
+
`AggregateRoot` provides a base abstraction for aggregates that need:
|
|
139
|
+
|
|
140
|
+
- an explicit identity
|
|
141
|
+
- aggregate versioning
|
|
142
|
+
- domain event application
|
|
143
|
+
- loading from event history
|
|
144
|
+
- tracking of uncommitted domain events.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import { AggregateRoot } from '@xeno-js/shared'
|
|
148
|
+
|
|
149
|
+
class UserId {
|
|
150
|
+
// ...
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
type UserEvent =
|
|
154
|
+
| {
|
|
155
|
+
type: 'UserCreated'
|
|
156
|
+
name: string
|
|
157
|
+
}
|
|
158
|
+
| {
|
|
159
|
+
type: 'UserRenamed'
|
|
160
|
+
name: string
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class User extends AggregateRoot<UserEvent> {
|
|
164
|
+
private name = ''
|
|
165
|
+
|
|
166
|
+
public rename(name: string): void {
|
|
167
|
+
this.raise({
|
|
168
|
+
eventType: 'UserRenamed',
|
|
169
|
+
payload: {
|
|
170
|
+
type: 'UserRenamed',
|
|
171
|
+
name,
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
protected apply(event: IDomainEvent<UserEvent>, isNew: boolean): void {
|
|
177
|
+
switch (event.eventType) {
|
|
178
|
+
case 'UserRenamed':
|
|
179
|
+
this.name = event.payload.name
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
An aggregate keeps its domain changes explicit:
|
|
187
|
+
|
|
188
|
+
```text
|
|
189
|
+
Aggregate
|
|
190
|
+
│
|
|
191
|
+
├── identity
|
|
192
|
+
├── version
|
|
193
|
+
├── state
|
|
194
|
+
│
|
|
195
|
+
└── uncommitted events
|
|
196
|
+
│
|
|
197
|
+
▼
|
|
198
|
+
IDomainEvent
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`AggregateRoot` does not provide an event store. It provides the aggregate-side
|
|
202
|
+
primitives required to model and track domain events.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Domain events
|
|
207
|
+
|
|
208
|
+
`IDomainEvent` defines a framework-neutral representation of an event produced
|
|
209
|
+
by an aggregate.
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
export interface IDomainEvent<
|
|
213
|
+
TPayload = unknown,
|
|
214
|
+
TValueObject extends object = object,
|
|
215
|
+
> {
|
|
216
|
+
readonly aggregateId: TValueObject
|
|
217
|
+
readonly eventType: string
|
|
218
|
+
readonly version: number
|
|
219
|
+
readonly occurredAt: Date
|
|
220
|
+
readonly payload: TPayload
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
A domain event carries:
|
|
225
|
+
|
|
226
|
+
- the aggregate identity
|
|
227
|
+
- an explicit event type
|
|
228
|
+
- the aggregate version
|
|
229
|
+
- the occurrence timestamp
|
|
230
|
+
- the event payload.
|
|
231
|
+
|
|
232
|
+
This makes domain changes representable without coupling the domain model to an
|
|
233
|
+
HTTP server, database driver, or message broker.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Value objects
|
|
238
|
+
|
|
239
|
+
Value objects provide domain concepts whose meaning comes from their value
|
|
240
|
+
rather than object identity.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { ValueObject } from '@xeno-js/shared'
|
|
244
|
+
|
|
245
|
+
interface EmailProps {
|
|
246
|
+
value: string
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
class Email extends ValueObject<EmailProps> {
|
|
250
|
+
public static create(value: string): Email {
|
|
251
|
+
return new Email({ value })
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
The base implementation provides:
|
|
257
|
+
|
|
258
|
+
- immutable properties
|
|
259
|
+
- value retrieval
|
|
260
|
+
- equality comparison
|
|
261
|
+
- string representation.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
const first = Email.create('user@example.com')
|
|
265
|
+
const second = Email.create('user@example.com')
|
|
266
|
+
|
|
267
|
+
first.equals(second) // true
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
# Application contracts
|
|
273
|
+
|
|
274
|
+
The package also defines contracts used to keep application code independent
|
|
275
|
+
from concrete infrastructure.
|
|
276
|
+
|
|
277
|
+
These include abstractions for areas such as:
|
|
278
|
+
|
|
279
|
+
- CQRS
|
|
280
|
+
- repositories
|
|
281
|
+
- data sources
|
|
282
|
+
- services
|
|
283
|
+
- factories
|
|
284
|
+
- policies
|
|
285
|
+
- transactions
|
|
286
|
+
- request context
|
|
287
|
+
- middleware
|
|
288
|
+
- logging
|
|
289
|
+
- caching
|
|
290
|
+
- storage
|
|
291
|
+
- HTTP
|
|
292
|
+
- mapping
|
|
293
|
+
- idempotency.
|
|
294
|
+
|
|
295
|
+
The important distinction is between the **contract** and its implementation.
|
|
296
|
+
|
|
297
|
+
For example:
|
|
298
|
+
|
|
299
|
+
```text
|
|
300
|
+
Application
|
|
301
|
+
│
|
|
302
|
+
│ depends on
|
|
303
|
+
▼
|
|
304
|
+
Repository contract
|
|
305
|
+
│
|
|
306
|
+
│ implemented by
|
|
307
|
+
▼
|
|
308
|
+
Infrastructure adapter
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The application therefore does not need to know whether data is stored in
|
|
312
|
+
PostgreSQL, Supabase, Redis, or another persistence mechanism.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
# CQRS contracts
|
|
317
|
+
|
|
318
|
+
`@xeno-js/shared` includes the contracts used to model commands and queries.
|
|
319
|
+
|
|
320
|
+
```text
|
|
321
|
+
Command
|
|
322
|
+
│
|
|
323
|
+
▼
|
|
324
|
+
Application handler
|
|
325
|
+
│
|
|
326
|
+
▼
|
|
327
|
+
Domain
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
and:
|
|
331
|
+
|
|
332
|
+
```text
|
|
333
|
+
Query
|
|
334
|
+
│
|
|
335
|
+
▼
|
|
336
|
+
Application handler
|
|
337
|
+
│
|
|
338
|
+
▼
|
|
339
|
+
Read model / data source
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The package defines the contracts.
|
|
343
|
+
|
|
344
|
+
`@xeno-js/core` provides the execution infrastructure around them.
|
|
345
|
+
|
|
346
|
+
This distinction keeps CQRS from becoming tied to a particular transport.
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
# Result and errors
|
|
351
|
+
|
|
352
|
+
Application code often needs to represent an expected failure without turning
|
|
353
|
+
every business outcome into an exception.
|
|
354
|
+
|
|
355
|
+
Xeno provides `Result` primitives alongside application/domain errors.
|
|
356
|
+
|
|
357
|
+
Conceptually:
|
|
358
|
+
|
|
359
|
+
```text
|
|
360
|
+
Operation
|
|
361
|
+
│
|
|
362
|
+
├── success → Result success
|
|
363
|
+
│
|
|
364
|
+
└── expected failure → Result failure
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
This allows application boundaries to make outcomes explicit while keeping error
|
|
368
|
+
handling independent from the HTTP layer.
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
# Runtime utilities
|
|
373
|
+
|
|
374
|
+
The package also contains shared runtime utilities used across the Xeno
|
|
375
|
+
ecosystem.
|
|
376
|
+
|
|
377
|
+
These include utilities for areas such as:
|
|
378
|
+
|
|
379
|
+
- runtime guards
|
|
380
|
+
- strings
|
|
381
|
+
- dates
|
|
382
|
+
- enumerables
|
|
383
|
+
- GUIDs
|
|
384
|
+
- promises
|
|
385
|
+
- HTTP helpers
|
|
386
|
+
- sanitization
|
|
387
|
+
- abort handling
|
|
388
|
+
- mathematical helpers.
|
|
389
|
+
|
|
390
|
+
These utilities are deliberately secondary to the architectural role of the
|
|
391
|
+
package.
|
|
392
|
+
|
|
393
|
+
The purpose of `@xeno-js/shared` is not to be a generic utility collection.
|
|
394
|
+
|
|
395
|
+
Its primary role is to provide **shared domain and application building
|
|
396
|
+
blocks**.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
# Infrastructure adapters
|
|
401
|
+
|
|
402
|
+
`@xeno-js/shared` also exports a limited set of reusable infrastructure
|
|
403
|
+
components, including integrations and adapters for areas such as:
|
|
404
|
+
|
|
405
|
+
- HTTP clients
|
|
406
|
+
- Supabase authentication
|
|
407
|
+
- caching
|
|
408
|
+
- storage
|
|
409
|
+
- validation
|
|
410
|
+
- mapping
|
|
411
|
+
- factories.
|
|
412
|
+
|
|
413
|
+
These are exported as reusable building blocks; they do not define the
|
|
414
|
+
architecture of the application.
|
|
415
|
+
|
|
416
|
+
For applications using `@xeno-js/core`, infrastructure can be composed through
|
|
417
|
+
the application architecture rather than becoming part of the domain model.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
# Framework independent by design
|
|
422
|
+
|
|
423
|
+
`@xeno-js/shared` does not define an HTTP application lifecycle.
|
|
424
|
+
|
|
425
|
+
You can model your domain and application contracts without choosing a
|
|
426
|
+
particular HTTP framework.
|
|
427
|
+
|
|
428
|
+
For example:
|
|
429
|
+
|
|
430
|
+
```text
|
|
431
|
+
┌── Fastify
|
|
432
|
+
│
|
|
433
|
+
├── Express
|
|
434
|
+
Application ─────┼── Hono
|
|
435
|
+
│
|
|
436
|
+
├── CLI
|
|
437
|
+
│
|
|
438
|
+
└── Worker
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
The transport is the host.
|
|
442
|
+
|
|
443
|
+
The domain and application contracts remain the application model.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
# Installation
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
npm install @xeno-js/shared
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
For the complete Xeno application architecture:
|
|
454
|
+
|
|
455
|
+
```bash
|
|
456
|
+
npm install @xeno-js/core
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
You can use `@xeno-js/shared` independently when you only need the domain and
|
|
460
|
+
application building blocks.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
# `@xeno-js/shared` vs `@xeno-js/core`
|
|
465
|
+
|
|
466
|
+
The two packages have different responsibilities.
|
|
467
|
+
|
|
468
|
+
| Package | Responsibility |
|
|
469
|
+
| ------------------- | --------------------------------------------------- |
|
|
470
|
+
| `@xeno-js/shared` | Domain primitives and application contracts |
|
|
471
|
+
| `@xeno-js/core` | Application runtime and architecture |
|
|
472
|
+
| Your transport | HTTP, CLI, worker, gRPC, etc. |
|
|
473
|
+
| Your infrastructure | Database, cache, external services, messaging, etc. |
|
|
474
|
+
|
|
475
|
+
A useful mental model is:
|
|
476
|
+
|
|
477
|
+
```text
|
|
478
|
+
@xeno-js/shared
|
|
479
|
+
defines the language
|
|
480
|
+
|
|
481
|
+
↓
|
|
482
|
+
|
|
483
|
+
@xeno-js/core
|
|
484
|
+
executes the architecture
|
|
485
|
+
|
|
486
|
+
↓
|
|
487
|
+
|
|
488
|
+
your application
|
|
489
|
+
defines the business behavior
|
|
490
|
+
|
|
491
|
+
↓
|
|
492
|
+
|
|
493
|
+
your transport / infrastructure
|
|
494
|
+
hosts and connects the system
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
# What `@xeno-js/shared` is not
|
|
500
|
+
|
|
501
|
+
`@xeno-js/shared` is not:
|
|
502
|
+
|
|
503
|
+
- an HTTP framework
|
|
504
|
+
- an application server
|
|
505
|
+
- an ORM
|
|
506
|
+
- an event bus
|
|
507
|
+
- an event store
|
|
508
|
+
- a complete event-sourcing framework
|
|
509
|
+
- a replacement for your transport framework.
|
|
510
|
+
|
|
511
|
+
It provides the primitives and contracts that let those concerns remain
|
|
512
|
+
separated from the domain model.
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
# Design principles
|
|
517
|
+
|
|
518
|
+
The package follows a few simple principles.
|
|
519
|
+
|
|
520
|
+
### Explicit contracts
|
|
521
|
+
|
|
522
|
+
Important application boundaries should be represented by explicit TypeScript
|
|
523
|
+
contracts.
|
|
524
|
+
|
|
525
|
+
### Domain first
|
|
526
|
+
|
|
527
|
+
Business concepts such as aggregates, value objects and domain events should not
|
|
528
|
+
depend on transport details.
|
|
529
|
+
|
|
530
|
+
### Infrastructure at the boundary
|
|
531
|
+
|
|
532
|
+
Concrete integrations belong outside the domain model.
|
|
533
|
+
|
|
534
|
+
### Framework independence
|
|
535
|
+
|
|
536
|
+
Domain and application contracts should not require a specific HTTP framework.
|
|
537
|
+
|
|
538
|
+
### Composition over magic
|
|
539
|
+
|
|
540
|
+
The architecture should be understandable from the code rather than depending on
|
|
541
|
+
runtime discovery or hidden conventions.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
# Relationship with Xeno
|
|
546
|
+
|
|
547
|
+
The Xeno ecosystem can be understood as three layers:
|
|
548
|
+
|
|
549
|
+
```text
|
|
550
|
+
Your application
|
|
551
|
+
│
|
|
552
|
+
▼
|
|
553
|
+
┌───────────────────┐
|
|
554
|
+
│ @xeno-js/core │
|
|
555
|
+
│ │
|
|
556
|
+
│ Runtime │
|
|
557
|
+
│ DI │
|
|
558
|
+
│ Scopes │
|
|
559
|
+
│ CQRS execution │
|
|
560
|
+
│ Pipelines │
|
|
561
|
+
│ Request context │
|
|
562
|
+
└─────────┬─────────┘
|
|
563
|
+
│
|
|
564
|
+
▼
|
|
565
|
+
┌───────────────────┐
|
|
566
|
+
│ @xeno-js/shared │
|
|
567
|
+
│ │
|
|
568
|
+
│ Domain │
|
|
569
|
+
│ Contracts │
|
|
570
|
+
│ Result / Errors │
|
|
571
|
+
│ Aggregates │
|
|
572
|
+
│ Value Objects │
|
|
573
|
+
│ Domain Events │
|
|
574
|
+
└───────────────────┘
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
The transport sits around the application rather than defining it.
|
|
578
|
+
|
|
579
|
+
> **Shared defines the contracts. Core executes the architecture. Your transport
|
|
580
|
+
> hosts the application.**
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
# Development
|
|
585
|
+
|
|
586
|
+
Clone the repository:
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
git clone https://github.com/xeno-js/xeno-shared.git
|
|
590
|
+
cd xeno-shared
|
|
591
|
+
npm install
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Run the main checks:
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
npm run check
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Available scripts:
|
|
601
|
+
|
|
602
|
+
| Command | Description |
|
|
603
|
+
| ----------------------- | ---------------------------- |
|
|
604
|
+
| `npm run build` | Build the package |
|
|
605
|
+
| `npm run typecheck` | Run TypeScript type checking |
|
|
606
|
+
| `npm run lint` | Run ESLint |
|
|
607
|
+
| `npm run format` | Format the repository |
|
|
608
|
+
| `npm run format:check` | Check formatting |
|
|
609
|
+
| `npm run test` | Run tests |
|
|
610
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
611
|
+
| `npm run test:coverage` | Run tests with coverage |
|
|
612
|
+
| `npm run check` | Typecheck, lint and test |
|
|
613
|
+
| `npm run changelog` | Generate the changelog |
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
# Contributing
|
|
618
|
+
|
|
619
|
+
Contributions are welcome.
|
|
620
|
+
|
|
621
|
+
Create a feature or fix branch from `develop`:
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
git checkout develop
|
|
625
|
+
git pull origin develop
|
|
626
|
+
git checkout -b feat/your-feature
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Before opening a pull request:
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
npm run check
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
Use Conventional Commits:
|
|
636
|
+
|
|
637
|
+
```text
|
|
638
|
+
feat(domain): add aggregate primitive
|
|
639
|
+
fix(result): correct failure handling
|
|
640
|
+
refactor(events): simplify event contract
|
|
641
|
+
docs(readme): improve architecture documentation
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Pull requests should target `develop`.
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
# License
|
|
649
|
+
|
|
650
|
+
ISC License.
|
|
651
|
+
|
|
652
|
+
Copyright (c) 2026 Xeno.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xeno-js/shared",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Domain primitives and framework-neutral application contracts for TypeScript, including DDD, CQRS, Result types, errors, value objects, aggregates and domain events.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"contracts"
|
|
65
65
|
],
|
|
66
66
|
"author": "Xeno",
|
|
67
|
-
"license": "
|
|
67
|
+
"license": "MIT",
|
|
68
68
|
"engines": {
|
|
69
69
|
"node": ">=20.0.0"
|
|
70
70
|
},
|