arckode-framework 1.0.5 → 1.0.7
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 +63 -19
- package/package.json +1 -1
package/kernel/framework.ts
CHANGED
|
@@ -386,8 +386,9 @@ export interface DbAdapter {
|
|
|
386
386
|
|
|
387
387
|
// ── Definición de campo ──
|
|
388
388
|
export interface FieldDefinition {
|
|
389
|
-
type: 'string' | 'number' | 'boolean' | 'json' | 'date'
|
|
389
|
+
type: 'string' | 'text' | 'number' | 'boolean' | 'json' | 'date'
|
|
390
390
|
required?: boolean
|
|
391
|
+
nullable?: boolean // alias semántico de !required — campo puede ser NULL
|
|
391
392
|
default?: unknown
|
|
392
393
|
unique?: boolean
|
|
393
394
|
indexed?: boolean
|
|
@@ -503,6 +504,28 @@ export class ORM {
|
|
|
503
504
|
|
|
504
505
|
// ── Helpers privados ──
|
|
505
506
|
|
|
507
|
+
private serializeForDb(def: ModelDefinition, record: Record<string, unknown>): Record<string, unknown> {
|
|
508
|
+
const out: Record<string, unknown> = {}
|
|
509
|
+
for (const [k, v] of Object.entries(record)) {
|
|
510
|
+
if (def.fields[k]?.type === 'json' && v !== null && v !== undefined && typeof v !== 'string') {
|
|
511
|
+
out[k] = JSON.stringify(v)
|
|
512
|
+
} else {
|
|
513
|
+
out[k] = v
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return out
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private deserializeFromDb(def: ModelDefinition, row: ModelResult): ModelResult {
|
|
520
|
+
const result = { ...row } as Record<string, unknown>
|
|
521
|
+
for (const [k, field] of Object.entries(def.fields)) {
|
|
522
|
+
if (field.type === 'json' && typeof result[k] === 'string') {
|
|
523
|
+
try { result[k] = JSON.parse(result[k] as string) } catch { /* no es JSON válido */ }
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return result as ModelResult
|
|
527
|
+
}
|
|
528
|
+
|
|
506
529
|
private getAllowedFields(def: ModelDefinition): Set<string> {
|
|
507
530
|
const allowed = new Set(['id', ...Object.keys(def.fields)])
|
|
508
531
|
if (def.timestamps) { allowed.add('createdAt'); allowed.add('updatedAt') }
|
|
@@ -566,7 +589,8 @@ export class ORM {
|
|
|
566
589
|
}
|
|
567
590
|
}
|
|
568
591
|
|
|
569
|
-
|
|
592
|
+
const rows = (await this.db.query(sql, params)) as ModelResult[]
|
|
593
|
+
return rows.map(r => this.deserializeFromDb(def, r))
|
|
570
594
|
}
|
|
571
595
|
|
|
572
596
|
async findById(name: string, id: string, select?: string[]): Promise<ModelResult | null> {
|
|
@@ -575,7 +599,8 @@ export class ORM {
|
|
|
575
599
|
let sql = `SELECT ${selectClause} FROM ${def.table} WHERE id = ?`
|
|
576
600
|
if (def.softDelete) sql += ' AND deletedAt IS NULL'
|
|
577
601
|
const rows = await this.db.query(sql, [id])
|
|
578
|
-
|
|
602
|
+
const row = (rows as ModelResult[])[0] ?? null
|
|
603
|
+
return row ? this.deserializeFromDb(def, row) : null
|
|
579
604
|
}
|
|
580
605
|
|
|
581
606
|
async findOne(name: string, filters: Record<string, unknown>): Promise<ModelResult | null> {
|
|
@@ -621,8 +646,9 @@ export class ORM {
|
|
|
621
646
|
record.updatedAt = now
|
|
622
647
|
}
|
|
623
648
|
|
|
624
|
-
const
|
|
625
|
-
const
|
|
649
|
+
const dbRecord = this.serializeForDb(def, record)
|
|
650
|
+
const keys = Object.keys(dbRecord)
|
|
651
|
+
const values = Object.values(dbRecord)
|
|
626
652
|
const placeholders = keys.map(() => '?').join(', ')
|
|
627
653
|
|
|
628
654
|
await this.db.run(
|
|
@@ -640,8 +666,9 @@ export class ORM {
|
|
|
640
666
|
const record = { ...data }
|
|
641
667
|
if (def.timestamps) record.updatedAt = now
|
|
642
668
|
|
|
643
|
-
const
|
|
644
|
-
const
|
|
669
|
+
const dbRecord = this.serializeForDb(def, record)
|
|
670
|
+
const keys = Object.keys(dbRecord)
|
|
671
|
+
const values = Object.values(dbRecord)
|
|
645
672
|
const setClause = keys.map(k => `${k} = ?`).join(', ')
|
|
646
673
|
|
|
647
674
|
await this.db.run(`UPDATE ${def.table} SET ${setClause} WHERE id = ?`, [...values, id])
|
|
@@ -682,10 +709,11 @@ export class ORM {
|
|
|
682
709
|
return record
|
|
683
710
|
})
|
|
684
711
|
|
|
685
|
-
const
|
|
712
|
+
const dbPrepared = prepared.map(r => this.serializeForDb(def, r))
|
|
713
|
+
const keys = Object.keys(dbPrepared[0] ?? {})
|
|
686
714
|
const rowPlaceholders = `(${keys.map(() => '?').join(', ')})`
|
|
687
|
-
const allPlaceholders =
|
|
688
|
-
const allValues =
|
|
715
|
+
const allPlaceholders = dbPrepared.map(() => rowPlaceholders).join(', ')
|
|
716
|
+
const allValues = dbPrepared.flatMap(r => Object.values(r))
|
|
689
717
|
|
|
690
718
|
await this.db.run(
|
|
691
719
|
`INSERT INTO ${def.table} (${keys.join(', ')}) VALUES ${allPlaceholders}`,
|
|
@@ -711,10 +739,11 @@ export class ORM {
|
|
|
711
739
|
const data = { ...changes }
|
|
712
740
|
if (def.timestamps) data.updatedAt = now
|
|
713
741
|
|
|
714
|
-
const
|
|
742
|
+
const dbData = this.serializeForDb(def, data)
|
|
743
|
+
const setClause = Object.keys(dbData).map(k => `${k} = ?`).join(', ')
|
|
715
744
|
const result = await this.db.run(
|
|
716
745
|
`UPDATE ${def.table} SET ${setClause}${clause}`,
|
|
717
|
-
[...Object.values(
|
|
746
|
+
[...Object.values(dbData), ...whereParams],
|
|
718
747
|
)
|
|
719
748
|
return result.changes
|
|
720
749
|
}
|
|
@@ -867,7 +896,7 @@ export class ORM {
|
|
|
867
896
|
// Postgres / estándar SQL
|
|
868
897
|
try {
|
|
869
898
|
const rows = await this.db.query(
|
|
870
|
-
`SELECT column_name FROM information_schema.columns WHERE table_name = '${table}'`
|
|
899
|
+
`SELECT column_name FROM information_schema.columns WHERE table_name = '${table}' AND table_schema = 'public'`
|
|
871
900
|
)
|
|
872
901
|
if (Array.isArray(rows) && rows.length > 0) {
|
|
873
902
|
return (rows as { column_name: string }[]).map(r => r.column_name)
|
|
@@ -880,6 +909,7 @@ export class ORM {
|
|
|
880
909
|
private fieldTypeToSQL(type: FieldDefinition['type']): string {
|
|
881
910
|
const map: Record<FieldDefinition['type'], string> = {
|
|
882
911
|
string: 'TEXT',
|
|
912
|
+
text: 'TEXT', // alias de string para TEXT largo
|
|
883
913
|
number: 'REAL',
|
|
884
914
|
boolean: 'BOOLEAN', // INTEGER falla en Postgres con DEFAULT false/true
|
|
885
915
|
json: 'TEXT',
|
|
@@ -1032,12 +1062,26 @@ export class Router {
|
|
|
1032
1062
|
})
|
|
1033
1063
|
}
|
|
1034
1064
|
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1065
|
+
// Acepta ambas convenciones:
|
|
1066
|
+
// (path, handler) — sin middlewares
|
|
1067
|
+
// (path, handler, mw[]) — handler primero (legado)
|
|
1068
|
+
// (path, mw[], handler) — middlewares primero (Express-like, convención del framework)
|
|
1069
|
+
private resolve2(
|
|
1070
|
+
handlerOrMw: RouteHandler | MiddlewareHandler[],
|
|
1071
|
+
handlerOrUndefined?: RouteHandler | MiddlewareHandler[],
|
|
1072
|
+
): { handler: RouteHandler; mw: MiddlewareHandler[] } {
|
|
1073
|
+
if (Array.isArray(handlerOrMw)) {
|
|
1074
|
+
return { handler: handlerOrUndefined as RouteHandler, mw: handlerOrMw }
|
|
1075
|
+
}
|
|
1076
|
+
return { handler: handlerOrMw, mw: (handlerOrUndefined as MiddlewareHandler[] | undefined) ?? [] }
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
get(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('GET', path, handler, mw) }
|
|
1080
|
+
post(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('POST', path, handler, mw) }
|
|
1081
|
+
put(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('PUT', path, handler, mw) }
|
|
1082
|
+
patch(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('PATCH', path, handler, mw) }
|
|
1083
|
+
delete(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('DELETE', path, handler, mw) }
|
|
1084
|
+
options(path: string, handlerOrMw: RouteHandler | MiddlewareHandler[], h?: RouteHandler | MiddlewareHandler[]): void { const { handler, mw } = this.resolve2(handlerOrMw, h); this.add('OPTIONS', path, handler, mw) }
|
|
1041
1085
|
|
|
1042
1086
|
async resolve(method: string, path: string, extras?: Partial<HttpRequest>): Promise<HttpResponse> {
|
|
1043
1087
|
const reqId = crypto.randomUUID().slice(0, 8)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arckode-framework",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
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",
|