arckode-framework 1.3.2 → 1.4.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/adapters/jwt.ts +6 -4
- package/adapters/mysql.ts +7 -2
- package/adapters/postgres.ts +37 -0
- package/adapters/sqlite.ts +7 -1
- package/adapters/vendor.d.ts +48 -0
- package/cli/analyze/checks.ts +333 -0
- package/cli/analyze/index.ts +44 -0
- package/cli/analyze/report.ts +107 -0
- package/cli/analyze/types.ts +46 -0
- package/cli/analyze/utils.ts +36 -0
- package/cli/analyze.ts +2 -647
- package/cli/commands/db-migrate.ts +213 -89
- package/cli/commands/db-seed.ts +97 -32
- package/cli/commands/db-utils.ts +192 -0
- package/cli/commands/new.ts +175 -0
- package/cli/commands/routes.ts +94 -0
- package/cli/index.ts +57 -404
- package/cli/stubs/module/core.ts +162 -0
- package/cli/stubs/module/data.ts +171 -0
- package/cli/stubs/module/index.ts +5 -0
- package/cli/stubs/module/service.ts +198 -0
- package/cli/stubs/module/types.ts +12 -0
- package/cli/stubs/module-stub.ts +2 -552
- package/kernel/auth.ts +114 -0
- package/kernel/cache.ts +37 -0
- package/kernel/config.ts +129 -0
- package/kernel/container.ts +64 -0
- package/kernel/db/orm-migrate.ts +136 -0
- package/kernel/db/orm-repository.ts +45 -0
- package/kernel/db/orm-utils.ts +93 -0
- package/kernel/db/orm.ts +254 -0
- package/kernel/db/transactor.ts +17 -0
- package/kernel/db/types.ts +72 -0
- package/kernel/errors.ts +102 -0
- package/kernel/framework.default.ts +41 -0
- package/kernel/framework.ts +8 -2144
- package/kernel/http/router.ts +131 -0
- package/kernel/http/server.ts +303 -0
- package/kernel/http/types.ts +56 -0
- package/kernel/index.ts +25 -0
- package/kernel/logger.ts +50 -0
- package/kernel/middlewares.ts +19 -7
- package/kernel/modules/create-module.ts +5 -0
- package/kernel/modules/system.ts +149 -0
- package/kernel/modules/types.ts +46 -0
- package/kernel/seeds.ts +48 -0
- package/kernel/static.ts +11 -2
- package/kernel/testing.ts +8 -3
- package/kernel/validator.ts +116 -0
- package/modules/events/index.ts +19 -3
- package/modules/mail/index.ts +14 -2
- package/modules/storage/local-adapter.ts +19 -5
- package/modules/ws/index.ts +123 -18
- package/package.json +8 -11
- package/skills/auth/SKILL.md +36 -220
- package/skills/cli/SKILL.md +32 -251
- package/skills/config/SKILL.md +30 -239
- package/skills/connectors/SKILL.md +32 -295
- package/skills/helpers/SKILL.md +26 -195
- package/skills/middlewares/SKILL.md +30 -280
- package/skills/orm/SKILL.md +42 -349
- package/skills/realtime/SKILL.md +22 -297
- package/skills/services/SKILL.md +40 -183
- package/skills/testing/SKILL.md +34 -266
package/kernel/db/orm.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { InternalError, NotFoundError, ValidationError } from '../errors'
|
|
2
|
+
import type { DbAdapter, FindOptions, ModelDefinition, ModelResult, PageResult } from './types'
|
|
3
|
+
import {
|
|
4
|
+
assertSafeIdentifier,
|
|
5
|
+
serializeForDb,
|
|
6
|
+
deserializeFromDb,
|
|
7
|
+
getAllowedFields,
|
|
8
|
+
buildSelect,
|
|
9
|
+
buildWhere,
|
|
10
|
+
} from './orm-utils'
|
|
11
|
+
import { ormMigrate } from './orm-migrate'
|
|
12
|
+
|
|
13
|
+
export class ORM {
|
|
14
|
+
private models = new Map<string, ModelDefinition>()
|
|
15
|
+
|
|
16
|
+
static readonly SCHEMA_TABLE = '_arckode_schema'
|
|
17
|
+
|
|
18
|
+
constructor(private db: DbAdapter) {}
|
|
19
|
+
|
|
20
|
+
define(name: string, def: ModelDefinition): this {
|
|
21
|
+
assertSafeIdentifier(def.table, `modelo "${name}" → table`)
|
|
22
|
+
for (const fieldName of Object.keys(def.fields)) {
|
|
23
|
+
assertSafeIdentifier(fieldName, `modelo "${name}" → campo`)
|
|
24
|
+
}
|
|
25
|
+
this.models.set(name, def)
|
|
26
|
+
return this
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getDefinition(name: string): ModelDefinition {
|
|
30
|
+
const def = this.models.get(name)
|
|
31
|
+
if (!def) throw new NotFoundError(`Modelo "${name}" no definido`)
|
|
32
|
+
return def
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async transaction<T>(fn: (tx: ORM) => Promise<T>): Promise<T> {
|
|
36
|
+
if (this.db.transaction) {
|
|
37
|
+
return this.db.transaction(async (txAdapter) => {
|
|
38
|
+
const txOrm = new ORM(txAdapter)
|
|
39
|
+
for (const [name, def] of this.models) txOrm.define(name, def)
|
|
40
|
+
return fn(txOrm)
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
throw new InternalError('El adaptador de base de datos no soporta transacciones. Usá SqliteAdapter, MySQLAdapter o PostgresAdapter.')
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── CRUD ──
|
|
47
|
+
|
|
48
|
+
async findMany<T extends object = ModelResult>(name: string, filters?: Record<string, unknown>, options?: FindOptions): Promise<T[]> {
|
|
49
|
+
const def = this.getDefinition(name)
|
|
50
|
+
const { clause, params } = buildWhere(def, filters)
|
|
51
|
+
const selectClause = buildSelect(def, options?.select)
|
|
52
|
+
let sql = `SELECT ${selectClause} FROM ${def.table}${clause}`
|
|
53
|
+
|
|
54
|
+
if (options?.orderBy) {
|
|
55
|
+
const allowed = getAllowedFields(def)
|
|
56
|
+
const clauses = Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]
|
|
57
|
+
const parts = clauses.map(({ field, dir = 'ASC' }) => {
|
|
58
|
+
if (!allowed.has(field)) throw new ValidationError(`Invalid sort field: "${field}"`)
|
|
59
|
+
return `${field} ${dir === 'DESC' ? 'DESC' : 'ASC'}`
|
|
60
|
+
})
|
|
61
|
+
sql += ` ORDER BY ${parts.join(', ')}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (options?.limit !== undefined) {
|
|
65
|
+
sql += ` LIMIT ${Math.max(1, Math.floor(options.limit))}`
|
|
66
|
+
if (options.offset !== undefined) {
|
|
67
|
+
sql += ` OFFSET ${Math.max(0, Math.floor(options.offset))}`
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rows = (await this.db.query(sql, params)) as T[]
|
|
72
|
+
return rows.map(r => deserializeFromDb(def, r as ModelResult) as T)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async findById<T extends object = ModelResult>(name: string, id: string, select?: string[]): Promise<T | null> {
|
|
76
|
+
const def = this.getDefinition(name)
|
|
77
|
+
const selectClause = buildSelect(def, select)
|
|
78
|
+
let sql = `SELECT ${selectClause} FROM ${def.table} WHERE id = ?`
|
|
79
|
+
if (def.softDelete) sql += ' AND deletedAt IS NULL'
|
|
80
|
+
const rows = await this.db.query(sql, [id])
|
|
81
|
+
const row = (rows as ModelResult[])[0] ?? null
|
|
82
|
+
return row ? deserializeFromDb(def, row) as T : null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async findOne<T extends object = ModelResult>(name: string, filters: Record<string, unknown>): Promise<T | null> {
|
|
86
|
+
const results = await this.findMany<T>(name, filters, { limit: 1 })
|
|
87
|
+
return results[0] ?? null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async count(name: string, filters?: Record<string, unknown>): Promise<number> {
|
|
91
|
+
const def = this.getDefinition(name)
|
|
92
|
+
const { clause, params } = buildWhere(def, filters)
|
|
93
|
+
const rows = await this.db.query(`SELECT COUNT(*) as n FROM ${def.table}${clause}`, params)
|
|
94
|
+
return Number((rows as Array<{ n: number | string }>)[0]?.n ?? 0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async paginate<T extends object = ModelResult>(
|
|
98
|
+
name: string,
|
|
99
|
+
filters?: Record<string, unknown>,
|
|
100
|
+
options: FindOptions & { limit: number } = { limit: 20 },
|
|
101
|
+
): Promise<PageResult<T>> {
|
|
102
|
+
const limit = Math.max(1, Math.floor(options.limit))
|
|
103
|
+
const offset = Math.max(0, Math.floor(options.offset ?? 0))
|
|
104
|
+
|
|
105
|
+
const [data, total] = await Promise.all([
|
|
106
|
+
this.findMany<T>(name, filters, { ...options, limit, offset }),
|
|
107
|
+
this.count(name, filters),
|
|
108
|
+
])
|
|
109
|
+
|
|
110
|
+
return { data, total, limit, offset, pages: Math.ceil(total / limit) }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async create<T extends object = ModelResult>(name: string, data: Record<string, unknown>): Promise<T> {
|
|
114
|
+
const def = this.getDefinition(name)
|
|
115
|
+
const allowedFields = new Set(Object.keys(def.fields))
|
|
116
|
+
const id = crypto.randomUUID()
|
|
117
|
+
const now = new Date().toISOString()
|
|
118
|
+
|
|
119
|
+
const record: Record<string, unknown> = { id }
|
|
120
|
+
for (const [k, v] of Object.entries(data)) {
|
|
121
|
+
if (allowedFields.has(k)) record[k] = v
|
|
122
|
+
}
|
|
123
|
+
if (def.timestamps) {
|
|
124
|
+
record.createdAt = now
|
|
125
|
+
record.updatedAt = now
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const dbRecord = serializeForDb(def, record)
|
|
129
|
+
const keys = Object.keys(dbRecord)
|
|
130
|
+
const values = Object.values(dbRecord)
|
|
131
|
+
const placeholders = keys.map(() => '?').join(', ')
|
|
132
|
+
|
|
133
|
+
await this.db.run(
|
|
134
|
+
`INSERT INTO ${def.table} (${keys.join(', ')}) VALUES (${placeholders})`,
|
|
135
|
+
values,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return record as T
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async update<T extends object = ModelResult>(name: string, id: string, data: Record<string, unknown>): Promise<T | null> {
|
|
142
|
+
const def = this.getDefinition(name)
|
|
143
|
+
const allowedFields = new Set(Object.keys(def.fields))
|
|
144
|
+
const now = new Date().toISOString()
|
|
145
|
+
|
|
146
|
+
const record: Record<string, unknown> = {}
|
|
147
|
+
for (const [k, v] of Object.entries(data)) {
|
|
148
|
+
if (allowedFields.has(k)) record[k] = v
|
|
149
|
+
}
|
|
150
|
+
if (def.timestamps) record.updatedAt = now
|
|
151
|
+
|
|
152
|
+
const dbRecord = serializeForDb(def, record)
|
|
153
|
+
const keys = Object.keys(dbRecord)
|
|
154
|
+
if (keys.length === 0) return this.findById<T>(name, id)
|
|
155
|
+
const values = Object.values(dbRecord)
|
|
156
|
+
const setClause = keys.map(k => `${k} = ?`).join(', ')
|
|
157
|
+
|
|
158
|
+
await this.db.run(`UPDATE ${def.table} SET ${setClause} WHERE id = ?`, [...values, id])
|
|
159
|
+
return this.findById<T>(name, id)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async delete(name: string, id: string): Promise<boolean> {
|
|
163
|
+
const def = this.getDefinition(name)
|
|
164
|
+
|
|
165
|
+
if (def.softDelete) {
|
|
166
|
+
const result = await this.db.run(`UPDATE ${def.table} SET deletedAt = ? WHERE id = ?`, [new Date().toISOString(), id])
|
|
167
|
+
return result.changes > 0
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result = await this.db.run(`DELETE FROM ${def.table} WHERE id = ?`, [id])
|
|
171
|
+
return result.changes > 0
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Bulk operations ──
|
|
175
|
+
|
|
176
|
+
async createMany(name: string, records: Record<string, unknown>[]): Promise<ModelResult[]> {
|
|
177
|
+
if (records.length === 0) return []
|
|
178
|
+
const def = this.getDefinition(name)
|
|
179
|
+
const allowedFields = new Set(Object.keys(def.fields))
|
|
180
|
+
const now = new Date().toISOString()
|
|
181
|
+
|
|
182
|
+
const prepared = records.map(data => {
|
|
183
|
+
const record: Record<string, unknown> = { id: crypto.randomUUID() }
|
|
184
|
+
for (const [k, v] of Object.entries(data)) {
|
|
185
|
+
if (allowedFields.has(k)) record[k] = v
|
|
186
|
+
}
|
|
187
|
+
if (def.timestamps) { record.createdAt = now; record.updatedAt = now }
|
|
188
|
+
return record
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const dbPrepared = prepared.map(r => serializeForDb(def, r))
|
|
192
|
+
const keys = Object.keys(dbPrepared[0] ?? {})
|
|
193
|
+
const rowPlaceholders = `(${keys.map(() => '?').join(', ')})`
|
|
194
|
+
const allPlaceholders = dbPrepared.map(() => rowPlaceholders).join(', ')
|
|
195
|
+
const allValues = dbPrepared.flatMap(r => Object.values(r))
|
|
196
|
+
|
|
197
|
+
await this.db.run(
|
|
198
|
+
`INSERT INTO ${def.table} (${keys.join(', ')}) VALUES ${allPlaceholders}`,
|
|
199
|
+
allValues,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
return prepared as ModelResult[]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async updateMany(
|
|
206
|
+
name: string,
|
|
207
|
+
filters: Record<string, unknown>,
|
|
208
|
+
changes: Record<string, unknown>,
|
|
209
|
+
): Promise<number> {
|
|
210
|
+
const def = this.getDefinition(name)
|
|
211
|
+
const allowedFields = new Set(Object.keys(def.fields))
|
|
212
|
+
const { clause, params: whereParams } = buildWhere(def, filters)
|
|
213
|
+
const now = new Date().toISOString()
|
|
214
|
+
|
|
215
|
+
const data: Record<string, unknown> = {}
|
|
216
|
+
for (const [k, v] of Object.entries(changes)) {
|
|
217
|
+
if (allowedFields.has(k)) data[k] = v
|
|
218
|
+
}
|
|
219
|
+
if (def.timestamps) data.updatedAt = now
|
|
220
|
+
|
|
221
|
+
const dbData = serializeForDb(def, data)
|
|
222
|
+
const setClause = Object.keys(dbData).map(k => `${k} = ?`).join(', ')
|
|
223
|
+
const result = await this.db.run(
|
|
224
|
+
`UPDATE ${def.table} SET ${setClause}${clause}`,
|
|
225
|
+
[...Object.values(dbData), ...whereParams],
|
|
226
|
+
)
|
|
227
|
+
return result.changes
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async deleteMany(name: string, filters: Record<string, unknown>): Promise<number> {
|
|
231
|
+
if (Object.keys(filters).length === 0) {
|
|
232
|
+
throw new ValidationError('deleteMany requires at least one filter to avoid deleting the entire table')
|
|
233
|
+
}
|
|
234
|
+
const def = this.getDefinition(name)
|
|
235
|
+
const { clause, params } = buildWhere(def, filters)
|
|
236
|
+
|
|
237
|
+
if (def.softDelete) {
|
|
238
|
+
const result = await this.db.run(
|
|
239
|
+
`UPDATE ${def.table} SET deletedAt = ?${clause}`,
|
|
240
|
+
[new Date().toISOString(), ...params],
|
|
241
|
+
)
|
|
242
|
+
return result.changes
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result = await this.db.run(`DELETE FROM ${def.table}${clause}`, params)
|
|
246
|
+
return result.changes
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Migraciones ──
|
|
250
|
+
|
|
251
|
+
async migrate(opts: { allowDrop?: boolean } = {}): Promise<void> {
|
|
252
|
+
return ormMigrate(this.db, this.models, opts)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RepositoryAdapter, Transactor, TransactionalRepos } from './types'
|
|
2
|
+
import { ORM } from './orm'
|
|
3
|
+
import { OrmRepository } from './orm-repository'
|
|
4
|
+
|
|
5
|
+
export class OrmTransactor implements Transactor {
|
|
6
|
+
constructor(private readonly orm: ORM) {}
|
|
7
|
+
|
|
8
|
+
run<R>(fn: (repos: TransactionalRepos) => Promise<R>): Promise<R> {
|
|
9
|
+
return this.orm.transaction(async (txOrm) => {
|
|
10
|
+
const repos: TransactionalRepos = {
|
|
11
|
+
for: <T extends object = Record<string, unknown>>(modelName: string): RepositoryAdapter<T> =>
|
|
12
|
+
new OrmRepository<T>(txOrm, modelName),
|
|
13
|
+
}
|
|
14
|
+
return fn(repos)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export interface DbAdapter {
|
|
2
|
+
query(sql: string, params?: unknown[]): Promise<unknown[]>
|
|
3
|
+
run(sql: string, params?: unknown[]): Promise<{ changes: number; lastId?: string }>
|
|
4
|
+
close(): Promise<void>
|
|
5
|
+
/** Opcional: soporte de transacciones. Si no lo implementa, ORM.transaction() lanza InternalError. */
|
|
6
|
+
transaction?<T>(fn: (adapter: DbAdapter) => Promise<T>): Promise<T>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FieldDefinition {
|
|
10
|
+
type: 'string' | 'text' | 'number' | 'boolean' | 'json' | 'date'
|
|
11
|
+
required?: boolean
|
|
12
|
+
nullable?: boolean
|
|
13
|
+
default?: unknown
|
|
14
|
+
unique?: boolean
|
|
15
|
+
indexed?: boolean
|
|
16
|
+
maxLength?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ModelDefinition {
|
|
20
|
+
table: string
|
|
21
|
+
fields: Record<string, FieldDefinition>
|
|
22
|
+
timestamps?: boolean
|
|
23
|
+
softDelete?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ModelResult {
|
|
27
|
+
id: string
|
|
28
|
+
[key: string]: unknown
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface OrderByClause {
|
|
32
|
+
field: string
|
|
33
|
+
dir?: 'ASC' | 'DESC'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FindOptions {
|
|
37
|
+
limit?: number
|
|
38
|
+
offset?: number
|
|
39
|
+
orderBy?: OrderByClause | OrderByClause[]
|
|
40
|
+
/** Campos a seleccionar. Si se omite: SELECT *. Los nombres se validan contra el modelo. */
|
|
41
|
+
select?: string[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface PageResult<T = ModelResult> {
|
|
45
|
+
data: T[]
|
|
46
|
+
total: number
|
|
47
|
+
limit: number
|
|
48
|
+
offset: number
|
|
49
|
+
pages: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RepositoryAdapter<T extends object = Record<string, unknown>> {
|
|
53
|
+
findMany(filters?: Record<string, unknown>, options?: FindOptions): Promise<T[]>
|
|
54
|
+
findById(id: string, select?: string[]): Promise<T | null>
|
|
55
|
+
findOne(filters: Record<string, unknown>): Promise<T | null>
|
|
56
|
+
create(data: Omit<T, 'id'>): Promise<T>
|
|
57
|
+
update(id: string, data: Partial<Omit<T, 'id'>>): Promise<T | null>
|
|
58
|
+
delete(id: string): Promise<boolean>
|
|
59
|
+
count(filters?: Record<string, unknown>): Promise<number>
|
|
60
|
+
paginate(
|
|
61
|
+
filters?: Record<string, unknown>,
|
|
62
|
+
options?: FindOptions & { limit: number },
|
|
63
|
+
): Promise<PageResult<T>>
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TransactionalRepos {
|
|
67
|
+
for<T extends object = Record<string, unknown>>(modelName: string): RepositoryAdapter<T>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface Transactor {
|
|
71
|
+
run<R>(fn: (repos: TransactionalRepos) => Promise<R>): Promise<R>
|
|
72
|
+
}
|
package/kernel/errors.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export abstract class ErrorContract extends Error {
|
|
2
|
+
public abstract readonly httpStatus: number
|
|
3
|
+
public abstract readonly isExpected: boolean
|
|
4
|
+
public abstract readonly canRetry: boolean
|
|
5
|
+
public abstract readonly errorCode: string
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
message: string,
|
|
9
|
+
public readonly details?: Record<string, unknown>,
|
|
10
|
+
) {
|
|
11
|
+
super(message)
|
|
12
|
+
this.name = this.constructor.name
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
toJSON(): Record<string, unknown> {
|
|
16
|
+
return {
|
|
17
|
+
error: this.message,
|
|
18
|
+
code: this.errorCode,
|
|
19
|
+
...(this.details ? { details: this.details } : {}),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class ValidationError extends ErrorContract {
|
|
25
|
+
httpStatus = 400
|
|
26
|
+
isExpected = true
|
|
27
|
+
canRetry = false
|
|
28
|
+
errorCode = 'VALIDATION_ERROR'
|
|
29
|
+
constructor(msg: string, public fields?: Record<string, string[]>) {
|
|
30
|
+
super(msg, fields ? { fields } : undefined)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class AuthError extends ErrorContract {
|
|
35
|
+
httpStatus = 401
|
|
36
|
+
isExpected = true
|
|
37
|
+
canRetry = false
|
|
38
|
+
errorCode = 'AUTH_ERROR'
|
|
39
|
+
constructor(message = 'Authentication error', details?: Record<string, unknown>) {
|
|
40
|
+
super(message, details)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ForbiddenError extends ErrorContract {
|
|
45
|
+
httpStatus = 403
|
|
46
|
+
isExpected = true
|
|
47
|
+
canRetry = false
|
|
48
|
+
errorCode = 'FORBIDDEN'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export class NotFoundError extends ErrorContract {
|
|
52
|
+
httpStatus = 404
|
|
53
|
+
isExpected = true
|
|
54
|
+
canRetry = false
|
|
55
|
+
errorCode = 'NOT_FOUND'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export class ConflictError extends ErrorContract {
|
|
59
|
+
httpStatus = 409
|
|
60
|
+
isExpected = true
|
|
61
|
+
canRetry = false
|
|
62
|
+
errorCode = 'CONFLICT'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class RateLimitError extends ErrorContract {
|
|
66
|
+
httpStatus = 429
|
|
67
|
+
isExpected = true
|
|
68
|
+
canRetry = true
|
|
69
|
+
errorCode = 'RATE_LIMIT'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export class RepositoryError extends ErrorContract {
|
|
73
|
+
httpStatus = 500
|
|
74
|
+
isExpected = true
|
|
75
|
+
canRetry = true
|
|
76
|
+
errorCode = 'REPOSITORY_ERROR'
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export class InternalError extends ErrorContract {
|
|
80
|
+
httpStatus = 500
|
|
81
|
+
isExpected = false
|
|
82
|
+
canRetry = false
|
|
83
|
+
errorCode = 'INTERNAL_ERROR'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class PayloadTooLargeError extends ErrorContract {
|
|
87
|
+
httpStatus = 413
|
|
88
|
+
isExpected = true
|
|
89
|
+
canRetry = false
|
|
90
|
+
errorCode = 'PAYLOAD_TOO_LARGE'
|
|
91
|
+
constructor() { super('Payload Too Large') }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class ModuleRuleError extends ErrorContract {
|
|
95
|
+
httpStatus = 500
|
|
96
|
+
isExpected = false
|
|
97
|
+
canRetry = false
|
|
98
|
+
errorCode = 'MODULE_RULE_VIOLATION'
|
|
99
|
+
constructor(module: string, rule: string) {
|
|
100
|
+
super(`[${module}] Regla violada: ${rule}`, { module, rule })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ErrorContract, ValidationError, AuthError, ForbiddenError,
|
|
3
|
+
NotFoundError, ConflictError, RateLimitError, RepositoryError,
|
|
4
|
+
InternalError, ModuleRuleError,
|
|
5
|
+
} from './errors'
|
|
6
|
+
import { Logger, ConsoleTransport } from './logger'
|
|
7
|
+
import { ConfigStore, loadEnv } from './config'
|
|
8
|
+
import { Container } from './container'
|
|
9
|
+
import { ORM, } from './db/orm'
|
|
10
|
+
import { OrmRepository } from './db/orm-repository'
|
|
11
|
+
import { OrmTransactor } from './db/transactor'
|
|
12
|
+
import { Router } from './http/router'
|
|
13
|
+
import { NodeServer } from './http/server'
|
|
14
|
+
import { validateSchema } from './validator'
|
|
15
|
+
import { Auth, } from './auth'
|
|
16
|
+
import { MemoryCache } from './cache'
|
|
17
|
+
import { createModule, } from './modules/create-module'
|
|
18
|
+
import { System } from './modules/system'
|
|
19
|
+
import { SeedRunner } from './seeds'
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
// Errors
|
|
23
|
+
ErrorContract, ValidationError, AuthError, ForbiddenError,
|
|
24
|
+
NotFoundError, ConflictError, RateLimitError, RepositoryError,
|
|
25
|
+
InternalError, ModuleRuleError,
|
|
26
|
+
|
|
27
|
+
// Infrastructure
|
|
28
|
+
Logger, ConsoleTransport,
|
|
29
|
+
ConfigStore, loadEnv,
|
|
30
|
+
Container,
|
|
31
|
+
ORM, OrmRepository, OrmTransactor,
|
|
32
|
+
Router, NodeServer,
|
|
33
|
+
validateSchema,
|
|
34
|
+
Auth, MemoryCache,
|
|
35
|
+
|
|
36
|
+
// Module System
|
|
37
|
+
createModule, System,
|
|
38
|
+
|
|
39
|
+
// Seeds
|
|
40
|
+
SeedRunner,
|
|
41
|
+
}
|