@xeno-js/core 0.1.9 → 0.1.11
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 +341 -321
- package/dist/index.cjs +267 -458
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -234
- package/dist/index.d.ts +17 -234
- package/dist/index.js +247 -438
- package/dist/index.js.map +1 -1
- package/package.json +171 -160
package/README.md
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
<img src="logo/logo.png" alt="Xeno Logo" width="140" />
|
|
3
3
|
|
|
4
|
-
<h1>Xeno
|
|
5
|
-
|
|
6
|
-
<p
|
|
4
|
+
<h1>Xeno.JS</h1>
|
|
5
|
+
<p><strong>The application architecture framework for TypeScript.</strong></p>
|
|
6
|
+
<p>Build long-lived applications with explicit dependency injection, DDD, CQRS, and transport-independent business logic.</p>
|
|
7
7
|
|
|
8
8
|
<p>
|
|
9
|
-
<a href="https://
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<a href="https://github.com/xeno-js/xeno-js/blob/main/LICENSE">
|
|
13
|
-
<img src="https://img.shields.io/npm/l/@xeno?style=flat-square" alt="License: ISC" />
|
|
14
|
-
</a>
|
|
15
|
-
<a href="https://www.npmjs.com/package/@xeno-js/core">
|
|
16
|
-
<img src="https://img.shields.io/npm/v/@xeno-js/core?style=flat-square" alt="NPM Version" />
|
|
17
|
-
</a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@xeno-js/core"><img src="https://img.shields.io/npm/v/@xeno-js/core?style=flat-square" alt="NPM Version" /></a>
|
|
10
|
+
<a href="https://github.com/xeno-js/xeno-js"><img src="https://img.shields.io/badge/Powered%20by-Xeno-blueviolet?style=flat-square" alt="Powered by Xeno" /></a>
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="License: MIT" /></a>
|
|
18
12
|
<a href="https://buymeacoffee.com/xenojs">
|
|
19
13
|
<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" />
|
|
20
14
|
</a>
|
|
@@ -25,393 +19,419 @@
|
|
|
25
19
|
|
|
26
20
|
## What is Xeno?
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
Node.js built natively with TypeScript. It provides structural primitives for
|
|
30
|
-
implementing robust **Domain-Driven Design (DDD)** and **Command Query
|
|
31
|
-
Responsibility Segregation (CQRS)** patterns. By shifting operational logic away
|
|
32
|
-
from delivery mechanisms and transport frameworks, Xeno ensures your core
|
|
33
|
-
application architecture remains pristine, testable, and completely isolated
|
|
34
|
-
from external infrastructural churn.
|
|
22
|
+
Xeno is a TypeScript application architecture framework for Node.js.
|
|
35
23
|
|
|
36
|
-
|
|
24
|
+
It provides explicit building blocks for applications organized around:
|
|
25
|
+
|
|
26
|
+
- **Dependency Injection** with explicit service registration and lifetimes
|
|
27
|
+
- **Domain-Driven Design (DDD)** and domain/application boundaries
|
|
28
|
+
- **CQRS** with commands, queries, handlers, and composable pipelines
|
|
29
|
+
- **Request context** built around asynchronous execution context
|
|
30
|
+
- **Repositories and data sources** that keep persistence behind application
|
|
31
|
+
boundaries
|
|
32
|
+
- **Infrastructure adapters** for databases, Redis, authentication, logging,
|
|
33
|
+
resilience, and other integrations
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
with an emphasis on developer experience, type safety, and clean separation of
|
|
43
|
-
concerns.
|
|
44
|
-
|
|
45
|
-
- **Zero Decorators**: Xeno eliminates reliance on experimental or unstable TS
|
|
46
|
-
decorator specifications (`reflect-metadata`). The IoC container
|
|
47
|
-
(`ServiceContainer`) uses pure, explicit functional factories that optimize
|
|
48
|
-
compilation speeds and eliminate runtime black-box behaviors.
|
|
49
|
-
- **Complete Server Decoupling**: Xeno does not care if you use Fastify, Hono,
|
|
50
|
-
Express, Koa, or AWS Lambda. The presentation layer handles incoming data
|
|
51
|
-
using plain, primitive contracts, making migration or multi-runtime hosting
|
|
52
|
-
completely seamless.
|
|
53
|
-
- **Pay-For-What-You-Use (Opt-in Modularity)**: Core dependencies are
|
|
54
|
-
strategically classified as optional peer dependencies. If your architecture
|
|
55
|
-
doesn't use Redis, Sentry, or Supabase, you do not pull them into your node
|
|
56
|
-
modules.
|
|
57
|
-
- **Enterprise-Grade Resiliency & Cross-Cutting Pipelines**: Address complex
|
|
58
|
-
distributed patterns natively without code duplication. Xeno provides
|
|
59
|
-
out-of-the-box composite behaviors:
|
|
60
|
-
- **Idempotency**: Implements multi-tenant logic keyspaces matching advanced
|
|
61
|
-
SaaS factory patterns for logical partitioning.
|
|
62
|
-
- **Concurrency Control**: Mitigates thundering herd impacts via advanced
|
|
63
|
-
backoff retry strategies coupled with randomized jitter.
|
|
64
|
-
- **Resilience Policies**: Deep integration with circuit breakers, bulkheads,
|
|
65
|
-
and fallbacks.
|
|
66
|
-
- **Deterministic Type Safety**: Strong infrastructure validation strategies
|
|
67
|
-
using Zod schemas.
|
|
35
|
+
The goal is simple: **make application architecture explicit in code.**
|
|
36
|
+
|
|
37
|
+
Xeno is not tied to a specific HTTP server. Your application layer can remain
|
|
38
|
+
independent from the transport that delivers a request.
|
|
68
39
|
|
|
69
40
|
---
|
|
70
41
|
|
|
71
|
-
##
|
|
42
|
+
## Why Xeno?
|
|
72
43
|
|
|
73
|
-
|
|
74
|
-
workflows of Xeno, read the full technical manuals located inside the main
|
|
75
|
-
documentation hub:
|
|
44
|
+
### 01 — Explicit Architecture
|
|
76
45
|
|
|
77
|
-
|
|
46
|
+
**Your dependency graph is code.**
|
|
78
47
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
48
|
+
Xeno does not require decorators, runtime scanning, or implicit dependency
|
|
49
|
+
discovery. Services are registered explicitly, and their lifetimes are visible
|
|
50
|
+
at the composition root.
|
|
82
51
|
|
|
83
|
-
|
|
52
|
+
```typescript
|
|
53
|
+
services.addScoped('USER_REPOSITORY', (container) => {
|
|
54
|
+
return new UserRepository(
|
|
55
|
+
container.resolve('USER_DATA_SOURCE'),
|
|
56
|
+
container.resolve('USER_MAPPER'),
|
|
57
|
+
)
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This makes the composition of the application easier to inspect, test, and
|
|
62
|
+
reason about.
|
|
63
|
+
|
|
64
|
+
### 02 — Transport Independence
|
|
65
|
+
|
|
66
|
+
Business logic should not belong to your HTTP framework.
|
|
67
|
+
|
|
68
|
+
Xeno keeps application concerns separate from delivery mechanisms, allowing the
|
|
69
|
+
same application architecture to be hosted behind transports such as Fastify,
|
|
70
|
+
Hono, Express, or other adapters.
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
HTTP / CLI / Worker / Lambda
|
|
74
|
+
|
|
|
75
|
+
v
|
|
76
|
+
Presentation
|
|
77
|
+
|
|
|
78
|
+
v
|
|
79
|
+
Application
|
|
80
|
+
Commands / Queries
|
|
81
|
+
|
|
|
82
|
+
v
|
|
83
|
+
Domain
|
|
84
|
+
|
|
|
85
|
+
v
|
|
86
|
+
Infrastructure
|
|
87
|
+
DB / Redis / APIs
|
|
88
|
+
```
|
|
84
89
|
|
|
85
|
-
|
|
90
|
+
### 03 — CQRS as an Application Primitive
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
showcasing end-to-end command/query segregation, multi-tenant databases, and
|
|
89
|
-
resilient schema handling.
|
|
92
|
+
Commands and queries are first-class application concepts.
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
sandbox environments:
|
|
94
|
+
Pipelines can compose cross-cutting behavior around execution, such as:
|
|
93
95
|
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
- authorization
|
|
97
|
+
- idempotency
|
|
98
|
+
- concurrency control
|
|
99
|
+
- caching
|
|
100
|
+
- resilience policies
|
|
101
|
+
- request context
|
|
102
|
+
|
|
103
|
+
This keeps cross-cutting concerns out of individual handlers.
|
|
104
|
+
|
|
105
|
+
### 04 — Explicit Lifetimes and Request Boundaries
|
|
106
|
+
|
|
107
|
+
Xeno distinguishes service lifetimes such as singleton, scoped, and transient
|
|
108
|
+
services.
|
|
109
|
+
|
|
110
|
+
Request-scoped dependencies are resolved inside an explicit application scope,
|
|
111
|
+
while request metadata can be carried through asynchronous execution using
|
|
112
|
+
`AsyncLocalStorage`.
|
|
113
|
+
|
|
114
|
+
Database transaction state is scoped to the same application boundary,
|
|
115
|
+
allowing `UnitOfWork` and `DbContext` to operate against the transaction
|
|
116
|
+
associated with the current scope.
|
|
117
|
+
|
|
118
|
+
### 05 — Infrastructure Stays Outside the Domain
|
|
119
|
+
|
|
120
|
+
Database clients, Redis, HTTP clients, authentication providers, loggers, and
|
|
121
|
+
other infrastructure integrations are composed at the edge of the application.
|
|
122
|
+
|
|
123
|
+
Your domain and application code can depend on contracts instead of concrete
|
|
124
|
+
infrastructure.
|
|
98
125
|
|
|
99
126
|
---
|
|
100
127
|
|
|
101
|
-
##
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
A typical Xeno application can be organized like this:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
+------------------------------------------+
|
|
134
|
+
| Presentation |
|
|
135
|
+
| HTTP / CLI / Workers / Lambda |
|
|
136
|
+
+---------------------+--------------------+
|
|
137
|
+
|
|
|
138
|
+
v
|
|
139
|
+
+------------------------------------------+
|
|
140
|
+
| Application |
|
|
141
|
+
| Commands / Queries / Handlers / Pipes |
|
|
142
|
+
+---------------------+--------------------+
|
|
143
|
+
|
|
|
144
|
+
v
|
|
145
|
+
+------------------------------------------+
|
|
146
|
+
| Domain |
|
|
147
|
+
| Entities / Policies / Rules |
|
|
148
|
+
+---------------------+--------------------+
|
|
149
|
+
|
|
|
150
|
+
v
|
|
151
|
+
+------------------------------------------+
|
|
152
|
+
| Infrastructure |
|
|
153
|
+
| DB / Redis / APIs / Auth / Logs |
|
|
154
|
+
+------------------------------------------+
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Xeno's core is focused on composition and application architecture.
|
|
158
|
+
Infrastructure capabilities can be enabled only when they are needed.
|
|
159
|
+
|
|
160
|
+
---
|
|
102
161
|
|
|
103
|
-
|
|
162
|
+
## Core Concepts
|
|
163
|
+
|
|
164
|
+
| Concept | Purpose |
|
|
165
|
+
| ------------------ | --------------------------------------------------------- |
|
|
166
|
+
| `AppBuilder` | Composition root for assembling an application |
|
|
167
|
+
| `ServiceContainer` | Explicit dependency injection and service lifetimes |
|
|
168
|
+
| `CQRS` | Commands, queries, handlers, and mediator-based execution |
|
|
169
|
+
| `Pipelines` | Cross-cutting behavior around application execution |
|
|
170
|
+
| `Request Context` | Request metadata across asynchronous execution |
|
|
171
|
+
| `Repository` | Application-facing persistence abstraction |
|
|
172
|
+
| `DataSource` | Infrastructure-facing data access implementation |
|
|
173
|
+
| `Module` | Explicit registration of related capabilities |
|
|
174
|
+
| `Result` | Typed success/failure flow for application operations |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Installation
|
|
104
179
|
|
|
105
180
|
```bash
|
|
106
181
|
npm install @xeno-js/core
|
|
107
|
-
|
|
108
182
|
```
|
|
109
183
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
184
|
+
Install only the integrations your application uses. Xeno exposes optional
|
|
185
|
+
infrastructure dependencies for capabilities such as databases, Redis, logging,
|
|
186
|
+
resilience, and authentication.
|
|
187
|
+
|
|
188
|
+
For example:
|
|
113
189
|
|
|
114
190
|
```bash
|
|
115
|
-
# Example: Install tools only if you enable them in the builder
|
|
116
191
|
npm install zod pino cockatiel drizzle-orm
|
|
117
|
-
|
|
118
192
|
```
|
|
119
193
|
|
|
120
194
|
---
|
|
121
195
|
|
|
122
|
-
##
|
|
123
|
-
|
|
124
|
-
Below is an architectural example of how to configure the Xeno
|
|
125
|
-
`ServiceContainer`, load core modules, and process an incoming application
|
|
126
|
-
payload natively inside a server middleware wrapper.
|
|
196
|
+
## A Small Example
|
|
127
197
|
|
|
128
|
-
|
|
198
|
+
The composition root is explicit:
|
|
129
199
|
|
|
130
200
|
```typescript
|
|
131
|
-
import { AppBuilder
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Map your registry token with XenoRegistry<TSchemaDb, TExtension>
|
|
139
|
-
type MyRegistry = XenoRegistry<{ /** Your Db Schema here **/}, {
|
|
140
|
-
USER_MAPPER_TOKEN: UserMapper
|
|
141
|
-
USER_DS_TOKEN: UserDataSource
|
|
142
|
-
USER_REPOSITORY_TOKEN: UserWriteRepository
|
|
143
|
-
FIND_USER_QUERY_HANDLER_TOKEN: FindUserQueryHandler
|
|
144
|
-
FIND_USER_CONTROLLER_TOKEN: FindUserController
|
|
145
|
-
}>
|
|
146
|
-
|
|
147
|
-
// Create the root IoC container context
|
|
148
|
-
export const xeno = new AppBuilder<MyRegistry>()
|
|
149
|
-
// Configure middleware and only PUBLIC routes
|
|
150
|
-
.addMiddlewares(opts => {
|
|
151
|
-
opts.routeRegistry = {
|
|
152
|
-
'/api/user/:id': ['GET', 'UPDATE', 'DELETE'],
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
// Configure the CQRS pipeline
|
|
156
|
-
// Can register Policies for your intent
|
|
157
|
-
.addPipeline((config) => {
|
|
158
|
-
config.authorization.policies = {
|
|
159
|
-
'FIND_USER_QUERY_HANDLER_TOKEN': {
|
|
160
|
-
// Add authz by user id
|
|
161
|
-
userId: true
|
|
162
|
-
// Add authz by tenant id
|
|
163
|
-
tenantId: true
|
|
164
|
-
// Add authz by roles
|
|
165
|
-
roles: ['admin']
|
|
166
|
-
// Add authz by perissions
|
|
167
|
-
permissions: ['read'],
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Can add idempotency pipeline for command
|
|
171
|
-
config.commandBus.idempotency = { lockTtlSeconds: 30, processedTtlSeconds: 60 }
|
|
172
|
-
// Can add concurrency pipeline for command
|
|
173
|
-
config.commandBus.concurrency = { delayConfig: { baseDelayMs: 100, maxJitterMs: 500 }, maxRetries: 3 }
|
|
174
|
-
// Can add caching pipeline for query
|
|
175
|
-
config.queryBus.isEnabled = true
|
|
176
|
-
})
|
|
177
|
-
// Configure Database with drizzle
|
|
178
|
-
.addDb((opts, config) => {
|
|
179
|
-
opts.connectionString = config.getOrThrow('DATABASE_URL')
|
|
180
|
-
})
|
|
181
|
-
// Configure Authentication with supabase
|
|
182
|
-
.addAuth((opts, config) => {
|
|
183
|
-
opts.key = 'demo-key'
|
|
184
|
-
opts.url = config.getOrThrow('API_BASE_URL')
|
|
185
|
-
})
|
|
186
|
-
// Configure your logger (e.g. Console, Sentry, Pino or custom logger)
|
|
187
|
-
.addLogger((config) => {
|
|
188
|
-
config.level = LOG_LEVEL.INFO
|
|
189
|
-
config.console = true
|
|
190
|
-
})
|
|
191
|
-
// Register your services
|
|
192
|
-
.addServices((services) => {
|
|
193
|
-
// REGISTER MAPPER
|
|
194
|
-
services.addScoped('USER_MAPPER_TOKEN', () => new UserMapper())
|
|
195
|
-
|
|
196
|
-
// REGISTER DATASOURCES
|
|
197
|
-
services.addScoped('USER_DS_TOKEN', (c) => new UserDataSource(c.resolve(TOKENS.DB_CONTEXT)))
|
|
198
|
-
|
|
199
|
-
// REGISTER REPOSITORIES
|
|
200
|
-
services.addScoped('USER_REPOSITORY_TOKEN', (c) => new UserWriteRepository(c.resolve('USER_DS_TOKEN'), c.resolve('USER_MAPPER_TOKEN')))
|
|
201
|
-
|
|
202
|
-
// REGISTER HANDLERS
|
|
203
|
-
services.addScoped('FIND_USER_QUERY_HANDLER_TOKEN', (c) => {
|
|
204
|
-
const requestcontext = c.resolve('USER_CONTEXT_FACTORY')
|
|
205
|
-
const repository = c.resolve('USER_READ_REPOSITORY')
|
|
206
|
-
return new FindUserQueryHandler(repository, requestcontext)
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
// REGISTER CONTROLLERS
|
|
210
|
-
services.addTransient('FIND_USER_CONTROLLER_TOKEN', (c) => {
|
|
211
|
-
return new FindUserController(c.resolve(TOKENS.CONTEXT_ACCESSOR), c.resolve(TOKENS.MEDIATOR))
|
|
212
|
-
})
|
|
213
|
-
})
|
|
201
|
+
import { AppBuilder } from '@xeno-js/core'
|
|
202
|
+
|
|
203
|
+
const app = new AppBuilder().addServices((services) => {
|
|
204
|
+
services.addScoped('USER_REPOSITORY', (container) => {
|
|
205
|
+
return new UserRepository(container.resolve('USER_DATA_SOURCE'))
|
|
206
|
+
})
|
|
214
207
|
|
|
208
|
+
services.addScoped('FIND_USER_HANDLER', (container) => {
|
|
209
|
+
return new FindUserHandler(container.resolve('USER_REPOSITORY'))
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
services.addTransient('FIND_USER_CONTROLLER', (c) => {
|
|
213
|
+
return new FindUserHandler(c.resolve(TOKENS.REQUEST_CONTEXT), c.resolve(TOKENS.MEDIATOR))
|
|
214
|
+
})
|
|
215
|
+
})
|
|
215
216
|
```
|
|
216
217
|
|
|
217
|
-
|
|
218
|
+
The transport remains outside the application composition:
|
|
219
|
+
|
|
220
|
+
> ⚠️ **Implementation note: Example using Fastify**
|
|
221
|
+
> The following snippet uses **Fastify** solely for demonstration purposes to illustrate the transport layer. Thanks to the framework's agnostic architecture, the underlying logic (`container` and `handler`) remains unchanged regardless of the chosen HTTP system (e.g., Express, Koa) or interface (CLI, gRPC).
|
|
218
222
|
|
|
219
223
|
```typescript
|
|
220
|
-
import '
|
|
221
|
-
import {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
async
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
console.log('🚀 Starting Fastify server on http://localhost:3000...')
|
|
232
|
-
|
|
233
|
-
// 2. Resolve the middleware from the container
|
|
234
|
-
const middleware = xeno.resolve(TOKENS.MIDDLEWARE)
|
|
235
|
-
// 3. Create a Fastify instance to handle HTTP requests
|
|
236
|
-
const app = fastify()
|
|
237
|
-
|
|
238
|
-
// ─── ENDPOINT 2: QUERY ────────────────────────────────────────────
|
|
239
|
-
app.get('/api/user/:id', async (request, reply) => {
|
|
240
|
-
// 4. Execute the middleware to handle the request context and authentication, then call the StatusController's handle method with the request payload.
|
|
241
|
-
const responseDto = await middleware.execute(
|
|
242
|
-
{
|
|
243
|
-
path: '/api/user/:id',
|
|
244
|
-
method: 'GET',
|
|
245
|
-
transport: { res: reply, req: request },
|
|
246
|
-
},
|
|
247
|
-
{ ...request.headers },
|
|
248
|
-
async () => {
|
|
249
|
-
const { id } = request.params as any
|
|
250
|
-
const controller = ContainerUtils.resolveServiceScoped(
|
|
251
|
-
'FIND_USER_CONTROLLER_TOKEN',
|
|
252
|
-
xenoApp,
|
|
253
|
-
)
|
|
254
|
-
return await controller.handle({ id: id ?? '123' })
|
|
255
|
-
},
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
return reply
|
|
259
|
-
.status(responseDto.status)
|
|
260
|
-
.type('application/json')
|
|
261
|
-
.send(responseDto.data)
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
console.log('✅ Routes set up. Ready to accept requests.')
|
|
265
|
-
|
|
266
|
-
// ─── START SERVER ─────────────────────────────────────────────────
|
|
267
|
-
try {
|
|
268
|
-
await app.listen({ port: 3000 })
|
|
269
|
-
console.log('🚀 Application running on http://localhost:3000')
|
|
270
|
-
console.log('👉 GET /api/user/:id (GET: api/user/1)')
|
|
271
|
-
} catch (err) {
|
|
272
|
-
console.error('Error starting Fastify server:', err)
|
|
273
|
-
app.log.error(err)
|
|
274
|
-
process.exit(1)
|
|
275
|
-
}
|
|
276
|
-
} catch (error) {
|
|
277
|
-
console.error('Error during bootstrap or server setup:', error)
|
|
278
|
-
process.exit(1)
|
|
224
|
+
import Fastify from 'fastify';
|
|
225
|
+
import { builder } from './bootstrap';
|
|
226
|
+
|
|
227
|
+
const app = Fastify({ logger: true });
|
|
228
|
+
|
|
229
|
+
app.get('/users/:id', async (req, reply) => {
|
|
230
|
+
const endpoint = req.url
|
|
231
|
+
const container = await builder.build();
|
|
232
|
+
const action = async () => {
|
|
233
|
+
const controller = ContainerUtils.resolveServiceScoped('FIND_USER_CONTROLLER', container)
|
|
234
|
+
return await controller.handle()
|
|
279
235
|
}
|
|
280
|
-
}
|
|
281
236
|
|
|
282
|
-
|
|
237
|
+
const result = await ContainerUtils.runExecute(endpoint, req.method, req.headers, { reply, req }, container, action)
|
|
238
|
+
|
|
239
|
+
return reply.send(result)
|
|
240
|
+
})
|
|
283
241
|
```
|
|
284
242
|
|
|
243
|
+
The HTTP adapter is responsible for HTTP. The application handler is responsible
|
|
244
|
+
for the use case.
|
|
245
|
+
|
|
285
246
|
---
|
|
286
247
|
|
|
287
|
-
##
|
|
248
|
+
## CQRS & Pipelines
|
|
288
249
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
250
|
+
Cross-cutting behavior can be composed around commands and queries:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
.addPipeline((config) => {
|
|
254
|
+
config.authorization.policies = {
|
|
255
|
+
FIND_USER_QUERY_HANDLER: {
|
|
256
|
+
roles: ['admin'],
|
|
257
|
+
permissions: ['read'],
|
|
258
|
+
},
|
|
259
|
+
}
|
|
293
260
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
261
|
+
config.commandBus.idempotency = {
|
|
262
|
+
lockTtlSeconds: 30,
|
|
263
|
+
processedTtlSeconds: 60,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
config.commandBus.concurrency = {
|
|
267
|
+
delayConfig: {
|
|
268
|
+
baseDelayMs: 100,
|
|
269
|
+
maxJitterMs: 500,
|
|
270
|
+
},
|
|
271
|
+
maxRetries: 3,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
config.queryBus.isEnabled = true
|
|
275
|
+
})
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The exact pipeline configuration depends on the integrations enabled by your
|
|
279
|
+
application.
|
|
297
280
|
|
|
298
281
|
---
|
|
299
282
|
|
|
300
|
-
##
|
|
283
|
+
## Infrastructure & Integrations
|
|
301
284
|
|
|
302
|
-
|
|
303
|
-
stability of the core framework, **direct pushes to the `main` and `develop`
|
|
304
|
-
branches are strictly prohibited.** Please follow this Git Flow to contribute:
|
|
285
|
+
Xeno Core can be composed with infrastructure such as:
|
|
305
286
|
|
|
306
|
-
|
|
307
|
-
|
|
287
|
+
- **Database:** Drizzle ORM, PostgreSQL, LibSQL
|
|
288
|
+
- **Cache / distributed coordination:** Redis
|
|
289
|
+
- **Authentication:** Supabase integrations and custom strategies
|
|
290
|
+
- **HTTP clients:** Axios
|
|
291
|
+
- **Resilience:** Cockatiel
|
|
292
|
+
- **Logging:** Console, Pino, Sentry, or custom loggers
|
|
293
|
+
- **Validation:** Zod
|
|
308
294
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
295
|
+
These integrations are opt-in rather than mandatory parts of the application
|
|
296
|
+
architecture.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## CLI
|
|
314
301
|
|
|
315
|
-
|
|
316
|
-
(linting, types, and tests).
|
|
302
|
+
Use the official CLI to scaffold a Xeno application:
|
|
317
303
|
|
|
318
304
|
```bash
|
|
319
|
-
npm
|
|
305
|
+
npm install @xeno-js/cli
|
|
306
|
+
xeno-js new my-xeno-app --core
|
|
320
307
|
```
|
|
321
308
|
|
|
322
|
-
|
|
323
|
-
[Conventional Commits](https://www.conventionalcommits.org/). Husky will
|
|
324
|
-
verify your commit message format.
|
|
309
|
+
See the [CLI documentation](https://www.xeno-js.it/cli/overview).
|
|
325
310
|
|
|
326
|
-
|
|
311
|
+
---
|
|
327
312
|
|
|
328
|
-
|
|
329
|
-
feat(scope): add new feature
|
|
330
|
-
fix(scope): resolve bug
|
|
331
|
-
chore(scope): update dependencies
|
|
332
|
-
```
|
|
313
|
+
## Documentation
|
|
333
314
|
|
|
334
|
-
|
|
335
|
-
Request targeting the **`develop`** branch.
|
|
315
|
+
The documentation hub contains the architecture and integration guides:
|
|
336
316
|
|
|
337
|
-
|
|
338
|
-
and merge it into `develop`.
|
|
317
|
+
**[xeno-js.it](https://www.xeno-js.it/introduction)**
|
|
339
318
|
|
|
340
|
-
|
|
341
|
-
flows from feature branches ➡️ `develop` ➡️ `main`._
|
|
319
|
+
Recommended starting points:
|
|
342
320
|
|
|
343
|
-
|
|
321
|
+
- [Introduction](https://www.xeno-js.it/introduction)
|
|
322
|
+
- [Architecture](https://www.xeno-js.it/architecture)
|
|
323
|
+
- [Dependency Injection](https://www.xeno-js.it/architecture/dependency-injection)
|
|
324
|
+
- [CQRS](https://www.xeno-js.it/architecture/cqrs)
|
|
325
|
+
- [Pipelines](https://www.xeno-js.it/architecture/pipelines)
|
|
326
|
+
- [Request Lifecycle](https://www.xeno-js.it/architecture/request-lifecycle)
|
|
327
|
+
- [Modules](https://www.xeno-js.it/architecture/modules)
|
|
328
|
+
- [CLI](https://www.xeno-js.it/cli/overview)
|
|
344
329
|
|
|
345
|
-
|
|
346
|
-
| ----------------------- | ---------------------------------------------- |
|
|
347
|
-
| `npm run build` | Builds the TypeScript source code into `dist/` |
|
|
348
|
-
| `npm run typecheck` | Checks types without emitting files |
|
|
349
|
-
| `npm run lint` | Runs ESLint |
|
|
350
|
-
| `npm run format` | Formats code with Prettier |
|
|
351
|
-
| `npm run test` | Runs the Vitest test suite |
|
|
352
|
-
| `npm run test:coverage` | Runs tests and generates a coverage report |
|
|
330
|
+
---
|
|
353
331
|
|
|
354
|
-
|
|
332
|
+
## Ecosystem
|
|
355
333
|
|
|
356
|
-
|
|
357
|
-
repository:
|
|
334
|
+
Xeno is designed as an ecosystem rather than a single monolithic package:
|
|
358
335
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
336
|
+
| Package | Role |
|
|
337
|
+
| ----------------- | ----------------------------------------- |
|
|
338
|
+
| `@xeno-js/core` | Application architecture and backend core |
|
|
339
|
+
| `@xeno-js/shared` | Shared contracts and types |
|
|
340
|
+
| `@xeno-js/vue` | Vue integration |
|
|
341
|
+
| `@xeno-js/cli` | Project scaffolding and developer tooling |
|
|
364
342
|
|
|
365
343
|
---
|
|
366
344
|
|
|
367
|
-
##
|
|
345
|
+
## What Xeno Is Not
|
|
346
|
+
|
|
347
|
+
Xeno is not primarily an HTTP framework.
|
|
368
348
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
349
|
+
If you are looking for a framework centered on routing, controllers, middleware,
|
|
350
|
+
and server lifecycle, there are excellent options already available in the
|
|
351
|
+
Node.js ecosystem.
|
|
372
352
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
353
|
+
Xeno focuses on the layer above transport:
|
|
354
|
+
|
|
355
|
+
> **How should a TypeScript application be structured so that its business
|
|
356
|
+
> logic, dependencies, and infrastructure boundaries remain explicit as the
|
|
357
|
+
> application grows?**
|
|
358
|
+
|
|
359
|
+
---
|
|
378
360
|
|
|
379
|
-
|
|
380
|
-
commitment of our community to keep the project independent and thriving.
|
|
381
|
-
Whether you are an individual developer or a business using Xeno, your support
|
|
382
|
-
makes a real difference.
|
|
361
|
+
## Production Considerations
|
|
383
362
|
|
|
384
|
-
|
|
385
|
-
|
|
363
|
+
Xeno provides architectural primitives, but application correctness still
|
|
364
|
+
depends on how those primitives are composed.
|
|
386
365
|
|
|
387
|
-
|
|
366
|
+
Before deploying an application, test the behaviors that matter to your
|
|
367
|
+
workload, especially:
|
|
388
368
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
369
|
+
- request and transaction isolation
|
|
370
|
+
- service lifetime boundaries
|
|
371
|
+
- authorization policies
|
|
372
|
+
- idempotency semantics
|
|
373
|
+
- concurrency behavior
|
|
374
|
+
- cache consistency
|
|
375
|
+
- failure and retry behavior
|
|
376
|
+
- trusted proxy / client IP configuration
|
|
377
|
+
- database transaction boundaries
|
|
378
|
+
|
|
379
|
+
The framework is designed to make these boundaries explicit rather than hide
|
|
380
|
+
them behind conventions.
|
|
394
381
|
|
|
395
382
|
---
|
|
396
383
|
|
|
397
|
-
##
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
384
|
+
## Contributing
|
|
385
|
+
|
|
386
|
+
Contributions are welcome.
|
|
387
|
+
|
|
388
|
+
Development happens from feature branches targeting `develop`.
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
git checkout develop
|
|
392
|
+
git pull origin develop
|
|
393
|
+
git checkout -b feat/your-feature
|
|
394
|
+
|
|
395
|
+
npm install
|
|
396
|
+
npm run check
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
We use Conventional Commits:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
feat(scope): add new feature
|
|
403
|
+
fix(scope): resolve bug
|
|
404
|
+
chore(scope): update dependencies
|
|
413
405
|
```
|
|
414
406
|
|
|
415
|
-
|
|
407
|
+
Before opening a pull request, run:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
npm run check
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
| Command | Description |
|
|
414
|
+
| ----------------------- | ------------------------ |
|
|
415
|
+
| `npm run build` | Build the package |
|
|
416
|
+
| `npm run typecheck` | TypeScript type checking |
|
|
417
|
+
| `npm run lint` | ESLint |
|
|
418
|
+
| `npm run format:check` | Prettier validation |
|
|
419
|
+
| `npm run test` | Vitest test suite |
|
|
420
|
+
| `npm run test:coverage` | Test suite with coverage |
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## Support
|
|
425
|
+
|
|
426
|
+
If Xeno is useful to you, you can support the project through the community and
|
|
427
|
+
sponsorship channels documented on the website:
|
|
428
|
+
|
|
429
|
+
**[Support Xeno](https://www.xeno-js.it/support-us)**
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## License
|
|
434
|
+
|
|
435
|
+
Copyright (c) 2026 Xeno.
|
|
416
436
|
|
|
417
|
-
|
|
437
|
+
Licensed under the [MIT License](LICENSE).
|