@uvrn/api 1.0.1 → 1.0.3
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/README.md +12 -4
- package/dist/server.js +0 -0
- package/package.json +17 -7
- package/TESTING_GUIDE.md +0 -495
- package/jest.config.js +0 -13
- package/src/config/loader.ts +0 -60
- package/src/config/types.ts +0 -18
- package/src/index.ts +0 -13
- package/src/middleware/errorHandler.ts +0 -69
- package/src/routes/delta.ts +0 -120
- package/src/routes/health.ts +0 -65
- package/src/server.ts +0 -139
- package/src/types/api.ts +0 -35
- package/tsconfig.json +0 -9
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
UVRN REST API — HTTP server for Delta Engine bundle processing. Exposes run, validate, and verify over HTTP so any client (browser, script, or service) can call the engine without installing the core or SDK.
|
|
4
4
|
|
|
5
|
+
**Disclaimer:** UVRN is in Alpha testing. The engine measures whether your sources agree with each other — not whether they’re correct. Final trust of output rests with the user. Use at your own risk. Have fun.
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -27,16 +29,20 @@ Or from your app:
|
|
|
27
29
|
```typescript
|
|
28
30
|
import { startServer, createServer } from '@uvrn/api';
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
await startServer(
|
|
32
|
+
// Default: start with default config (port 3000)
|
|
33
|
+
await startServer();
|
|
34
|
+
|
|
35
|
+
// Or create server with custom config, then listen
|
|
36
|
+
const server = await createServer({ port: 4000 });
|
|
37
|
+
await server.listen({ port: 4000, host: '0.0.0.0' });
|
|
32
38
|
```
|
|
33
39
|
|
|
34
|
-
2. **Send a bundle** (e.g. POST to `/delta/run`) and get a receipt in the response. Use `/delta/validate` and `/delta/verify` for validation and verification.
|
|
40
|
+
2. **Send a bundle** (e.g. POST to `/api/v1/delta/run`) and get a receipt in the response. Use `/api/v1/delta/validate` and `/api/v1/delta/verify` for validation and verification.
|
|
35
41
|
|
|
36
42
|
Example with curl:
|
|
37
43
|
|
|
38
44
|
```bash
|
|
39
|
-
curl -X POST http://localhost:3000/delta/run \
|
|
45
|
+
curl -X POST http://localhost:3000/api/v1/delta/run \
|
|
40
46
|
-H "Content-Type: application/json" \
|
|
41
47
|
-d '{"bundleId":"example-001","claim":"Compare sources","thresholdPct":0.1,"dataSpecs":[...]}'
|
|
42
48
|
```
|
|
@@ -49,6 +55,8 @@ curl -X POST http://localhost:3000/delta/run \
|
|
|
49
55
|
|
|
50
56
|
## Links
|
|
51
57
|
|
|
58
|
+
**Open source:** Source code and issues: [GitHub (uvrn-packages)](https://github.com/UVRN-org/uvrn-packages). Project landing: [UVRN](https://github.com/UVRN-org/uvrn).
|
|
59
|
+
|
|
52
60
|
- [Repository](https://github.com/UVRN-org/uvrn-packages) — monorepo (this package: `uvrn-api`)
|
|
53
61
|
- [@uvrn/core](https://www.npmjs.com/package/@uvrn/core) — Delta Engine core used by this server
|
|
54
62
|
- [@uvrn/cli](https://www.npmjs.com/package/@uvrn/cli) — run the engine from the command line instead of HTTP
|
package/dist/server.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uvrn/api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
4
7
|
"description": "UVRN REST API — HTTP access to bundle processing",
|
|
5
8
|
"main": "dist/index.js",
|
|
6
9
|
"types": "dist/index.d.ts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"uvrn-api": "dist/server.js"
|
|
12
|
+
},
|
|
7
13
|
"keywords": [
|
|
8
14
|
"uvrn",
|
|
9
15
|
"rest-api",
|
|
@@ -18,11 +24,11 @@
|
|
|
18
24
|
},
|
|
19
25
|
"homepage": "https://github.com/UVRN-org/uvrn-packages#readme",
|
|
20
26
|
"dependencies": {
|
|
21
|
-
"fastify": "^
|
|
22
|
-
"@fastify/cors": "^
|
|
23
|
-
"@fastify/rate-limit": "^
|
|
24
|
-
"@fastify/helmet": "^
|
|
25
|
-
"@uvrn/core": "1.0.
|
|
27
|
+
"fastify": "^5.7.3",
|
|
28
|
+
"@fastify/cors": "^10.0.0",
|
|
29
|
+
"@fastify/rate-limit": "^10.0.0",
|
|
30
|
+
"@fastify/helmet": "^12.0.0",
|
|
31
|
+
"@uvrn/core": "^1.0.3"
|
|
26
32
|
},
|
|
27
33
|
"devDependencies": {
|
|
28
34
|
"@types/node": "^20.0.0",
|
|
@@ -37,8 +43,12 @@
|
|
|
37
43
|
"typescript": "^5.3.0"
|
|
38
44
|
},
|
|
39
45
|
"engines": {
|
|
40
|
-
"node": ">=
|
|
46
|
+
"node": ">=20.0.0"
|
|
41
47
|
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist",
|
|
50
|
+
"README.md"
|
|
51
|
+
],
|
|
42
52
|
"scripts": {
|
|
43
53
|
"dev": "tsx watch src/server.ts",
|
|
44
54
|
"build": "tsc",
|
package/TESTING_GUIDE.md
DELETED
|
@@ -1,495 +0,0 @@
|
|
|
1
|
-
# Delta Engine API - Testing Guide
|
|
2
|
-
|
|
3
|
-
## Quick Start
|
|
4
|
-
|
|
5
|
-
### 1. Start the Development Server
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
cd packages/uvrn-api
|
|
9
|
-
npm run dev
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
**What to look for:**
|
|
13
|
-
- ✅ Server starts without errors
|
|
14
|
-
- ✅ You see log messages like:
|
|
15
|
-
```
|
|
16
|
-
🚀 Delta Engine API server running at http://0.0.0.0:3000
|
|
17
|
-
📊 Health check: http://0.0.0.0:3000/api/v1/health
|
|
18
|
-
📦 Environment: development
|
|
19
|
-
🔒 Rate limit: 100 requests per 1 minute
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### 2. Run Automated Tests
|
|
23
|
-
|
|
24
|
-
From the repository root:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
./test-api.sh
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
This will test all 5 endpoints plus error scenarios.
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Manual Testing
|
|
35
|
-
|
|
36
|
-
### Test 1: Health Check ✅
|
|
37
|
-
|
|
38
|
-
**Purpose:** Verify the server is running and the engine is available
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
curl http://localhost:3000/api/v1/health
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
**Expected Response (200 OK):**
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"status": "healthy",
|
|
48
|
-
"uptime": 12345,
|
|
49
|
-
"version": "1.0.0",
|
|
50
|
-
"engine": {
|
|
51
|
-
"available": true
|
|
52
|
-
},
|
|
53
|
-
"timestamp": "2026-01-15T14:55:00.000Z"
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
**What to look for:**
|
|
58
|
-
- ✅ `status: "healthy"`
|
|
59
|
-
- ✅ `engine.available: true`
|
|
60
|
-
- ✅ HTTP status code `200`
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
### Test 2: Version Information ✅
|
|
65
|
-
|
|
66
|
-
**Purpose:** Check API, engine, and protocol versions
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
curl http://localhost:3000/api/v1/version
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Expected Response (200 OK):**
|
|
73
|
-
```json
|
|
74
|
-
{
|
|
75
|
-
"apiVersion": "1.0.0",
|
|
76
|
-
"engineVersion": "1.0.0",
|
|
77
|
-
"protocolVersion": "1.0"
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**What to look for:**
|
|
82
|
-
- ✅ All version fields present
|
|
83
|
-
- ✅ HTTP status code `200`
|
|
84
|
-
|
|
85
|
-
---
|
|
86
|
-
|
|
87
|
-
### Test 3: Validate Bundle Schema ✅
|
|
88
|
-
|
|
89
|
-
**Purpose:** Validate a bundle without executing it
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
curl -X POST http://localhost:3000/api/v1/delta/validate \
|
|
93
|
-
-H "Content-Type: application/json" \
|
|
94
|
-
-d '{
|
|
95
|
-
"bundleId": "test-bundle-001",
|
|
96
|
-
"claim": "Revenue reconciliation test",
|
|
97
|
-
"dataSpecs": [
|
|
98
|
-
{
|
|
99
|
-
"id": "spec-1",
|
|
100
|
-
"label": "Q4 Revenue Report",
|
|
101
|
-
"sourceKind": "report",
|
|
102
|
-
"originDocIds": ["doc-123"],
|
|
103
|
-
"metrics": [
|
|
104
|
-
{"key": "revenue", "value": 100000, "unit": "USD"}
|
|
105
|
-
]
|
|
106
|
-
}
|
|
107
|
-
],
|
|
108
|
-
"thresholdPct": 0.1
|
|
109
|
-
}'
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Expected Response (200 OK):**
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"valid": true
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
**What to look for:**
|
|
120
|
-
- ✅ `valid: true` for correct bundles
|
|
121
|
-
- ✅ HTTP status code `200`
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
### Test 4: Run Engine on Bundle 🚀
|
|
126
|
-
|
|
127
|
-
**Purpose:** Execute the delta engine and get a receipt
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
curl -X POST http://localhost:3000/api/v1/delta/run \
|
|
131
|
-
-H "Content-Type: application/json" \
|
|
132
|
-
-d '{
|
|
133
|
-
"bundleId": "revenue-q4-2025",
|
|
134
|
-
"claim": "Q4 2025 revenue: $500K",
|
|
135
|
-
"dataSpecs": [
|
|
136
|
-
{
|
|
137
|
-
"id": "spec-1",
|
|
138
|
-
"label": "Accounting System",
|
|
139
|
-
"sourceKind": "report",
|
|
140
|
-
"originDocIds": ["acc-report-q4"],
|
|
141
|
-
"metrics": [
|
|
142
|
-
{"key": "revenue", "value": 500000, "unit": "USD"}
|
|
143
|
-
]
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
"id": "spec-2",
|
|
147
|
-
"label": "Sales Database",
|
|
148
|
-
"sourceKind": "report",
|
|
149
|
-
"originDocIds": ["sales-db-q4"],
|
|
150
|
-
"metrics": [
|
|
151
|
-
{"key": "revenue", "value": 502000, "unit": "USD"}
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
],
|
|
155
|
-
"thresholdPct": 0.05
|
|
156
|
-
}'
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
**Expected Response (200 OK):**
|
|
160
|
-
```json
|
|
161
|
-
{
|
|
162
|
-
"bundleId": "revenue-q4-2025",
|
|
163
|
-
"deltaFinal": 0.004,
|
|
164
|
-
"sources": ["Accounting System", "Sales Database"],
|
|
165
|
-
"rounds": [
|
|
166
|
-
{
|
|
167
|
-
"round": 1,
|
|
168
|
-
"deltasByMetric": {"revenue": 0.004},
|
|
169
|
-
"withinThreshold": true,
|
|
170
|
-
"witnessRequired": false
|
|
171
|
-
}
|
|
172
|
-
],
|
|
173
|
-
"suggestedFixes": [],
|
|
174
|
-
"outcome": "consensus",
|
|
175
|
-
"hash": "abc123..."
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
**What to look for:**
|
|
180
|
-
- ✅ Receipt contains all fields: `bundleId`, `deltaFinal`, `sources`, `rounds`, `outcome`, `hash`
|
|
181
|
-
- ✅ `outcome` is either `"consensus"` or `"indeterminate"`
|
|
182
|
-
- ✅ `hash` is a SHA-256 hash string
|
|
183
|
-
- ✅ HTTP status code `200`
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
### Test 5: Verify Receipt 🔐
|
|
188
|
-
|
|
189
|
-
**Purpose:** Verify receipt integrity by recomputing the hash
|
|
190
|
-
|
|
191
|
-
```bash
|
|
192
|
-
curl -X POST http://localhost:3000/api/v1/delta/verify \
|
|
193
|
-
-H "Content-Type: application/json" \
|
|
194
|
-
-d '{
|
|
195
|
-
"bundleId": "test-001",
|
|
196
|
-
"deltaFinal": 0.02,
|
|
197
|
-
"sources": ["Source A", "Source B"],
|
|
198
|
-
"rounds": [
|
|
199
|
-
{
|
|
200
|
-
"round": 1,
|
|
201
|
-
"deltasByMetric": {"metric1": 0.02},
|
|
202
|
-
"withinThreshold": true,
|
|
203
|
-
"witnessRequired": false
|
|
204
|
-
}
|
|
205
|
-
],
|
|
206
|
-
"suggestedFixes": [],
|
|
207
|
-
"outcome": "consensus",
|
|
208
|
-
"hash": "abc123def456..."
|
|
209
|
-
}'
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
**Expected Response (200 OK):**
|
|
213
|
-
```json
|
|
214
|
-
{
|
|
215
|
-
"verified": true,
|
|
216
|
-
"recomputedHash": "abc123def456..."
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**What to look for:**
|
|
221
|
-
- ✅ `verified: true` if hash matches
|
|
222
|
-
- ✅ `verified: false` if hash doesn't match
|
|
223
|
-
- ✅ `recomputedHash` is provided
|
|
224
|
-
- ✅ HTTP status code `200`
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
## Error Scenario Testing
|
|
229
|
-
|
|
230
|
-
### Test 6: Invalid Bundle Schema ❌
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
curl -X POST http://localhost:3000/api/v1/delta/validate \
|
|
234
|
-
-H "Content-Type: application/json" \
|
|
235
|
-
-d '{
|
|
236
|
-
"bundleId": "incomplete",
|
|
237
|
-
"claim": "Missing dataSpecs field"
|
|
238
|
-
}'
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
**Expected Response (200 OK with validation errors):**
|
|
242
|
-
```json
|
|
243
|
-
{
|
|
244
|
-
"valid": false,
|
|
245
|
-
"errors": [
|
|
246
|
-
{
|
|
247
|
-
"field": "bundle",
|
|
248
|
-
"message": "Missing required field: dataSpecs"
|
|
249
|
-
}
|
|
250
|
-
]
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
**What to look for:**
|
|
255
|
-
- ✅ `valid: false`
|
|
256
|
-
- ✅ `errors` array with details
|
|
257
|
-
- ✅ HTTP status code `200` (validation succeeded, bundle failed)
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
### Test 7: Wrong Content-Type ❌
|
|
262
|
-
|
|
263
|
-
```bash
|
|
264
|
-
curl -X POST http://localhost:3000/api/v1/delta/run \
|
|
265
|
-
-H "Content-Type: text/plain" \
|
|
266
|
-
-d 'not json'
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
**Expected Response (415 Unsupported Media Type):**
|
|
270
|
-
```json
|
|
271
|
-
{
|
|
272
|
-
"error": {
|
|
273
|
-
"code": "UNSUPPORTED_MEDIA_TYPE",
|
|
274
|
-
"message": "Content-Type must be application/json",
|
|
275
|
-
"details": {
|
|
276
|
-
"receivedContentType": "text/plain"
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
**What to look for:**
|
|
283
|
-
- ✅ HTTP status code `415`
|
|
284
|
-
- ✅ Error message explains the issue
|
|
285
|
-
|
|
286
|
-
---
|
|
287
|
-
|
|
288
|
-
### Test 8: Malformed JSON ❌
|
|
289
|
-
|
|
290
|
-
```bash
|
|
291
|
-
curl -X POST http://localhost:3000/api/v1/delta/run \
|
|
292
|
-
-H "Content-Type: application/json" \
|
|
293
|
-
-d '{invalid json'
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
**Expected Response (400 Bad Request):**
|
|
297
|
-
```json
|
|
298
|
-
{
|
|
299
|
-
"error": {
|
|
300
|
-
"code": "BAD_REQUEST",
|
|
301
|
-
"message": "Invalid JSON",
|
|
302
|
-
"details": {...}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
**What to look for:**
|
|
308
|
-
- ✅ HTTP status code `400`
|
|
309
|
-
- ✅ Clear error message
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
### Test 9: Rate Limiting 🚦
|
|
314
|
-
|
|
315
|
-
```bash
|
|
316
|
-
# Run this in a loop to exceed rate limit
|
|
317
|
-
for i in {1..105}; do
|
|
318
|
-
curl -s http://localhost:3000/api/v1/health > /dev/null
|
|
319
|
-
done
|
|
320
|
-
curl http://localhost:3000/api/v1/health
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
**Expected Response (429 Too Many Requests):**
|
|
324
|
-
```json
|
|
325
|
-
{
|
|
326
|
-
"error": {
|
|
327
|
-
"code": "RATE_LIMIT_EXCEEDED",
|
|
328
|
-
"message": "Too many requests, please try again later",
|
|
329
|
-
"details": {
|
|
330
|
-
"rateLimitMax": 100,
|
|
331
|
-
"timeWindow": "1 minute"
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
**What to look for:**
|
|
338
|
-
- ✅ HTTP status code `429`
|
|
339
|
-
- ✅ Rate limit details in response
|
|
340
|
-
|
|
341
|
-
---
|
|
342
|
-
|
|
343
|
-
### Test 10: 404 Not Found ❌
|
|
344
|
-
|
|
345
|
-
```bash
|
|
346
|
-
curl http://localhost:3000/api/v1/nonexistent
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**Expected Response (404 Not Found):**
|
|
350
|
-
```json
|
|
351
|
-
{
|
|
352
|
-
"error": {
|
|
353
|
-
"code": "NOT_FOUND",
|
|
354
|
-
"message": "Route GET /api/v1/nonexistent not found",
|
|
355
|
-
"details": {
|
|
356
|
-
"method": "GET",
|
|
357
|
-
"url": "/api/v1/nonexistent"
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
**What to look for:**
|
|
364
|
-
- ✅ HTTP status code `404`
|
|
365
|
-
- ✅ Clear error message with URL
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
## Testing with Real Bundles
|
|
370
|
-
|
|
371
|
-
You can test with existing demo bundles from the CLI:
|
|
372
|
-
|
|
373
|
-
```bash
|
|
374
|
-
# Find demo bundles
|
|
375
|
-
ls ../../examples/*.json
|
|
376
|
-
|
|
377
|
-
# Test with a real bundle
|
|
378
|
-
curl -X POST http://localhost:3000/api/v1/delta/run \
|
|
379
|
-
-H "Content-Type: application/json" \
|
|
380
|
-
-d @../../examples/revenue-bundle.json
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Server Logs
|
|
386
|
-
|
|
387
|
-
When running in development mode (`npm run dev`), you'll see detailed logs:
|
|
388
|
-
|
|
389
|
-
**Good logs to look for:**
|
|
390
|
-
```
|
|
391
|
-
[INFO] Incoming request - GET /api/v1/health
|
|
392
|
-
[INFO] Request completed - Status: 200, Time: 5ms
|
|
393
|
-
```
|
|
394
|
-
|
|
395
|
-
**Error logs:**
|
|
396
|
-
```
|
|
397
|
-
[ERROR] Engine execution failed - Invalid bundle structure
|
|
398
|
-
[WARN] Rate limit exceeded for IP 127.0.0.1
|
|
399
|
-
```
|
|
400
|
-
|
|
401
|
-
---
|
|
402
|
-
|
|
403
|
-
## Checklist: Server is Working ✅
|
|
404
|
-
|
|
405
|
-
After running all tests, verify:
|
|
406
|
-
|
|
407
|
-
- [ ] Health endpoint returns `healthy` status
|
|
408
|
-
- [ ] Version endpoint returns correct versions
|
|
409
|
-
- [ ] Validate endpoint accepts valid bundles
|
|
410
|
-
- [ ] Validate endpoint rejects invalid bundles
|
|
411
|
-
- [ ] Run endpoint executes engine and returns receipt
|
|
412
|
-
- [ ] Verify endpoint checks receipt integrity
|
|
413
|
-
- [ ] Rate limiting kicks in after 100 requests
|
|
414
|
-
- [ ] Wrong content-type returns 415 error
|
|
415
|
-
- [ ] Malformed JSON returns 400 error
|
|
416
|
-
- [ ] Non-existent routes return 404 error
|
|
417
|
-
- [ ] All responses have correct error structure
|
|
418
|
-
- [ ] Server logs requests and responses
|
|
419
|
-
- [ ] No crashes or unhandled exceptions
|
|
420
|
-
|
|
421
|
-
---
|
|
422
|
-
|
|
423
|
-
## Performance Testing
|
|
424
|
-
|
|
425
|
-
To test performance and concurrent requests:
|
|
426
|
-
|
|
427
|
-
```bash
|
|
428
|
-
# Install autocannon if not already installed
|
|
429
|
-
npm install -g autocannon
|
|
430
|
-
|
|
431
|
-
# Run load test (100 requests, 10 concurrent)
|
|
432
|
-
autocannon -c 10 -d 10 http://localhost:3000/api/v1/health
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
**What to look for:**
|
|
436
|
-
- ✅ Response times < 100ms for health endpoint
|
|
437
|
-
- ✅ No 5xx errors under load
|
|
438
|
-
- ✅ Rate limiting works correctly
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Troubleshooting
|
|
443
|
-
|
|
444
|
-
### Server won't start
|
|
445
|
-
|
|
446
|
-
1. Check if port 3000 is already in use:
|
|
447
|
-
```bash
|
|
448
|
-
lsof -i :3000
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
2. Use a different port:
|
|
452
|
-
```bash
|
|
453
|
-
PORT=3001 npm run dev
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
### Engine not available
|
|
457
|
-
|
|
458
|
-
If health check shows `engine.available: false`:
|
|
459
|
-
|
|
460
|
-
1. Ensure `@uvrn/core` is installed:
|
|
461
|
-
```bash
|
|
462
|
-
npm list @uvrn/core
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
2. Build the engine package:
|
|
466
|
-
```bash
|
|
467
|
-
cd ../uvrn-core && npm run build
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
### CORS errors (in browser)
|
|
471
|
-
|
|
472
|
-
If testing from a browser and seeing CORS errors:
|
|
473
|
-
|
|
474
|
-
1. Set allowed origins in `.env`:
|
|
475
|
-
```
|
|
476
|
-
CORS_ORIGINS=http://localhost:3000,http://localhost:8080
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
2. Restart the server
|
|
480
|
-
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
## Next Steps
|
|
484
|
-
|
|
485
|
-
Once all tests pass:
|
|
486
|
-
1. ✅ Proceed to Task A.2.3 (OpenAPI Specification)
|
|
487
|
-
2. ✅ Add integration tests with Jest
|
|
488
|
-
3. ✅ Deploy to staging environment
|
|
489
|
-
4. ✅ Run load tests with realistic traffic
|
|
490
|
-
|
|
491
|
-
---
|
|
492
|
-
|
|
493
|
-
**Created:** 2026-01-15
|
|
494
|
-
**API Version:** 1.0.0
|
|
495
|
-
**Last Updated:** 2026-01-15
|
package/jest.config.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
preset: 'ts-jest',
|
|
3
|
-
testEnvironment: 'node',
|
|
4
|
-
roots: ['<rootDir>/tests'],
|
|
5
|
-
testMatch: ['**/*.test.ts'],
|
|
6
|
-
collectCoverageFrom: [
|
|
7
|
-
'src/**/*.ts',
|
|
8
|
-
'!src/**/*.d.ts',
|
|
9
|
-
'!src/index.ts',
|
|
10
|
-
],
|
|
11
|
-
coverageDirectory: 'coverage',
|
|
12
|
-
coverageReporters: ['text', 'lcov', 'html'],
|
|
13
|
-
};
|
package/src/config/loader.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration Loader
|
|
3
|
-
* Loads and validates server configuration from environment variables
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { ServerConfig, ConfigValidationError } from './types';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Load configuration from environment variables with defaults
|
|
10
|
-
*/
|
|
11
|
-
export function loadConfig(): ServerConfig {
|
|
12
|
-
const config: ServerConfig = {
|
|
13
|
-
port: parseInt(process.env.PORT || '3000', 10),
|
|
14
|
-
host: process.env.HOST || '0.0.0.0',
|
|
15
|
-
corsOrigins: process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) || ['*'],
|
|
16
|
-
rateLimitMax: parseInt(process.env.RATE_LIMIT_MAX || '100', 10),
|
|
17
|
-
rateLimitTimeWindow: process.env.RATE_LIMIT_TIME_WINDOW || '1 minute',
|
|
18
|
-
logLevel: (process.env.LOG_LEVEL as ServerConfig['logLevel']) || 'info',
|
|
19
|
-
nodeEnv: (process.env.NODE_ENV as ServerConfig['nodeEnv']) || 'development'
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const errors = validateConfig(config);
|
|
23
|
-
if (errors.length > 0) {
|
|
24
|
-
const errorMessages = errors.map(e => `${e.field}: ${e.message}`).join(', ');
|
|
25
|
-
throw new Error(`Configuration validation failed: ${errorMessages}`);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return config;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Validate configuration values
|
|
33
|
-
*/
|
|
34
|
-
function validateConfig(config: ServerConfig): ConfigValidationError[] {
|
|
35
|
-
const errors: ConfigValidationError[] = [];
|
|
36
|
-
|
|
37
|
-
if (config.port < 1 || config.port > 65535) {
|
|
38
|
-
errors.push({ field: 'port', message: 'Must be between 1 and 65535' });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!config.host) {
|
|
42
|
-
errors.push({ field: 'host', message: 'Host cannot be empty' });
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (config.rateLimitMax < 1) {
|
|
46
|
-
errors.push({ field: 'rateLimitMax', message: 'Must be at least 1' });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const validLogLevels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
50
|
-
if (!validLogLevels.includes(config.logLevel)) {
|
|
51
|
-
errors.push({ field: 'logLevel', message: `Must be one of: ${validLogLevels.join(', ')}` });
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const validNodeEnvs = ['development', 'production', 'test'];
|
|
55
|
-
if (!validNodeEnvs.includes(config.nodeEnv)) {
|
|
56
|
-
errors.push({ field: 'nodeEnv', message: `Must be one of: ${validNodeEnvs.join(', ')}` });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return errors;
|
|
60
|
-
}
|
package/src/config/types.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server Configuration Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface ServerConfig {
|
|
6
|
-
port: number;
|
|
7
|
-
host: string;
|
|
8
|
-
corsOrigins: string[];
|
|
9
|
-
rateLimitMax: number;
|
|
10
|
-
rateLimitTimeWindow: string;
|
|
11
|
-
logLevel: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
12
|
-
nodeEnv: 'development' | 'production' | 'test';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ConfigValidationError {
|
|
16
|
-
field: string;
|
|
17
|
-
message: string;
|
|
18
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delta Engine API - Entry Point
|
|
3
|
-
* Exports for programmatic usage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export { startServer, createServer } from './server';
|
|
7
|
-
export type { ServerConfig } from './config/types';
|
|
8
|
-
export type {
|
|
9
|
-
ErrorResponse,
|
|
10
|
-
HealthResponse,
|
|
11
|
-
VersionResponse,
|
|
12
|
-
ValidationResponse
|
|
13
|
-
} from './types/api';
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Global Error Handler
|
|
3
|
-
* Handles uncaught errors and formats error responses
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { FastifyInstance, FastifyError, FastifyRequest, FastifyReply } from 'fastify';
|
|
7
|
-
import { ErrorResponse } from '../types/api';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Register global error handler
|
|
11
|
-
*/
|
|
12
|
-
export function registerErrorHandler(server: FastifyInstance): void {
|
|
13
|
-
server.setErrorHandler((error: FastifyError, request: FastifyRequest, reply: FastifyReply) => {
|
|
14
|
-
// Log the error
|
|
15
|
-
request.log.error({
|
|
16
|
-
err: error,
|
|
17
|
-
url: request.url,
|
|
18
|
-
method: request.method
|
|
19
|
-
}, 'Request error');
|
|
20
|
-
|
|
21
|
-
// Determine status code
|
|
22
|
-
const statusCode = error.statusCode || 500;
|
|
23
|
-
|
|
24
|
-
// Map error to code
|
|
25
|
-
let errorCode = 'INTERNAL_ERROR';
|
|
26
|
-
if (statusCode === 400) errorCode = 'BAD_REQUEST';
|
|
27
|
-
else if (statusCode === 404) errorCode = 'NOT_FOUND';
|
|
28
|
-
else if (statusCode === 415) errorCode = 'UNSUPPORTED_MEDIA_TYPE';
|
|
29
|
-
else if (statusCode === 429) errorCode = 'RATE_LIMIT_EXCEEDED';
|
|
30
|
-
else if (statusCode === 503) errorCode = 'SERVICE_UNAVAILABLE';
|
|
31
|
-
|
|
32
|
-
// Build error response
|
|
33
|
-
const errorResponse: ErrorResponse = {
|
|
34
|
-
error: {
|
|
35
|
-
code: errorCode,
|
|
36
|
-
message: error.message || 'An unexpected error occurred',
|
|
37
|
-
details: {
|
|
38
|
-
statusCode
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
// Don't expose internal error details in production
|
|
44
|
-
if (process.env.NODE_ENV !== 'production' && error.stack) {
|
|
45
|
-
errorResponse.error.details = {
|
|
46
|
-
...errorResponse.error.details,
|
|
47
|
-
stack: error.stack
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
reply.code(statusCode).send(errorResponse);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
// Handle 404 errors
|
|
55
|
-
server.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => {
|
|
56
|
-
const errorResponse: ErrorResponse = {
|
|
57
|
-
error: {
|
|
58
|
-
code: 'NOT_FOUND',
|
|
59
|
-
message: `Route ${request.method} ${request.url} not found`,
|
|
60
|
-
details: {
|
|
61
|
-
method: request.method,
|
|
62
|
-
url: request.url
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
reply.code(404).send(errorResponse);
|
|
68
|
-
});
|
|
69
|
-
}
|
package/src/routes/delta.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delta Engine API Routes
|
|
3
|
-
* Endpoints for bundle processing, validation, and verification
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
7
|
-
import {
|
|
8
|
-
DeltaBundle,
|
|
9
|
-
DeltaReceipt,
|
|
10
|
-
runDeltaEngine,
|
|
11
|
-
validateBundle,
|
|
12
|
-
verifyReceipt
|
|
13
|
-
} from '@uvrn/core';
|
|
14
|
-
import { ErrorResponse, ValidationResponse } from '../types/api';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Register delta engine routes
|
|
18
|
-
*/
|
|
19
|
-
export async function registerDeltaRoutes(server: FastifyInstance): Promise<void> {
|
|
20
|
-
// POST /api/v1/delta/run - Execute engine on bundle
|
|
21
|
-
server.post<{
|
|
22
|
-
Body: DeltaBundle;
|
|
23
|
-
Reply: DeltaReceipt | ErrorResponse;
|
|
24
|
-
}>('/api/v1/delta/run', async (request: FastifyRequest<{ Body: DeltaBundle }>, reply: FastifyReply) => {
|
|
25
|
-
try {
|
|
26
|
-
const bundle = request.body;
|
|
27
|
-
|
|
28
|
-
// Validate bundle schema
|
|
29
|
-
const validation = validateBundle(bundle);
|
|
30
|
-
if (!validation.valid) {
|
|
31
|
-
return reply.code(400).send({
|
|
32
|
-
error: {
|
|
33
|
-
code: 'INVALID_BUNDLE',
|
|
34
|
-
message: 'Bundle validation failed',
|
|
35
|
-
details: { validationError: validation.error }
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Execute engine
|
|
41
|
-
const receipt = runDeltaEngine(bundle);
|
|
42
|
-
|
|
43
|
-
return reply.code(200).send(receipt);
|
|
44
|
-
} catch (error) {
|
|
45
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
46
|
-
request.log.error({ error: errorMessage }, 'Engine execution failed');
|
|
47
|
-
|
|
48
|
-
return reply.code(500).send({
|
|
49
|
-
error: {
|
|
50
|
-
code: 'ENGINE_ERROR',
|
|
51
|
-
message: 'Failed to process bundle',
|
|
52
|
-
details: { error: errorMessage }
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// POST /api/v1/delta/validate - Validate bundle schema
|
|
59
|
-
server.post<{
|
|
60
|
-
Body: DeltaBundle;
|
|
61
|
-
Reply: ValidationResponse | ErrorResponse;
|
|
62
|
-
}>('/api/v1/delta/validate', async (request: FastifyRequest<{ Body: DeltaBundle }>, reply: FastifyReply) => {
|
|
63
|
-
try {
|
|
64
|
-
const bundle = request.body;
|
|
65
|
-
const validation = validateBundle(bundle);
|
|
66
|
-
|
|
67
|
-
if (!validation.valid) {
|
|
68
|
-
return reply.code(200).send({
|
|
69
|
-
valid: false,
|
|
70
|
-
errors: [
|
|
71
|
-
{
|
|
72
|
-
field: 'bundle',
|
|
73
|
-
message: validation.error || 'Validation failed'
|
|
74
|
-
}
|
|
75
|
-
]
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return reply.code(200).send({ valid: true });
|
|
80
|
-
} catch (error) {
|
|
81
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
82
|
-
request.log.error({ error: errorMessage }, 'Validation failed');
|
|
83
|
-
|
|
84
|
-
return reply.code(500).send({
|
|
85
|
-
error: {
|
|
86
|
-
code: 'VALIDATION_ERROR',
|
|
87
|
-
message: 'Failed to validate bundle',
|
|
88
|
-
details: { error: errorMessage }
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// POST /api/v1/delta/verify - Verify receipt replay
|
|
95
|
-
server.post<{
|
|
96
|
-
Body: DeltaReceipt;
|
|
97
|
-
Reply: { verified: boolean; recomputedHash?: string } | ErrorResponse;
|
|
98
|
-
}>('/api/v1/delta/verify', async (request: FastifyRequest<{ Body: DeltaReceipt }>, reply: FastifyReply) => {
|
|
99
|
-
try {
|
|
100
|
-
const receipt = request.body;
|
|
101
|
-
const verifyResult = verifyReceipt(receipt);
|
|
102
|
-
|
|
103
|
-
return reply.code(200).send({
|
|
104
|
-
verified: verifyResult.verified,
|
|
105
|
-
recomputedHash: verifyResult.recomputedHash
|
|
106
|
-
});
|
|
107
|
-
} catch (error) {
|
|
108
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
109
|
-
request.log.error({ error: errorMessage }, 'Verification failed');
|
|
110
|
-
|
|
111
|
-
return reply.code(500).send({
|
|
112
|
-
error: {
|
|
113
|
-
code: 'VERIFICATION_ERROR',
|
|
114
|
-
message: 'Failed to verify receipt',
|
|
115
|
-
details: { error: errorMessage }
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
package/src/routes/health.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Health and Version Routes
|
|
3
|
-
* System status and version information endpoints
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
7
|
-
import { HealthResponse, VersionResponse } from '../types/api';
|
|
8
|
-
|
|
9
|
-
const SERVER_START_TIME = Date.now();
|
|
10
|
-
const API_VERSION = '1.0.0';
|
|
11
|
-
const PROTOCOL_VERSION = '1.0';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Register health and version routes
|
|
15
|
-
*/
|
|
16
|
-
export async function registerHealthRoutes(server: FastifyInstance): Promise<void> {
|
|
17
|
-
// GET /api/v1/health - Health check endpoint
|
|
18
|
-
server.get<{
|
|
19
|
-
Reply: HealthResponse;
|
|
20
|
-
}>('/api/v1/health', async (_request: FastifyRequest, reply: FastifyReply) => {
|
|
21
|
-
const uptime = Date.now() - SERVER_START_TIME;
|
|
22
|
-
|
|
23
|
-
// Check if engine is available by attempting to import it
|
|
24
|
-
let engineAvailable = true;
|
|
25
|
-
try {
|
|
26
|
-
require('@uvrn/core');
|
|
27
|
-
} catch {
|
|
28
|
-
engineAvailable = false;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const health: HealthResponse = {
|
|
32
|
-
status: engineAvailable ? 'healthy' : 'unhealthy',
|
|
33
|
-
uptime,
|
|
34
|
-
version: API_VERSION,
|
|
35
|
-
engine: {
|
|
36
|
-
available: engineAvailable
|
|
37
|
-
},
|
|
38
|
-
timestamp: new Date().toISOString()
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const statusCode = engineAvailable ? 200 : 503;
|
|
42
|
-
return reply.code(statusCode).send(health);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// GET /api/v1/version - Version information
|
|
46
|
-
server.get<{
|
|
47
|
-
Reply: VersionResponse;
|
|
48
|
-
}>('/api/v1/version', async (_request: FastifyRequest, reply: FastifyReply) => {
|
|
49
|
-
let engineVersion = 'unknown';
|
|
50
|
-
try {
|
|
51
|
-
const enginePkg = require('@uvrn/core/package.json');
|
|
52
|
-
engineVersion = enginePkg.version;
|
|
53
|
-
} catch {
|
|
54
|
-
// Engine version not available
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const version: VersionResponse = {
|
|
58
|
-
apiVersion: API_VERSION,
|
|
59
|
-
engineVersion,
|
|
60
|
-
protocolVersion: PROTOCOL_VERSION
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return reply.code(200).send(version);
|
|
64
|
-
});
|
|
65
|
-
}
|
package/src/server.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delta Engine API Server
|
|
3
|
-
* Fastify-based REST API for bundle processing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import Fastify, { FastifyInstance } from 'fastify';
|
|
7
|
-
import cors from '@fastify/cors';
|
|
8
|
-
import helmet from '@fastify/helmet';
|
|
9
|
-
import rateLimit from '@fastify/rate-limit';
|
|
10
|
-
import { loadConfig } from './config/loader';
|
|
11
|
-
import { ServerConfig } from './config/types';
|
|
12
|
-
import { registerDeltaRoutes } from './routes/delta';
|
|
13
|
-
import { registerHealthRoutes } from './routes/health';
|
|
14
|
-
import { registerErrorHandler } from './middleware/errorHandler';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create and configure Fastify server instance
|
|
18
|
-
*/
|
|
19
|
-
export async function createServer(config?: ServerConfig): Promise<FastifyInstance> {
|
|
20
|
-
// Load configuration
|
|
21
|
-
const serverConfig = config || loadConfig();
|
|
22
|
-
|
|
23
|
-
// Create Fastify instance with logging
|
|
24
|
-
const server = Fastify({
|
|
25
|
-
logger: {
|
|
26
|
-
level: serverConfig.logLevel,
|
|
27
|
-
transport: serverConfig.nodeEnv === 'development' ? {
|
|
28
|
-
target: 'pino-pretty',
|
|
29
|
-
options: {
|
|
30
|
-
translateTime: 'HH:MM:ss Z',
|
|
31
|
-
ignore: 'pid,hostname'
|
|
32
|
-
}
|
|
33
|
-
} : undefined
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Register plugins
|
|
38
|
-
await server.register(helmet, {
|
|
39
|
-
contentSecurityPolicy: serverConfig.nodeEnv === 'production' ? undefined : false
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
await server.register(cors, {
|
|
43
|
-
origin: serverConfig.corsOrigins,
|
|
44
|
-
credentials: true
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
await server.register(rateLimit, {
|
|
48
|
-
max: serverConfig.rateLimitMax,
|
|
49
|
-
timeWindow: serverConfig.rateLimitTimeWindow,
|
|
50
|
-
errorResponseBuilder: () => ({
|
|
51
|
-
error: {
|
|
52
|
-
code: 'RATE_LIMIT_EXCEEDED',
|
|
53
|
-
message: 'Too many requests, please try again later',
|
|
54
|
-
details: {
|
|
55
|
-
rateLimitMax: serverConfig.rateLimitMax,
|
|
56
|
-
timeWindow: serverConfig.rateLimitTimeWindow
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Add request logging hook
|
|
63
|
-
server.addHook('onRequest', async (request, _reply) => {
|
|
64
|
-
request.log.info({
|
|
65
|
-
url: request.url,
|
|
66
|
-
method: request.method,
|
|
67
|
-
ip: request.ip
|
|
68
|
-
}, 'Incoming request');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
server.addHook('onResponse', async (request, reply) => {
|
|
72
|
-
request.log.info({
|
|
73
|
-
url: request.url,
|
|
74
|
-
method: request.method,
|
|
75
|
-
statusCode: reply.statusCode,
|
|
76
|
-
responseTime: reply.elapsedTime
|
|
77
|
-
}, 'Request completed');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Validate content-type for POST requests
|
|
81
|
-
server.addHook('preHandler', async (request, reply) => {
|
|
82
|
-
if (request.method === 'POST' && request.url.startsWith('/api/v1/delta/')) {
|
|
83
|
-
const contentType = request.headers['content-type'];
|
|
84
|
-
if (!contentType || !contentType.includes('application/json')) {
|
|
85
|
-
return reply.code(415).send({
|
|
86
|
-
error: {
|
|
87
|
-
code: 'UNSUPPORTED_MEDIA_TYPE',
|
|
88
|
-
message: 'Content-Type must be application/json',
|
|
89
|
-
details: {
|
|
90
|
-
receivedContentType: contentType || 'none'
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Register routes
|
|
99
|
-
await registerHealthRoutes(server);
|
|
100
|
-
await registerDeltaRoutes(server);
|
|
101
|
-
|
|
102
|
-
// Register error handler (must be last)
|
|
103
|
-
registerErrorHandler(server);
|
|
104
|
-
|
|
105
|
-
return server;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Start the server
|
|
110
|
-
*/
|
|
111
|
-
export async function startServer(config?: ServerConfig): Promise<FastifyInstance> {
|
|
112
|
-
const serverConfig = config || loadConfig();
|
|
113
|
-
const server = await createServer(serverConfig);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
await server.listen({
|
|
117
|
-
port: serverConfig.port,
|
|
118
|
-
host: serverConfig.host
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
server.log.info(`🚀 Delta Engine API server running at http://${serverConfig.host}:${serverConfig.port}`);
|
|
122
|
-
server.log.info(`📊 Health check: http://${serverConfig.host}:${serverConfig.port}/api/v1/health`);
|
|
123
|
-
server.log.info(`📦 Environment: ${serverConfig.nodeEnv}`);
|
|
124
|
-
server.log.info(`🔒 Rate limit: ${serverConfig.rateLimitMax} requests per ${serverConfig.rateLimitTimeWindow}`);
|
|
125
|
-
|
|
126
|
-
return server;
|
|
127
|
-
} catch (error) {
|
|
128
|
-
server.log.error(error);
|
|
129
|
-
throw error;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Start server if run directly
|
|
134
|
-
if (require.main === module) {
|
|
135
|
-
startServer().catch((error) => {
|
|
136
|
-
console.error('Failed to start server:', error);
|
|
137
|
-
process.exit(1);
|
|
138
|
-
});
|
|
139
|
-
}
|
package/src/types/api.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API-specific type definitions
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface ErrorResponse {
|
|
6
|
-
error: {
|
|
7
|
-
code: string;
|
|
8
|
-
message: string;
|
|
9
|
-
details?: Record<string, unknown>;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export interface HealthResponse {
|
|
14
|
-
status: 'healthy' | 'unhealthy';
|
|
15
|
-
uptime: number;
|
|
16
|
-
version: string;
|
|
17
|
-
engine: {
|
|
18
|
-
available: boolean;
|
|
19
|
-
};
|
|
20
|
-
timestamp: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface VersionResponse {
|
|
24
|
-
apiVersion: string;
|
|
25
|
-
engineVersion: string;
|
|
26
|
-
protocolVersion: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ValidationResponse {
|
|
30
|
-
valid: boolean;
|
|
31
|
-
errors?: Array<{
|
|
32
|
-
field: string;
|
|
33
|
-
message: string;
|
|
34
|
-
}>;
|
|
35
|
-
}
|