@xeno-js/vercel 0.0.0-stage → 1.0.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 +21 -0
- package/README.md +254 -2
- package/dist/index.cjs +95 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/package.json +106 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xeno.JS
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,255 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="logo/logo.png" alt="Xeno.JS Logo" width="140" />
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
<h1>Xeno.JS Vercel</h1>
|
|
5
|
+
<p><strong>A Typescript application framework architecture for Node.js, designed for lower technical debt, and faster infrastructure migrations.</strong></p>
|
|
6
|
+
<p>Build long-lived applications with explicit dependency injection, DDD, CQRS, and transport-independent business logic.</p>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://www.npmjs.com/package/@xeno-js/vercel"><img src="https://img.shields.io/npm/v/@xeno-js/vercel?style=flat-square" alt="NPM Version" /></a>
|
|
10
|
+
<a href="https://github.com/xeno-js/vercel"><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>
|
|
12
|
+
<a href="https://buymeacoffee.com/xenojs">
|
|
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" />
|
|
14
|
+
</a>
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
`@xeno-js/vercel` provides native integration for deploying Xeno.JS Backend-for-Frontend (BFF) applications as Vercel Serverless Functions. It maps Vercel request/response lifecycles directly into the framework's transport-independent execution pipelines.
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @xeno-js/vercel
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The package requires Node.js 20 or higher, `@xeno-js/core` as peer dependencies.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Setting Up the Application Bootstrap Container
|
|
33
|
+
|
|
34
|
+
**Problem.** Serverless functions require cold-start optimization to avoid bootstrapping application modules on every single incoming HTTP request.
|
|
35
|
+
|
|
36
|
+
**Principle.** Application container initialization must be lazy-loaded, cached as a singleton instance across function invocations, and cleanly decoupled from the serverless transport entry point.
|
|
37
|
+
|
|
38
|
+
**Mechanism.** The `getApp` utility ensures the Xeno.JS application container is bootstrapped precisely once during the initial cold start and securely reused for subsequent warm requests.
|
|
39
|
+
|
|
40
|
+
**Implementation:**
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// _lib/app.ts
|
|
44
|
+
import { Guards, IXenoApplication, Maybe } from '@xeno-js/core'
|
|
45
|
+
import { MyRegistry } from './registry.js'
|
|
46
|
+
import { bootstrap } from './bootstrap.js'
|
|
47
|
+
|
|
48
|
+
let cachedApp: Maybe<IXenoApplication<MyRegistry>> = null
|
|
49
|
+
|
|
50
|
+
export async function getApp() {
|
|
51
|
+
if (!Guards.isDefined(cachedApp)) {
|
|
52
|
+
cachedApp = await bootstrap()
|
|
53
|
+
}
|
|
54
|
+
return cachedApp
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Behavior:** During the first execution, `bootstrap()` wires routes, middlewares, CQRS pipelines, and DI containers. Subsequent requests instantly resolve the cached application instance.
|
|
59
|
+
|
|
60
|
+
**Boundary:** The application bootstrap handles infrastructure wiring and container configuration, remaining completely agnostic of the deployment host (Vercel, AWS, or bare-metal).
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## 2. Registering Routes with `addRoutes`
|
|
65
|
+
|
|
66
|
+
**Problem.** An application container requires an explicit route registry mapping HTTP verbs and paths to their corresponding handlers. Without route definitions, the internal `RouteMatcher` cannot resolve incoming endpoints, resulting in a `404 Not Found` response.
|
|
67
|
+
|
|
68
|
+
**Principle.** Route registration must be explicitly defined using `addRoutes` during application building to bind endpoints to the dispatching pipeline.
|
|
69
|
+
|
|
70
|
+
**Mechanism.** The `addRoutes` method registers route definitions into the `XenoAppBuilder`, initializing the route matcher index.
|
|
71
|
+
|
|
72
|
+
**Implementation:**
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// _lib/bootstrap.ts
|
|
76
|
+
import { XenoAppBuilder } from '@xeno-js/core';
|
|
77
|
+
import { routes } from './routes.js';
|
|
78
|
+
import type { MyRegistry } from './registry.js';
|
|
79
|
+
|
|
80
|
+
export async function bootstrap() {
|
|
81
|
+
const builder = new XenoAppBuilder<MyRegistry>()
|
|
82
|
+
.addRoutes(routes) // REQUIRED: Registers application routes
|
|
83
|
+
// ... other modules and configurations
|
|
84
|
+
|
|
85
|
+
return await builder.build();
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Behavior:** The router compiles patterns and methods during bootstrap so that incoming serverless requests can be efficiently matched and dispatched.
|
|
90
|
+
|
|
91
|
+
**Boundary:** Route definitions handle URL matching and parameter extraction, delegating business logic to controllers and handlers.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 3. Configuring the Vercel Catch-All Serverless Handler
|
|
96
|
+
|
|
97
|
+
**Problem.** Vercel maps individual files inside an `api/` directory to distinct serverless endpoints, whereas modern BFFs typically rely on a unified internal router to handle complex nested sub-routes (e.g., `/api/v1/orders`).
|
|
98
|
+
|
|
99
|
+
**Principle.** All serverless traffic must be routed to a single catch-all entrypoint function that delegates request processing to the framework's internal `RouteMatcher` and `HttpDispatcher`.
|
|
100
|
+
|
|
101
|
+
**Mechanism.** The `XenoVercel.handle` utility adapts incoming `VercelRequest` and `VercelResponse` objects, executes the standard middleware chain, evaluates route policies, and formats the output response.
|
|
102
|
+
|
|
103
|
+
**Implementation:**
|
|
104
|
+
|
|
105
|
+
Assuming you place your serverless entry point inside a directory designated for deployment (for example, `api/index.ts` as an example directory layout), configure the handler as follows:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
// api/index.ts (Catch-all serverless function entrypoint)
|
|
109
|
+
import type { VercelRequest, VercelResponse } from '@vercel/node'
|
|
110
|
+
import { ERROR_CODE_MESSAGES, ERROR_CODES, GuidHelper, STATUS_CODES } from "@xeno-js/core";
|
|
111
|
+
import { XenoVercel } from "@xeno-js/vercel";
|
|
112
|
+
import { getApp } from "./_lib/app.js";
|
|
113
|
+
|
|
114
|
+
export default async function handler(req: VercelRequest, res: VercelResponse): Promise<VercelResponse> {
|
|
115
|
+
try {
|
|
116
|
+
return await XenoVercel.handle(req, res, getApp)
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('Fatal error in serverless runtime:', error)
|
|
119
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
120
|
+
const errorMessage = error instanceof Error ? error.message : 'Internal Server Error'
|
|
121
|
+
|
|
122
|
+
return res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({
|
|
123
|
+
success: false,
|
|
124
|
+
error: {
|
|
125
|
+
code: ERROR_CODES.SYSTEM_ERROR,
|
|
126
|
+
message: ERROR_CODE_MESSAGES[ERROR_CODES.SYSTEM_ERROR],
|
|
127
|
+
details: isDev ? errorMessage : undefined,
|
|
128
|
+
path: req.url,
|
|
129
|
+
},
|
|
130
|
+
correlationId: GuidHelper.generate(),
|
|
131
|
+
requestId: GuidHelper.generate(),
|
|
132
|
+
timestamp: new Date().toISOString(),
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Behavior:** When an HTTP request hits `https://your-domain.com/api/v1/orders`, Vercel rewrites or routes the call to `api/index.ts`. `XenoVercel.handle` matches the route, executes CORS, rate-limiting, authentication, and validation middleware, and invokes the target controller.
|
|
139
|
+
|
|
140
|
+
**Boundary:** The Vercel handler translates serverless transport boundaries into standard framework requests, keeping core controllers and use cases 100% portable.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 4. Technical Documentation: Global Rate Limiting Error on Vercel / Reverse Proxy
|
|
145
|
+
|
|
146
|
+
### 1. Problem Description
|
|
147
|
+
|
|
148
|
+
During application execution (or during load testing with tools like k6), after a minimal number of requests (e.g., 10 requests), all users start receiving the following error:
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"success": false,
|
|
153
|
+
"error": {
|
|
154
|
+
"code": "TOO_MANY_REQUESTS",
|
|
155
|
+
"message": "errors.too_many_requests",
|
|
156
|
+
"details": "Rate limit exceeded.",
|
|
157
|
+
"path": "/api/v1/orders"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The block occurs even when requests originate from different client IPs, different devices, or distinct users, rendering the service unusable for the entire global user base for the duration of the time window (`windowSeconds`).
|
|
163
|
+
|
|
164
|
+
### 2. Root Cause
|
|
165
|
+
|
|
166
|
+
In Xeno.JS, `RateLimitMiddleware` builds the counting cache key via `RateLimitKeyBuilder`:
|
|
167
|
+
|
|
168
|
+
```text
|
|
169
|
+
ratelimit:ip:<clientIp>:<resource>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Extracting `clientIp` is delegated to the `ClientIpResolver` service. In the Xeno.JS source code, the resolver operates as follows:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
export class ClientIpResolver implements IIPResolver {
|
|
176
|
+
constructor(private readonly _trustedProxies: string[] = []) {}
|
|
177
|
+
|
|
178
|
+
public resolve(req: XenoRequest, clientIp: Optional<string>): Optional<string> {
|
|
179
|
+
const socketIp = req.socket?.remoteAddress
|
|
180
|
+
if (!Guards.isDefined(socketIp))
|
|
181
|
+
return !Guards.isNullOrEmpty(clientIp) ? clientIp.split(',')[0].trim() : undefined
|
|
182
|
+
const isTrustedProxy = this._trustedProxies.includes(socketIp)
|
|
183
|
+
if (!isTrustedProxy) return socketIp
|
|
184
|
+
if (Guards.isDefined(clientIp)) return clientIp.split(',')[0].trim()
|
|
185
|
+
return socketIp
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### Why does collective blocking happen?
|
|
191
|
+
|
|
192
|
+
1. **TCP Connection on Serverless Platforms (Vercel):**
|
|
193
|
+
When a user sends a request to an application deployed on Vercel, they do not establish a direct TCP connection with the Serverless Function; they connect first to Vercel's Edge/Reverse Proxy infrastructure.
|
|
194
|
+
2. **Vercel Proxy to Container:**
|
|
195
|
+
The Vercel proxy then connects to the serverless container. Consequently, `req.socket.remoteAddress` contains the internal IP address of Vercel's proxy, not the real visitor's IP.
|
|
196
|
+
3. **Missing Trusted Proxies (`_trustedProxies` is empty):**
|
|
197
|
+
For security reasons (preventing IP spoofing), `ClientIpResolver` refuses to read headers such as `X-Forwarded-For` or `X-Real-IP` unless `req.socket.remoteAddress` matches the list of trusted proxies (`_trustedProxies`). If `trustedProxies` is unconfigured, `_trustedProxies` defaults to an empty array `[]`.
|
|
198
|
+
4. **Fallback to Socket IP:**
|
|
199
|
+
Because `isTrustedProxy` evaluates to `false`, the resolver returns `socketIp` directly. This means every request—regardless of the user originating it—is labeled with the identical internal Vercel IP.
|
|
200
|
+
5. **Bucket Saturation:**
|
|
201
|
+
All users share the exact same cache key (e.g., `ratelimit:ip:10.x.x.x:/api/v1/orders`). As soon as the first 10 aggregate requests worldwide are executed, the counter exceeds the threshold (`maxRequests: 10`), triggering the middleware for anyone else sending a request within the next 60 seconds.
|
|
202
|
+
|
|
203
|
+
### 3. Solution
|
|
204
|
+
|
|
205
|
+
To ensure each user receives an isolated request budget, configure `MiddlewareModule` to:
|
|
206
|
+
|
|
207
|
+
1. Recognize `x-forwarded-for` as the primary header for identifying the client.
|
|
208
|
+
2. Trust Vercel's reverse proxy by enabling the wildcard (`['*']`).
|
|
209
|
+
|
|
210
|
+
#### Bootstrap Configuration (`_lib/bootstrap.ts`)
|
|
211
|
+
|
|
212
|
+
Inside the `.addMiddlewares()` configuration:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
.addMiddlewares((opts, config) => {
|
|
216
|
+
// 1. CORS Configuration
|
|
217
|
+
opts.cors = {
|
|
218
|
+
allowOrigins: [config.get('CLIENT_URL', '')!],
|
|
219
|
+
enablePreflight: true,
|
|
220
|
+
enabled: true
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// 2. Instruct Xeno.JS to read the header injected by Vercel
|
|
224
|
+
opts.trustedIpHeader = 'x-forwarded-for';
|
|
225
|
+
|
|
226
|
+
// 3. Instruct the resolver to trust intermediate proxies
|
|
227
|
+
// Without this, x-forwarded-for would be discarded in favor of req.socket.remoteAddress
|
|
228
|
+
opts.trustedProxies = ['*'];
|
|
229
|
+
|
|
230
|
+
// 4. Rate Limiting configuration per individual user IP
|
|
231
|
+
opts.rateLimit = {
|
|
232
|
+
enabled: true,
|
|
233
|
+
maxRequests: 10, // Maximum 10 requests per user/IP
|
|
234
|
+
windowSeconds: 60 // 60-second time window
|
|
235
|
+
};
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### 4. Why This Configuration Is Mandatory
|
|
240
|
+
|
|
241
|
+
- **Cache Key Isolation:**
|
|
242
|
+
With `trustedProxies = ['*']`, `ClientIpResolver` extracts the first IP from the list present in the `X-Forwarded-For` header (the actual public IP address of the client that initiated the request).
|
|
243
|
+
- **Multi-User Operation:**
|
|
244
|
+
The generated cache keys will be unique per client:
|
|
245
|
+
|
|
246
|
+
- User A: `ratelimit:ip:93.45.12.1:/api/v1/orders` (10 requests available).
|
|
247
|
+
- User B: `ratelimit:ip:80.18.23.4:/api/v1/orders` (10 requests available).
|
|
248
|
+
|
|
249
|
+
If User A exhausts their 10 requests and gets throttled with a `429 Too Many Requests`, User B can continue chatting freely without encountering any slowdowns or blocks.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
Released under the [MIT License](LICENSE).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
XenoVercel: () => XenoVercel
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/infrastructure/vercel.handler.ts
|
|
28
|
+
var import_core = require("@xeno-js/core");
|
|
29
|
+
var XenoVercel = class {
|
|
30
|
+
static async handle(req, res, getApp) {
|
|
31
|
+
try {
|
|
32
|
+
const app = await getApp();
|
|
33
|
+
const { routeMatcher, httpAdapter, httpDispatcher } = app.getServices();
|
|
34
|
+
const url = req.url ?? "/";
|
|
35
|
+
const method = req.method ?? "GET";
|
|
36
|
+
const matchedRoute = routeMatcher.match(url, method);
|
|
37
|
+
if (!import_core.Guards.isDefined(matchedRoute)) {
|
|
38
|
+
return res.status(import_core.STATUS_CODES.NOT_FOUND).json({
|
|
39
|
+
success: false,
|
|
40
|
+
error: {
|
|
41
|
+
code: import_core.ERROR_CODES.NOT_FOUND,
|
|
42
|
+
message: `Route ${method} ${url} not found`,
|
|
43
|
+
path: url
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
const normalizedHeaders = import_core.HttpHelper.normalizeHeaders(req.headers);
|
|
48
|
+
const xenoreq = {
|
|
49
|
+
url,
|
|
50
|
+
headers: new Headers(normalizedHeaders),
|
|
51
|
+
signal: req.signal,
|
|
52
|
+
socket: req.socket,
|
|
53
|
+
body: req.body,
|
|
54
|
+
query: req.query,
|
|
55
|
+
method,
|
|
56
|
+
path: new URL(url, "http://localhost").pathname
|
|
57
|
+
};
|
|
58
|
+
const extendedRequest = httpAdapter.adapt(xenoreq);
|
|
59
|
+
const routeResult = await matchedRoute.handler({
|
|
60
|
+
req: extendedRequest,
|
|
61
|
+
handle: (token, input) => httpDispatcher.handle(extendedRequest, token, input)
|
|
62
|
+
});
|
|
63
|
+
if (import_core.Guards.isDefined(routeResult)) {
|
|
64
|
+
if (import_core.Guards.isDefined(routeResult.headers)) {
|
|
65
|
+
const responseHeaders = new Headers(routeResult.headers);
|
|
66
|
+
responseHeaders.forEach((value, key) => {
|
|
67
|
+
res.setHeader(key, value);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return res.status(routeResult.status).json(routeResult.data);
|
|
71
|
+
}
|
|
72
|
+
return res.status(import_core.STATUS_CODES.NO_CONTENT).end();
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.error("Errore fatale nell'handler Vercel di Xeno:", error);
|
|
75
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
76
|
+
const errorMessage = error instanceof Error ? error.message : "Internal Server Error";
|
|
77
|
+
return res.status(import_core.STATUS_CODES.INTERNAL_SERVER_ERROR).json({
|
|
78
|
+
success: false,
|
|
79
|
+
error: {
|
|
80
|
+
code: import_core.ERROR_CODES.SYSTEM_ERROR,
|
|
81
|
+
message: import_core.ERROR_CODE_MESSAGES[import_core.ERROR_CODES.SYSTEM_ERROR],
|
|
82
|
+
details: isDev ? errorMessage : void 0
|
|
83
|
+
},
|
|
84
|
+
correlationId: import_core.GuidHelper.generate(),
|
|
85
|
+
requestId: import_core.GuidHelper.generate(),
|
|
86
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
XenoVercel
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/infrastructure/vercel.handler.ts"],"sourcesContent":["export { XenoVercel } from './infrastructure/vercel.handler'","import type { VercelRequest, VercelResponse } from '@vercel/node'\r\nimport type { Dictionary, IXenoApplication, XenoIncomingRequest, XenoRegistry } from '@xeno-js/core'\r\nimport { ERROR_CODE_MESSAGES, ERROR_CODES, Guards, GuidHelper, HttpHelper, STATUS_CODES } from '@xeno-js/core'\r\n\r\nexport class XenoVercel {\r\n public static async handle<TRegistry extends XenoRegistry = XenoRegistry>(\r\n req: VercelRequest,\r\n res: VercelResponse,\r\n getApp: () => Promise<IXenoApplication<TRegistry>>\r\n ): Promise<VercelResponse> {\r\n try {\r\n const app = await getApp()\r\n const { routeMatcher, httpAdapter, httpDispatcher } = app.getServices()\r\n\r\n const url = req.url ?? '/'\r\n const method = req.method ?? 'GET'\r\n\r\n const matchedRoute = routeMatcher.match(url, method)\r\n\r\n if (!Guards.isDefined(matchedRoute)) {\r\n return res.status(STATUS_CODES.NOT_FOUND).json({\r\n success: false,\r\n error: {\r\n code: ERROR_CODES.NOT_FOUND,\r\n message: `Route ${method} ${url} not found`,\r\n path: url\r\n }\r\n })\r\n }\r\n\r\n const normalizedHeaders = HttpHelper.normalizeHeaders(req.headers)\r\n const xenoreq: XenoIncomingRequest = {\r\n url,\r\n headers: new Headers(normalizedHeaders as Dictionary<string>),\r\n signal: req.signal,\r\n socket: req.socket,\r\n body: req.body,\r\n query: req.query as Dictionary<string>,\r\n method,\r\n path: new URL(url, 'http://localhost').pathname\r\n }\r\n\r\n const extendedRequest = httpAdapter.adapt(xenoreq)\r\n\r\n const routeResult = await matchedRoute.handler({\r\n req: extendedRequest,\r\n handle: (token, input) => httpDispatcher.handle(extendedRequest, token, input),\r\n })\r\n\r\n if (Guards.isDefined(routeResult)) {\r\n if (Guards.isDefined(routeResult.headers)) {\r\n const responseHeaders = new Headers(routeResult.headers as Dictionary<string>)\r\n responseHeaders.forEach((value, key) => {\r\n res.setHeader(key, value)\r\n })\r\n }\r\n return res.status(routeResult.status).json(routeResult.data)\r\n }\r\n\r\n return res.status(STATUS_CODES.NO_CONTENT).end()\r\n\r\n } catch (error) {\r\n console.error('Errore fatale nell\\'handler Vercel di Xeno:', error)\r\n const isDev = process.env.NODE_ENV === 'development'\r\n const errorMessage = error instanceof Error ? error.message : 'Internal Server Error'\r\n\r\n return res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({\r\n success: false,\r\n error: {\r\n code: ERROR_CODES.SYSTEM_ERROR,\r\n message: ERROR_CODE_MESSAGES[ERROR_CODES.SYSTEM_ERROR],\r\n details: isDev ? errorMessage : undefined,\r\n },\r\n correlationId: GuidHelper.generate(),\r\n requestId: GuidHelper.generate(),\r\n timestamp: new Date().toISOString(),\r\n })\r\n }\r\n }\r\n}"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,kBAA+F;AAExF,IAAM,aAAN,MAAiB;AAAA,EACpB,aAAoB,OAChB,KACA,KACA,QACuB;AACvB,QAAI;AACA,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,EAAE,cAAc,aAAa,eAAe,IAAI,IAAI,YAAY;AAEtE,YAAM,MAAM,IAAI,OAAO;AACvB,YAAM,SAAS,IAAI,UAAU;AAE7B,YAAM,eAAe,aAAa,MAAM,KAAK,MAAM;AAEnD,UAAI,CAAC,mBAAO,UAAU,YAAY,GAAG;AACjC,eAAO,IAAI,OAAO,yBAAa,SAAS,EAAE,KAAK;AAAA,UAC3C,SAAS;AAAA,UACT,OAAO;AAAA,YACH,MAAM,wBAAY;AAAA,YAClB,SAAS,SAAS,MAAM,IAAI,GAAG;AAAA,YAC/B,MAAM;AAAA,UACV;AAAA,QACJ,CAAC;AAAA,MACL;AAEA,YAAM,oBAAoB,uBAAW,iBAAiB,IAAI,OAAO;AACjE,YAAM,UAA+B;AAAA,QACjC;AAAA,QACA,SAAS,IAAI,QAAQ,iBAAuC;AAAA,QAC5D,QAAQ,IAAI;AAAA,QACZ,QAAQ,IAAI;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,OAAO,IAAI;AAAA,QACX;AAAA,QACA,MAAM,IAAI,IAAI,KAAK,kBAAkB,EAAE;AAAA,MAC3C;AAEA,YAAM,kBAAkB,YAAY,MAAM,OAAO;AAEjD,YAAM,cAAc,MAAM,aAAa,QAAQ;AAAA,QAC3C,KAAK;AAAA,QACL,QAAQ,CAAC,OAAO,UAAU,eAAe,OAAO,iBAAiB,OAAO,KAAK;AAAA,MACjF,CAAC;AAED,UAAI,mBAAO,UAAU,WAAW,GAAG;AAC/B,YAAI,mBAAO,UAAU,YAAY,OAAO,GAAG;AACvC,gBAAM,kBAAkB,IAAI,QAAQ,YAAY,OAA6B;AAC7E,0BAAgB,QAAQ,CAAC,OAAO,QAAQ;AACpC,gBAAI,UAAU,KAAK,KAAK;AAAA,UAC5B,CAAC;AAAA,QACL;AACA,eAAO,IAAI,OAAO,YAAY,MAAM,EAAE,KAAK,YAAY,IAAI;AAAA,MAC/D;AAEA,aAAO,IAAI,OAAO,yBAAa,UAAU,EAAE,IAAI;AAAA,IAEnD,SAAS,OAAO;AACZ,cAAQ,MAAM,8CAA+C,KAAK;AAClE,YAAM,QAAQ,QAAQ,IAAI,aAAa;AACvC,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAE9D,aAAO,IAAI,OAAO,yBAAa,qBAAqB,EAAE,KAAK;AAAA,QACvD,SAAS;AAAA,QACT,OAAO;AAAA,UACH,MAAM,wBAAY;AAAA,UAClB,SAAS,gCAAoB,wBAAY,YAAY;AAAA,UACrD,SAAS,QAAQ,eAAe;AAAA,QACpC;AAAA,QACA,eAAe,uBAAW,SAAS;AAAA,QACnC,WAAW,uBAAW,SAAS;AAAA,QAC/B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtC,CAAC;AAAA,IACL;AAAA,EACJ;AACJ;","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VercelRequest, VercelResponse } from '@vercel/node';
|
|
2
|
+
import { XenoRegistry, IXenoApplication } from '@xeno-js/core';
|
|
3
|
+
|
|
4
|
+
declare class XenoVercel {
|
|
5
|
+
static handle<TRegistry extends XenoRegistry = XenoRegistry>(req: VercelRequest, res: VercelResponse, getApp: () => Promise<IXenoApplication<TRegistry>>): Promise<VercelResponse>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { XenoVercel };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VercelRequest, VercelResponse } from '@vercel/node';
|
|
2
|
+
import { XenoRegistry, IXenoApplication } from '@xeno-js/core';
|
|
3
|
+
|
|
4
|
+
declare class XenoVercel {
|
|
5
|
+
static handle<TRegistry extends XenoRegistry = XenoRegistry>(req: VercelRequest, res: VercelResponse, getApp: () => Promise<IXenoApplication<TRegistry>>): Promise<VercelResponse>;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export { XenoVercel };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/infrastructure/vercel.handler.ts
|
|
2
|
+
import { ERROR_CODE_MESSAGES, ERROR_CODES, Guards, GuidHelper, HttpHelper, STATUS_CODES } from "@xeno-js/core";
|
|
3
|
+
var XenoVercel = class {
|
|
4
|
+
static async handle(req, res, getApp) {
|
|
5
|
+
try {
|
|
6
|
+
const app = await getApp();
|
|
7
|
+
const { routeMatcher, httpAdapter, httpDispatcher } = app.getServices();
|
|
8
|
+
const url = req.url ?? "/";
|
|
9
|
+
const method = req.method ?? "GET";
|
|
10
|
+
const matchedRoute = routeMatcher.match(url, method);
|
|
11
|
+
if (!Guards.isDefined(matchedRoute)) {
|
|
12
|
+
return res.status(STATUS_CODES.NOT_FOUND).json({
|
|
13
|
+
success: false,
|
|
14
|
+
error: {
|
|
15
|
+
code: ERROR_CODES.NOT_FOUND,
|
|
16
|
+
message: `Route ${method} ${url} not found`,
|
|
17
|
+
path: url
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const normalizedHeaders = HttpHelper.normalizeHeaders(req.headers);
|
|
22
|
+
const xenoreq = {
|
|
23
|
+
url,
|
|
24
|
+
headers: new Headers(normalizedHeaders),
|
|
25
|
+
signal: req.signal,
|
|
26
|
+
socket: req.socket,
|
|
27
|
+
body: req.body,
|
|
28
|
+
query: req.query,
|
|
29
|
+
method,
|
|
30
|
+
path: new URL(url, "http://localhost").pathname
|
|
31
|
+
};
|
|
32
|
+
const extendedRequest = httpAdapter.adapt(xenoreq);
|
|
33
|
+
const routeResult = await matchedRoute.handler({
|
|
34
|
+
req: extendedRequest,
|
|
35
|
+
handle: (token, input) => httpDispatcher.handle(extendedRequest, token, input)
|
|
36
|
+
});
|
|
37
|
+
if (Guards.isDefined(routeResult)) {
|
|
38
|
+
if (Guards.isDefined(routeResult.headers)) {
|
|
39
|
+
const responseHeaders = new Headers(routeResult.headers);
|
|
40
|
+
responseHeaders.forEach((value, key) => {
|
|
41
|
+
res.setHeader(key, value);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return res.status(routeResult.status).json(routeResult.data);
|
|
45
|
+
}
|
|
46
|
+
return res.status(STATUS_CODES.NO_CONTENT).end();
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error("Errore fatale nell'handler Vercel di Xeno:", error);
|
|
49
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
50
|
+
const errorMessage = error instanceof Error ? error.message : "Internal Server Error";
|
|
51
|
+
return res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({
|
|
52
|
+
success: false,
|
|
53
|
+
error: {
|
|
54
|
+
code: ERROR_CODES.SYSTEM_ERROR,
|
|
55
|
+
message: ERROR_CODE_MESSAGES[ERROR_CODES.SYSTEM_ERROR],
|
|
56
|
+
details: isDev ? errorMessage : void 0
|
|
57
|
+
},
|
|
58
|
+
correlationId: GuidHelper.generate(),
|
|
59
|
+
requestId: GuidHelper.generate(),
|
|
60
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
export {
|
|
66
|
+
XenoVercel
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infrastructure/vercel.handler.ts"],"sourcesContent":["import type { VercelRequest, VercelResponse } from '@vercel/node'\r\nimport type { Dictionary, IXenoApplication, XenoIncomingRequest, XenoRegistry } from '@xeno-js/core'\r\nimport { ERROR_CODE_MESSAGES, ERROR_CODES, Guards, GuidHelper, HttpHelper, STATUS_CODES } from '@xeno-js/core'\r\n\r\nexport class XenoVercel {\r\n public static async handle<TRegistry extends XenoRegistry = XenoRegistry>(\r\n req: VercelRequest,\r\n res: VercelResponse,\r\n getApp: () => Promise<IXenoApplication<TRegistry>>\r\n ): Promise<VercelResponse> {\r\n try {\r\n const app = await getApp()\r\n const { routeMatcher, httpAdapter, httpDispatcher } = app.getServices()\r\n\r\n const url = req.url ?? '/'\r\n const method = req.method ?? 'GET'\r\n\r\n const matchedRoute = routeMatcher.match(url, method)\r\n\r\n if (!Guards.isDefined(matchedRoute)) {\r\n return res.status(STATUS_CODES.NOT_FOUND).json({\r\n success: false,\r\n error: {\r\n code: ERROR_CODES.NOT_FOUND,\r\n message: `Route ${method} ${url} not found`,\r\n path: url\r\n }\r\n })\r\n }\r\n\r\n const normalizedHeaders = HttpHelper.normalizeHeaders(req.headers)\r\n const xenoreq: XenoIncomingRequest = {\r\n url,\r\n headers: new Headers(normalizedHeaders as Dictionary<string>),\r\n signal: req.signal,\r\n socket: req.socket,\r\n body: req.body,\r\n query: req.query as Dictionary<string>,\r\n method,\r\n path: new URL(url, 'http://localhost').pathname\r\n }\r\n\r\n const extendedRequest = httpAdapter.adapt(xenoreq)\r\n\r\n const routeResult = await matchedRoute.handler({\r\n req: extendedRequest,\r\n handle: (token, input) => httpDispatcher.handle(extendedRequest, token, input),\r\n })\r\n\r\n if (Guards.isDefined(routeResult)) {\r\n if (Guards.isDefined(routeResult.headers)) {\r\n const responseHeaders = new Headers(routeResult.headers as Dictionary<string>)\r\n responseHeaders.forEach((value, key) => {\r\n res.setHeader(key, value)\r\n })\r\n }\r\n return res.status(routeResult.status).json(routeResult.data)\r\n }\r\n\r\n return res.status(STATUS_CODES.NO_CONTENT).end()\r\n\r\n } catch (error) {\r\n console.error('Errore fatale nell\\'handler Vercel di Xeno:', error)\r\n const isDev = process.env.NODE_ENV === 'development'\r\n const errorMessage = error instanceof Error ? error.message : 'Internal Server Error'\r\n\r\n return res.status(STATUS_CODES.INTERNAL_SERVER_ERROR).json({\r\n success: false,\r\n error: {\r\n code: ERROR_CODES.SYSTEM_ERROR,\r\n message: ERROR_CODE_MESSAGES[ERROR_CODES.SYSTEM_ERROR],\r\n details: isDev ? errorMessage : undefined,\r\n },\r\n correlationId: GuidHelper.generate(),\r\n requestId: GuidHelper.generate(),\r\n timestamp: new Date().toISOString(),\r\n })\r\n }\r\n }\r\n}"],"mappings":";AAEA,SAAS,qBAAqB,aAAa,QAAQ,YAAY,YAAY,oBAAoB;AAExF,IAAM,aAAN,MAAiB;AAAA,EACpB,aAAoB,OAChB,KACA,KACA,QACuB;AACvB,QAAI;AACA,YAAM,MAAM,MAAM,OAAO;AACzB,YAAM,EAAE,cAAc,aAAa,eAAe,IAAI,IAAI,YAAY;AAEtE,YAAM,MAAM,IAAI,OAAO;AACvB,YAAM,SAAS,IAAI,UAAU;AAE7B,YAAM,eAAe,aAAa,MAAM,KAAK,MAAM;AAEnD,UAAI,CAAC,OAAO,UAAU,YAAY,GAAG;AACjC,eAAO,IAAI,OAAO,aAAa,SAAS,EAAE,KAAK;AAAA,UAC3C,SAAS;AAAA,UACT,OAAO;AAAA,YACH,MAAM,YAAY;AAAA,YAClB,SAAS,SAAS,MAAM,IAAI,GAAG;AAAA,YAC/B,MAAM;AAAA,UACV;AAAA,QACJ,CAAC;AAAA,MACL;AAEA,YAAM,oBAAoB,WAAW,iBAAiB,IAAI,OAAO;AACjE,YAAM,UAA+B;AAAA,QACjC;AAAA,QACA,SAAS,IAAI,QAAQ,iBAAuC;AAAA,QAC5D,QAAQ,IAAI;AAAA,QACZ,QAAQ,IAAI;AAAA,QACZ,MAAM,IAAI;AAAA,QACV,OAAO,IAAI;AAAA,QACX;AAAA,QACA,MAAM,IAAI,IAAI,KAAK,kBAAkB,EAAE;AAAA,MAC3C;AAEA,YAAM,kBAAkB,YAAY,MAAM,OAAO;AAEjD,YAAM,cAAc,MAAM,aAAa,QAAQ;AAAA,QAC3C,KAAK;AAAA,QACL,QAAQ,CAAC,OAAO,UAAU,eAAe,OAAO,iBAAiB,OAAO,KAAK;AAAA,MACjF,CAAC;AAED,UAAI,OAAO,UAAU,WAAW,GAAG;AAC/B,YAAI,OAAO,UAAU,YAAY,OAAO,GAAG;AACvC,gBAAM,kBAAkB,IAAI,QAAQ,YAAY,OAA6B;AAC7E,0BAAgB,QAAQ,CAAC,OAAO,QAAQ;AACpC,gBAAI,UAAU,KAAK,KAAK;AAAA,UAC5B,CAAC;AAAA,QACL;AACA,eAAO,IAAI,OAAO,YAAY,MAAM,EAAE,KAAK,YAAY,IAAI;AAAA,MAC/D;AAEA,aAAO,IAAI,OAAO,aAAa,UAAU,EAAE,IAAI;AAAA,IAEnD,SAAS,OAAO;AACZ,cAAQ,MAAM,8CAA+C,KAAK;AAClE,YAAM,QAAQ,QAAQ,IAAI,aAAa;AACvC,YAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU;AAE9D,aAAO,IAAI,OAAO,aAAa,qBAAqB,EAAE,KAAK;AAAA,QACvD,SAAS;AAAA,QACT,OAAO;AAAA,UACH,MAAM,YAAY;AAAA,UAClB,SAAS,oBAAoB,YAAY,YAAY;AAAA,UACrD,SAAS,QAAQ,eAAe;AAAA,QACpC;AAAA,QACA,eAAe,WAAW,SAAS;AAAA,QACnC,WAAW,WAAW,SAAS;AAAA,QAC/B,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MACtC,CAAC;AAAA,IACL;AAAA,EACJ;AACJ;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,108 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xeno-js/vercel",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Vercel Serverless Functions and Edge runtime integration for Xeno.js applications, enabling seamless BFF deployments.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"xeno",
|
|
7
|
+
"xeno-js",
|
|
8
|
+
"xeno.js",
|
|
9
|
+
"vercel",
|
|
10
|
+
"vercel serverless",
|
|
11
|
+
"vercel functions",
|
|
12
|
+
"serverless functions",
|
|
13
|
+
"serverless typescript",
|
|
14
|
+
"edge runtime",
|
|
15
|
+
"bff",
|
|
16
|
+
"backend for frontend",
|
|
17
|
+
"cloud deployment",
|
|
18
|
+
"typescript",
|
|
19
|
+
"typescript backend",
|
|
20
|
+
"nodejs",
|
|
21
|
+
"node.js",
|
|
22
|
+
"dependency injection",
|
|
23
|
+
"clean architecture",
|
|
24
|
+
"hexagonal architecture"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/xeno-js/vercel.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/xeno-js/vercel/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://www.xeno-js.it",
|
|
34
|
+
"type": "module",
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"module": "./dist/index.js",
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
".": {
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"import": "./dist/index.js",
|
|
42
|
+
"require": "./dist/index.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist"
|
|
47
|
+
],
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public",
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsup",
|
|
54
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
55
|
+
"lint": "eslint --config eslint.config.mjs \"src/**/*.ts\" --max-warnings=0",
|
|
56
|
+
"lint:fix": "eslint --config eslint.config.mjs \"src/**/*.ts\" --fix --max-warnings=0",
|
|
57
|
+
"lint-staged": "lint-staged",
|
|
58
|
+
"commitlint": "commitlint",
|
|
59
|
+
"format": "prettier --write .",
|
|
60
|
+
"format:check": "prettier --check .",
|
|
61
|
+
"test": "vitest run",
|
|
62
|
+
"test:watch": "vitest",
|
|
63
|
+
"test:coverage": "vitest run --coverage",
|
|
64
|
+
"check": "npm run typecheck && npm run lint:fix && npm run test",
|
|
65
|
+
"verify:commit": "npm run lint-staged && npm run test",
|
|
66
|
+
"verify:push": "npm run check",
|
|
67
|
+
"prepare": "husky",
|
|
68
|
+
"changelog": "npx conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
|
69
|
+
"prepublishOnly": "npm run check && npm run build",
|
|
70
|
+
"release:patch": "npm version patch && npm run changelog && npm run prepublishOnly && npm publish",
|
|
71
|
+
"release:minor": "npm version minor && npm run changelog && npm run prepublishOnly && npm publish",
|
|
72
|
+
"release:major": "npm version major && npm run changelog && npm run prepublishOnly && npm publish"
|
|
73
|
+
},
|
|
74
|
+
"author": "Xeno",
|
|
75
|
+
"license": "MIT",
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=20.0.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"@xeno-js/core": "^4.1.1"
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@commitlint/cli": "^21.2.3",
|
|
84
|
+
"@commitlint/config-conventional": "^21.2.3",
|
|
85
|
+
"@eslint/js": "^9.39.5",
|
|
86
|
+
"@types/pg": "^8.23.1",
|
|
87
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
88
|
+
"@xeno-js/core": "^4.1.1",
|
|
89
|
+
"conventional-changelog": "^8.1.3",
|
|
90
|
+
"conventional-changelog-angular": "^9.4.0",
|
|
91
|
+
"conventional-changelog-cli": "^5.0.0",
|
|
92
|
+
"conventional-changelog-writer": "^9.2.1",
|
|
93
|
+
"eslint": "^9.39.5",
|
|
94
|
+
"eslint-config-prettier": "^10.1.8",
|
|
95
|
+
"eslint-plugin-simple-import-sort": "^14.0.0",
|
|
96
|
+
"husky": "^9.1.7",
|
|
97
|
+
"lint-staged": "^17.5.1",
|
|
98
|
+
"prettier": "^3.9.9",
|
|
99
|
+
"tsc-alias": "^1.9.5",
|
|
100
|
+
"tsup": "^8.5.1",
|
|
101
|
+
"typescript": "^5.8.0",
|
|
102
|
+
"typescript-eslint": "^8.36.0",
|
|
103
|
+
"vitest": "^5.0.1"
|
|
104
|
+
},
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"@vercel/node": "^30.0.0"
|
|
107
|
+
}
|
|
108
|
+
}
|