arckode-framework 1.1.0 → 1.1.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/kernel/framework.ts +57 -6
- package/package.json +1 -1
package/kernel/framework.ts
CHANGED
|
@@ -1252,6 +1252,55 @@ export interface ServerAdapter {
|
|
|
1252
1252
|
stop(): Promise<void>
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
1255
|
+
// ── Envelope estándar de respuesta API ──────────────────────────
|
|
1256
|
+
// Todas las respuestas siguen este contrato. Un solo lugar para cambiar el formato.
|
|
1257
|
+
// Inspirado en JSend + convenciones de GitHub/Laravel:
|
|
1258
|
+
// success → { success: true, data: T, meta: { pagination? } | null, error: null }
|
|
1259
|
+
// error → { success: false, data: null, meta: null, error: { code, message, details } }
|
|
1260
|
+
export interface ApiResponse<T = unknown> {
|
|
1261
|
+
success: boolean
|
|
1262
|
+
data: T | null
|
|
1263
|
+
meta: { pagination?: unknown } | null
|
|
1264
|
+
error: { code: string; message: string; details?: unknown } | null
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
function buildEnvelope(status: number, body: unknown): string {
|
|
1268
|
+
// Errores 4xx / 5xx
|
|
1269
|
+
if (status >= 400) {
|
|
1270
|
+
const b = (body ?? {}) as Record<string, unknown>
|
|
1271
|
+
return JSON.stringify({
|
|
1272
|
+
success: false,
|
|
1273
|
+
data: null,
|
|
1274
|
+
meta: null,
|
|
1275
|
+
error: {
|
|
1276
|
+
code: b.code ?? 'ERROR',
|
|
1277
|
+
message: b.error ?? 'Error',
|
|
1278
|
+
details: b.details ?? null,
|
|
1279
|
+
},
|
|
1280
|
+
} satisfies ApiResponse)
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Sin contenido (DELETE 204)
|
|
1284
|
+
if (body === null || body === undefined) {
|
|
1285
|
+
return JSON.stringify({ success: true, data: null, meta: null, error: null } satisfies ApiResponse)
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const b = body as Record<string, unknown>
|
|
1289
|
+
|
|
1290
|
+
// Lista paginada — el service retorna { data: [], pagination: {} }
|
|
1291
|
+
if (Array.isArray(b.data) && b.pagination !== undefined) {
|
|
1292
|
+
return JSON.stringify({
|
|
1293
|
+
success: true,
|
|
1294
|
+
data: b.data,
|
|
1295
|
+
meta: { pagination: b.pagination },
|
|
1296
|
+
error: null,
|
|
1297
|
+
} satisfies ApiResponse)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Recurso único o cualquier otro payload exitoso
|
|
1301
|
+
return JSON.stringify({ success: true, data: body, meta: null, error: null } satisfies ApiResponse)
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1255
1304
|
export class NodeServer implements ServerAdapter {
|
|
1256
1305
|
private server?: ReturnType<typeof createNodeServer>
|
|
1257
1306
|
private maxBodyBytes: number
|
|
@@ -1314,8 +1363,10 @@ export class NodeServer implements ServerAdapter {
|
|
|
1314
1363
|
|
|
1315
1364
|
// ── Respuesta normal (JSON o binario comprimido) ──
|
|
1316
1365
|
const isBuffer = Buffer.isBuffer(res.body)
|
|
1317
|
-
|
|
1318
|
-
|
|
1366
|
+
// 204 prohíbe body por spec HTTP — con envelope usamos 200 para "no content"
|
|
1367
|
+
const effectiveStatus = res.status === 204 ? 200 : res.status
|
|
1368
|
+
const responseBody = isBuffer ? res.body : buildEnvelope(effectiveStatus, res.body)
|
|
1369
|
+
nodeRes.writeHead(effectiveStatus, {
|
|
1319
1370
|
'Content-Type': 'application/json',
|
|
1320
1371
|
'X-Request-Id': req.id,
|
|
1321
1372
|
...res.headers,
|
|
@@ -1325,13 +1376,13 @@ export class NodeServer implements ServerAdapter {
|
|
|
1325
1376
|
const httpStatus = (error as any)?.httpStatus
|
|
1326
1377
|
if (httpStatus) {
|
|
1327
1378
|
nodeRes.writeHead(httpStatus, { 'Content-Type': 'application/json' })
|
|
1328
|
-
nodeRes.end(
|
|
1379
|
+
nodeRes.end(buildEnvelope(httpStatus, { error: (error as Error).message, code: 'REQUEST_ERROR' }))
|
|
1329
1380
|
return
|
|
1330
1381
|
}
|
|
1331
1382
|
const stack = error instanceof Error ? error.stack : String(error)
|
|
1332
1383
|
this.logger.error('Error no manejado en HTTP', { error: String(error), stack })
|
|
1333
1384
|
nodeRes.writeHead(500, { 'Content-Type': 'application/json' })
|
|
1334
|
-
nodeRes.end(
|
|
1385
|
+
nodeRes.end(buildEnvelope(500, { error: 'Error interno', code: 'INTERNAL_ERROR' }))
|
|
1335
1386
|
} finally {
|
|
1336
1387
|
this.activeRequests--
|
|
1337
1388
|
}
|
|
@@ -1968,6 +2019,6 @@ export default {
|
|
|
1968
2019
|
// Seeds
|
|
1969
2020
|
SeedRunner,
|
|
1970
2021
|
|
|
1971
|
-
//
|
|
1972
|
-
//
|
|
2022
|
+
// Response envelope
|
|
2023
|
+
// ApiResponse interface is exported as named export above
|
|
1973
2024
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arckode-framework",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "AI-first TypeScript/Bun framework. Modular, SOLID, zero magic. The AI reads the composition root and knows everything.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./kernel/framework.ts",
|