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.
@@ -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
- const responseBody = isBuffer ? res.body : JSON.stringify(res.body)
1318
- nodeRes.writeHead(res.status, {
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(JSON.stringify({ error: (error as Error).message, code: 'REQUEST_ERROR' }))
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(JSON.stringify({ error: 'Error interno', code: 'INTERNAL_ERROR' }))
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
- // Types (re-exported for convenience)
1972
- // All interfaces are exported above
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.0",
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",