@arcaelas/dynamite 1.0.19 → 1.0.23
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/package.json +11 -42
- package/src/scripts/load_seed.d.ts +5 -0
- package/src/scripts/load_seed.js +54 -0
- package/src/src/@types/index.d.ts +188 -0
- package/src/src/@types/index.js +9 -0
- package/src/{core → src/core}/client.d.ts +4 -16
- package/src/{core → src/core}/client.js +115 -38
- package/src/{core → src/core}/decorator.d.ts +0 -15
- package/src/{core → src/core}/decorator.js +5 -35
- package/src/src/core/table.d.ts +81 -0
- package/src/src/core/table.js +892 -0
- package/src/{decorators → src/decorators}/indexes.d.ts +1 -1
- package/src/{decorators → src/decorators}/indexes.js +12 -20
- package/src/{decorators → src/decorators}/relations.d.ts +20 -1
- package/src/{decorators → src/decorators}/relations.js +32 -2
- package/src/{decorators → src/decorators}/timestamps.d.ts +4 -4
- package/src/{decorators → src/decorators}/timestamps.js +11 -6
- package/src/{decorators → src/decorators}/transforms.d.ts +17 -4
- package/src/{decorators → src/decorators}/transforms.js +40 -28
- package/src/src/index.d.ts +15 -0
- package/src/{index.js → src/index.js} +8 -22
- package/src/src/index.test.d.ts +6 -0
- package/src/src/index.test.js +789 -0
- package/src/{utils → src/utils}/relations.d.ts +5 -3
- package/src/src/utils/relations.js +216 -0
- package/src/@types/index.d.ts +0 -137
- package/src/core/method.d.ts +0 -73
- package/src/core/method.js +0 -140
- package/src/core/table.d.ts +0 -56
- package/src/core/table.js +0 -659
- package/src/index.d.ts +0 -16
- package/src/index.test.d.ts +0 -13
- package/src/index.test.js +0 -1992
- package/src/utils/relations.js +0 -141
|
@@ -0,0 +1,892 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* @file table.ts
|
|
5
|
+
* @description Tabla autocontenida con arquitectura minimalista y Symbol storage
|
|
6
|
+
* @autor Miguel Alejandro
|
|
7
|
+
* @fecha 2025-01-28
|
|
8
|
+
*/
|
|
9
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
10
|
+
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
|
|
11
|
+
const relations_1 = require("../utils/relations");
|
|
12
|
+
const client_1 = require("./client");
|
|
13
|
+
const decorator_1 = require("./decorator");
|
|
14
|
+
class Table {
|
|
15
|
+
constructor(props = {}) {
|
|
16
|
+
// Cache para instancias de relaciones (único Map necesario)
|
|
17
|
+
this._relationCache = new Map();
|
|
18
|
+
(0, client_1.requireClient)();
|
|
19
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
20
|
+
// Flag de persistencia con closure (no enumerable)
|
|
21
|
+
let __isPersisted = false;
|
|
22
|
+
Object.defineProperty(this, "__isPersisted", {
|
|
23
|
+
enumerable: false,
|
|
24
|
+
configurable: false,
|
|
25
|
+
get: () => __isPersisted,
|
|
26
|
+
set: (v) => {
|
|
27
|
+
__isPersisted = v;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
for (const column_name in schema.columns) {
|
|
31
|
+
const column = schema.columns[column_name];
|
|
32
|
+
if (column.store?.relation) {
|
|
33
|
+
// Relación con closure
|
|
34
|
+
let relation_value = props[column_name] ?? undefined;
|
|
35
|
+
Object.defineProperty(this, column_name, {
|
|
36
|
+
enumerable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
set: (v) => {
|
|
39
|
+
relation_value = v;
|
|
40
|
+
},
|
|
41
|
+
get: () => {
|
|
42
|
+
if (relation_value === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
// Cachear instancias para evitar recreación
|
|
45
|
+
const cache_key = `${column_name}:${JSON.stringify(relation_value)}`;
|
|
46
|
+
if (this._relationCache.has(cache_key)) {
|
|
47
|
+
return this._relationCache.get(cache_key);
|
|
48
|
+
}
|
|
49
|
+
const RelatedModel = column.store.relation.model();
|
|
50
|
+
const type = column.store.relation.type;
|
|
51
|
+
let result;
|
|
52
|
+
if (type === "HasMany" || type === "ManyToMany") {
|
|
53
|
+
result = []
|
|
54
|
+
.concat(relation_value ?? [])
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
.map((item) => item instanceof RelatedModel ? item : new RelatedModel(item));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (relation_value === null)
|
|
60
|
+
return null;
|
|
61
|
+
result =
|
|
62
|
+
relation_value instanceof RelatedModel
|
|
63
|
+
? relation_value
|
|
64
|
+
: new RelatedModel(relation_value);
|
|
65
|
+
}
|
|
66
|
+
// Guardar en caché
|
|
67
|
+
this._relationCache.set(cache_key, result);
|
|
68
|
+
return result;
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
// Columna normal con closure
|
|
74
|
+
let value = props[column_name];
|
|
75
|
+
Object.defineProperty(this, column_name, {
|
|
76
|
+
enumerable: true,
|
|
77
|
+
configurable: true,
|
|
78
|
+
get: () => {
|
|
79
|
+
const computed = column.get.reduce((v, fn) => fn(v), value);
|
|
80
|
+
// Cache default values so they don't regenerate on each access
|
|
81
|
+
if (value == null && computed != null) {
|
|
82
|
+
value = computed;
|
|
83
|
+
}
|
|
84
|
+
return computed;
|
|
85
|
+
},
|
|
86
|
+
set: (next) => {
|
|
87
|
+
// Pipeline de setters recibe (current, accumulated)
|
|
88
|
+
value = column.set.reduce((accumulated, fn) => fn(value, accumulated), next);
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// Asignar valor inicial a través del setter para que se procese
|
|
92
|
+
if (column_name in props) {
|
|
93
|
+
this[column_name] = props[column_name];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
toJSON() {
|
|
99
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
100
|
+
const result = {};
|
|
101
|
+
for (const column_name in schema.columns) {
|
|
102
|
+
const column = schema.columns[column_name];
|
|
103
|
+
const value = this[column_name];
|
|
104
|
+
if (value === null || value === undefined)
|
|
105
|
+
continue;
|
|
106
|
+
if (column.store?.relation) {
|
|
107
|
+
result[column_name] = Array.isArray(value)
|
|
108
|
+
? value.map((item) => (item?.toJSON ? item.toJSON() : item))
|
|
109
|
+
: value?.toJSON
|
|
110
|
+
? value.toJSON()
|
|
111
|
+
: value;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
result[column_name] = value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* @description Mapea nombres de propiedades TypeScript a nombres de columnas de DB
|
|
121
|
+
* @param data Objeto con propiedades TypeScript
|
|
122
|
+
* @returns Objeto con nombres de columnas de DB
|
|
123
|
+
*/
|
|
124
|
+
_mapPropertiesToDB(data) {
|
|
125
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
126
|
+
const mapped = {};
|
|
127
|
+
for (const prop_name in data) {
|
|
128
|
+
const column = schema.columns[prop_name];
|
|
129
|
+
if (!column) {
|
|
130
|
+
mapped[prop_name] = data[prop_name];
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const db_name = column.name || prop_name;
|
|
134
|
+
mapped[db_name] = data[prop_name];
|
|
135
|
+
}
|
|
136
|
+
return mapped;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* @description Mapea nombres de columnas de DB a nombres de propiedades TypeScript
|
|
140
|
+
* @param data Objeto con nombres de columnas de DB
|
|
141
|
+
* @returns Objeto con propiedades TypeScript
|
|
142
|
+
*/
|
|
143
|
+
static _mapDBToProperties(data) {
|
|
144
|
+
const schema = this[decorator_1.SCHEMA];
|
|
145
|
+
const mapped = {};
|
|
146
|
+
// Crear índice inverso: db_name → prop_name
|
|
147
|
+
const db_to_prop = {};
|
|
148
|
+
for (const prop_name in schema.columns) {
|
|
149
|
+
const db_name = schema.columns[prop_name].name;
|
|
150
|
+
db_to_prop[db_name] = prop_name;
|
|
151
|
+
}
|
|
152
|
+
// Mapear datos de DB a propiedades
|
|
153
|
+
for (const db_name in data) {
|
|
154
|
+
const prop_name = db_to_prop[db_name] || db_name;
|
|
155
|
+
mapped[prop_name] = data[db_name];
|
|
156
|
+
}
|
|
157
|
+
return mapped;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @description Convierte la instancia a un payload listo para DynamoDB
|
|
161
|
+
* @returns Objeto con nombres de columnas de DB y valores apropiados
|
|
162
|
+
*/
|
|
163
|
+
_toDBPayload() {
|
|
164
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
165
|
+
const payload = {};
|
|
166
|
+
for (const prop_name in schema.columns) {
|
|
167
|
+
const column = schema.columns[prop_name];
|
|
168
|
+
// Skip relations
|
|
169
|
+
if (column.store?.relation) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const value = this[prop_name];
|
|
173
|
+
const db_name = column.name || prop_name;
|
|
174
|
+
// Skip undefined values (DynamoDB doesn't support them)
|
|
175
|
+
if (value !== undefined) {
|
|
176
|
+
payload[db_name] = value;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return payload;
|
|
180
|
+
}
|
|
181
|
+
toString() {
|
|
182
|
+
return JSON.stringify(this);
|
|
183
|
+
}
|
|
184
|
+
async save() {
|
|
185
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
186
|
+
const primary_key_name = schema.primary_key || "id";
|
|
187
|
+
const primary_key_value = this[primary_key_name];
|
|
188
|
+
const data = {};
|
|
189
|
+
for (const column_name in schema.columns) {
|
|
190
|
+
const column = schema.columns[column_name];
|
|
191
|
+
if (!column.store?.relation) {
|
|
192
|
+
data[column_name] = this[column_name];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Intentar UPDATE primero
|
|
196
|
+
const updated = await this.update(data);
|
|
197
|
+
// Si no actualizó nada (registro no existe), hacer INSERT
|
|
198
|
+
if (!updated && primary_key_value) {
|
|
199
|
+
// Verificar si el registro existe
|
|
200
|
+
const existing = await this.constructor.where({
|
|
201
|
+
[primary_key_name]: primary_key_value,
|
|
202
|
+
});
|
|
203
|
+
if (existing.length === 0) {
|
|
204
|
+
// No existe, crear nuevo registro
|
|
205
|
+
const created = await this.constructor.create(data);
|
|
206
|
+
// Sincronizar propiedades desde el registro creado
|
|
207
|
+
for (const key in created) {
|
|
208
|
+
if (Object.prototype.hasOwnProperty.call(created, key)) {
|
|
209
|
+
this[key] = created[key];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
this.__isPersisted = true;
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (updated) {
|
|
217
|
+
this.__isPersisted = true;
|
|
218
|
+
}
|
|
219
|
+
return updated;
|
|
220
|
+
}
|
|
221
|
+
async update(data) {
|
|
222
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
223
|
+
// Filtrar relaciones (ignorarlas) en vez de lanzar error
|
|
224
|
+
const filtered_data = {};
|
|
225
|
+
for (const key in data) {
|
|
226
|
+
const column = schema.columns[key];
|
|
227
|
+
// Solo incluir campos que NO son relaciones
|
|
228
|
+
if (!column?.store?.relation) {
|
|
229
|
+
filtered_data[key] = data[key];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
const affected = await this.constructor.update(filtered_data, {
|
|
233
|
+
[schema.primary_key]: this[schema.primary_key],
|
|
234
|
+
});
|
|
235
|
+
if (affected > 0) {
|
|
236
|
+
// Actualizar la instancia con los nuevos valores (incluyendo updated_at)
|
|
237
|
+
for (const key in filtered_data) {
|
|
238
|
+
this[key] = filtered_data[key];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return affected > 0;
|
|
242
|
+
}
|
|
243
|
+
async destroy() {
|
|
244
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
245
|
+
const id = this[schema.primary_key];
|
|
246
|
+
if (!id)
|
|
247
|
+
throw new Error("Cannot destroy record without ID");
|
|
248
|
+
for (const column_name in schema.columns) {
|
|
249
|
+
if (schema.columns[column_name].store?.softDelete) {
|
|
250
|
+
this[column_name] = new Date().toISOString();
|
|
251
|
+
await this.save();
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return this.forceDestroy();
|
|
256
|
+
}
|
|
257
|
+
async forceDestroy() {
|
|
258
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
259
|
+
const id = this[schema.primary_key];
|
|
260
|
+
if (!id)
|
|
261
|
+
throw new Error("Cannot destroy record without ID");
|
|
262
|
+
await (0, client_1.requireClient)().send(new client_dynamodb_1.DeleteItemCommand({
|
|
263
|
+
TableName: schema.name,
|
|
264
|
+
Key: (0, util_dynamodb_1.marshall)({ [schema.primary_key]: id }),
|
|
265
|
+
}));
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
async attach(RelatedModel, related_id, pivot_data) {
|
|
269
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
270
|
+
const primary_key = schema.primary_key || "id";
|
|
271
|
+
// VALIDACIÓN PRIORITARIA: Verificar que la instancia esté persistida
|
|
272
|
+
const local_id = this[primary_key];
|
|
273
|
+
const is_persisted = this.__isPersisted;
|
|
274
|
+
if (!local_id || !is_persisted) {
|
|
275
|
+
throw new Error("No se puede attach sin ID: la instancia debe persistirse primero con save() o create()");
|
|
276
|
+
}
|
|
277
|
+
const related_table_name = RelatedModel[decorator_1.SCHEMA]?.name;
|
|
278
|
+
if (!related_table_name) {
|
|
279
|
+
throw new Error("Related model no tiene SCHEMA definido");
|
|
280
|
+
}
|
|
281
|
+
let relation = null;
|
|
282
|
+
for (const column_name in schema.columns) {
|
|
283
|
+
const rel = schema.columns[column_name].store?.relation;
|
|
284
|
+
if (rel?.type === "ManyToMany" &&
|
|
285
|
+
rel.model()[decorator_1.SCHEMA]?.name === related_table_name) {
|
|
286
|
+
relation = rel;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!relation) {
|
|
291
|
+
throw new Error(`No se encontró relación ManyToMany entre ${schema.name} y ${related_table_name}`);
|
|
292
|
+
}
|
|
293
|
+
const foreign_key_value = this[relation.localKey];
|
|
294
|
+
const result = await (0, client_1.requireClient)().send(new client_dynamodb_1.ScanCommand({
|
|
295
|
+
TableName: relation.pivotTable,
|
|
296
|
+
FilterExpression: "#fk = :local_id AND #rk = :related_id",
|
|
297
|
+
ExpressionAttributeNames: {
|
|
298
|
+
"#fk": relation.foreignKey,
|
|
299
|
+
"#rk": relation.relatedKey,
|
|
300
|
+
},
|
|
301
|
+
ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
|
|
302
|
+
":local_id": foreign_key_value,
|
|
303
|
+
":related_id": related_id,
|
|
304
|
+
}),
|
|
305
|
+
}));
|
|
306
|
+
if (result.Items && result.Items.length > 0)
|
|
307
|
+
return;
|
|
308
|
+
await (0, client_1.requireClient)().send(new client_dynamodb_1.PutItemCommand({
|
|
309
|
+
TableName: relation.pivotTable,
|
|
310
|
+
Item: (0, util_dynamodb_1.marshall)({
|
|
311
|
+
id: `${foreign_key_value}_${related_id}`,
|
|
312
|
+
[relation.foreignKey]: foreign_key_value,
|
|
313
|
+
[relation.relatedKey]: related_id,
|
|
314
|
+
created_at: new Date().toISOString(),
|
|
315
|
+
...pivot_data,
|
|
316
|
+
}, { removeUndefinedValues: true }),
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
async detach(RelatedModel, related_id) {
|
|
320
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
321
|
+
const related_table_name = RelatedModel[decorator_1.SCHEMA]?.name;
|
|
322
|
+
if (!related_table_name)
|
|
323
|
+
return;
|
|
324
|
+
let relation = null;
|
|
325
|
+
for (const column_name in schema.columns) {
|
|
326
|
+
const rel = schema.columns[column_name].store?.relation;
|
|
327
|
+
if (rel?.type === "ManyToMany" &&
|
|
328
|
+
rel.model()[decorator_1.SCHEMA]?.name === related_table_name) {
|
|
329
|
+
relation = rel;
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!relation)
|
|
334
|
+
return;
|
|
335
|
+
const local_id = this[relation.localKey];
|
|
336
|
+
if (!local_id)
|
|
337
|
+
return;
|
|
338
|
+
const result = await (0, client_1.requireClient)().send(new client_dynamodb_1.ScanCommand({
|
|
339
|
+
TableName: relation.pivotTable,
|
|
340
|
+
FilterExpression: "#fk = :local_id AND #rk = :related_id",
|
|
341
|
+
ExpressionAttributeNames: {
|
|
342
|
+
"#fk": relation.foreignKey,
|
|
343
|
+
"#rk": relation.relatedKey,
|
|
344
|
+
},
|
|
345
|
+
ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({
|
|
346
|
+
":local_id": local_id,
|
|
347
|
+
":related_id": related_id,
|
|
348
|
+
}),
|
|
349
|
+
}));
|
|
350
|
+
if (!result.Items || result.Items.length === 0)
|
|
351
|
+
return;
|
|
352
|
+
await (0, client_1.requireClient)().send(new client_dynamodb_1.DeleteItemCommand({
|
|
353
|
+
TableName: relation.pivotTable,
|
|
354
|
+
Key: (0, util_dynamodb_1.marshall)({ id: (0, util_dynamodb_1.unmarshall)(result.Items[0]).id }),
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Sincronizar relación ManyToMany reemplazando todas las relaciones existentes
|
|
359
|
+
* @param RelatedModel Modelo relacionado
|
|
360
|
+
* @param related_ids Array de IDs a sincronizar
|
|
361
|
+
*/
|
|
362
|
+
async sync(RelatedModel, related_ids) {
|
|
363
|
+
const schema = this.constructor[decorator_1.SCHEMA];
|
|
364
|
+
const related_table_name = RelatedModel[decorator_1.SCHEMA]?.name;
|
|
365
|
+
if (!related_table_name) {
|
|
366
|
+
throw new Error(`No se encontró schema para el modelo relacionado`);
|
|
367
|
+
}
|
|
368
|
+
// Buscar la relación ManyToMany
|
|
369
|
+
let relation = null;
|
|
370
|
+
for (const column_name in schema.columns) {
|
|
371
|
+
const rel = schema.columns[column_name].store?.relation;
|
|
372
|
+
if (rel?.type === "ManyToMany" &&
|
|
373
|
+
rel.model()[decorator_1.SCHEMA]?.name === related_table_name) {
|
|
374
|
+
relation = rel;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!relation) {
|
|
379
|
+
throw new Error(`No se encontró relación ManyToMany entre ${schema.name} y ${related_table_name}`);
|
|
380
|
+
}
|
|
381
|
+
const local_id = this[relation.localKey];
|
|
382
|
+
if (!local_id) {
|
|
383
|
+
throw new Error(`El valor de ${relation.localKey} no está definido`);
|
|
384
|
+
}
|
|
385
|
+
// 1. Obtener todas las relaciones existentes
|
|
386
|
+
const scan_result = await (0, client_1.requireClient)().send(new client_dynamodb_1.ScanCommand({
|
|
387
|
+
TableName: relation.pivotTable,
|
|
388
|
+
FilterExpression: "#fk = :local_id",
|
|
389
|
+
ExpressionAttributeNames: { "#fk": relation.foreignKey },
|
|
390
|
+
ExpressionAttributeValues: (0, util_dynamodb_1.marshall)({ ":local_id": local_id }),
|
|
391
|
+
}));
|
|
392
|
+
const existing_ids = new Set((scan_result.Items || []).map((item) => (0, util_dynamodb_1.unmarshall)(item)[relation.relatedKey]));
|
|
393
|
+
const target_ids = new Set(related_ids);
|
|
394
|
+
// 2. Detach relaciones que no están en la lista objetivo
|
|
395
|
+
for (const existing_id of existing_ids) {
|
|
396
|
+
if (!target_ids.has(existing_id)) {
|
|
397
|
+
await this.detach(RelatedModel, existing_id);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// 3. Attach nuevas relaciones que no existen
|
|
401
|
+
for (const target_id of target_ids) {
|
|
402
|
+
if (!existing_ids.has(target_id)) {
|
|
403
|
+
await this.attach(RelatedModel, target_id);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
static async create(data, tx) {
|
|
408
|
+
const instance = new this(data);
|
|
409
|
+
const schema = this[decorator_1.SCHEMA];
|
|
410
|
+
// Validar campos @NotNull antes de guardar
|
|
411
|
+
for (const column_name in schema.columns) {
|
|
412
|
+
const column = schema.columns[column_name];
|
|
413
|
+
if (column.store?.nullable === false) {
|
|
414
|
+
const value = instance[column_name];
|
|
415
|
+
const is_empty = value === null ||
|
|
416
|
+
value === undefined ||
|
|
417
|
+
(typeof value === "string" && value.trim() === "");
|
|
418
|
+
if (is_empty) {
|
|
419
|
+
const message = column.store.notNullMessage ||
|
|
420
|
+
`El campo ${column_name} no puede estar vacío`;
|
|
421
|
+
throw new Error(message);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const payload = instance._toDBPayload();
|
|
426
|
+
if (tx) {
|
|
427
|
+
tx.addPut(schema.name, payload);
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
const client = (0, client_1.requireClient)();
|
|
431
|
+
await client.send(new client_dynamodb_1.PutItemCommand({
|
|
432
|
+
TableName: schema.name,
|
|
433
|
+
Item: (0, util_dynamodb_1.marshall)(payload, { removeUndefinedValues: true }),
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
// Marcar instancia como persistida
|
|
437
|
+
instance.__isPersisted = true;
|
|
438
|
+
return instance;
|
|
439
|
+
}
|
|
440
|
+
static async update(updates, filters, tx) {
|
|
441
|
+
const schema = this[decorator_1.SCHEMA];
|
|
442
|
+
// Filtrar solo campos que no son relaciones
|
|
443
|
+
const parsed_updates = {};
|
|
444
|
+
for (const key in updates) {
|
|
445
|
+
const column = schema.columns[key];
|
|
446
|
+
if (!column?.store?.relation) {
|
|
447
|
+
parsed_updates[key] = updates[key];
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const records = await this.where(filters);
|
|
451
|
+
if (records.length === 0)
|
|
452
|
+
return 0;
|
|
453
|
+
for (const record of records) {
|
|
454
|
+
// Asignar valores - los setters de la instancia procesarán automáticamente
|
|
455
|
+
for (const [key, value] of Object.entries(parsed_updates)) {
|
|
456
|
+
record[key] = value;
|
|
457
|
+
}
|
|
458
|
+
if (tx) {
|
|
459
|
+
tx.addPut(schema.name, record._toDBPayload());
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
await (0, client_1.requireClient)().send(new client_dynamodb_1.PutItemCommand({
|
|
463
|
+
TableName: schema.name,
|
|
464
|
+
Item: (0, util_dynamodb_1.marshall)(record._toDBPayload(), {
|
|
465
|
+
removeUndefinedValues: true,
|
|
466
|
+
}),
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return records.length;
|
|
471
|
+
}
|
|
472
|
+
static async delete(filters, tx) {
|
|
473
|
+
const records = await this.where(filters);
|
|
474
|
+
if (records.length === 0)
|
|
475
|
+
return 0;
|
|
476
|
+
const schema = this[decorator_1.SCHEMA];
|
|
477
|
+
for (const record of records) {
|
|
478
|
+
const id = record[schema.primary_key];
|
|
479
|
+
if (id) {
|
|
480
|
+
if (tx) {
|
|
481
|
+
tx.addDelete(schema.name, { [schema.primary_key]: id });
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
await (0, client_1.requireClient)().send(new client_dynamodb_1.DeleteItemCommand({
|
|
485
|
+
TableName: schema.name,
|
|
486
|
+
Key: (0, util_dynamodb_1.marshall)({ [schema.primary_key]: id }),
|
|
487
|
+
}));
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return records.length;
|
|
492
|
+
}
|
|
493
|
+
static async where(field_or_filters, operator_or_value, value, options) {
|
|
494
|
+
const schema = this[decorator_1.SCHEMA];
|
|
495
|
+
// ========================================================================
|
|
496
|
+
// FASE 1: NORMALIZACIÓN DE ARGUMENTOS
|
|
497
|
+
// Transforma las 3 sobrecargas en un formato unificado (filters + options)
|
|
498
|
+
// ========================================================================
|
|
499
|
+
let filters;
|
|
500
|
+
let queryOptions;
|
|
501
|
+
let operator = "=";
|
|
502
|
+
if (typeof operator_or_value === "string" &&
|
|
503
|
+
[
|
|
504
|
+
"=",
|
|
505
|
+
"<>",
|
|
506
|
+
"!=",
|
|
507
|
+
"<",
|
|
508
|
+
"<=",
|
|
509
|
+
">",
|
|
510
|
+
">=",
|
|
511
|
+
"in",
|
|
512
|
+
"$eq",
|
|
513
|
+
"$ne",
|
|
514
|
+
"$lt",
|
|
515
|
+
"$lte",
|
|
516
|
+
"$gt",
|
|
517
|
+
"$gte",
|
|
518
|
+
"$in",
|
|
519
|
+
"$include",
|
|
520
|
+
"include",
|
|
521
|
+
].includes(operator_or_value)) {
|
|
522
|
+
operator = operator_or_value;
|
|
523
|
+
// Normalizar operadores para DynamoDB
|
|
524
|
+
if (operator === "!=" || operator === "$ne") {
|
|
525
|
+
operator = "<>";
|
|
526
|
+
}
|
|
527
|
+
// Wrap value in operator object for proper processing
|
|
528
|
+
if (operator !== "=") {
|
|
529
|
+
filters = { [field_or_filters]: { [operator]: value } };
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
filters = { [field_or_filters]: value };
|
|
533
|
+
}
|
|
534
|
+
queryOptions = options || {};
|
|
535
|
+
}
|
|
536
|
+
else if (value !== undefined) {
|
|
537
|
+
filters = {
|
|
538
|
+
[field_or_filters]: Array.isArray(operator_or_value)
|
|
539
|
+
? { in: operator_or_value }
|
|
540
|
+
: operator_or_value,
|
|
541
|
+
};
|
|
542
|
+
queryOptions = value || {};
|
|
543
|
+
}
|
|
544
|
+
else if (operator_or_value !== undefined &&
|
|
545
|
+
typeof operator_or_value === "object") {
|
|
546
|
+
filters = field_or_filters;
|
|
547
|
+
queryOptions = operator_or_value;
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
filters = field_or_filters;
|
|
551
|
+
queryOptions = {};
|
|
552
|
+
}
|
|
553
|
+
// Validaciones de paginación
|
|
554
|
+
if (queryOptions.limit !== undefined) {
|
|
555
|
+
if (queryOptions.limit < 0)
|
|
556
|
+
throw new Error("limit debe ser mayor o igual a 0");
|
|
557
|
+
if (queryOptions.limit === 0)
|
|
558
|
+
return []; // Retorno temprano optimizado
|
|
559
|
+
}
|
|
560
|
+
if (queryOptions.offset !== undefined && queryOptions.offset < 0)
|
|
561
|
+
throw new Error("offset debe ser mayor o igual a 0");
|
|
562
|
+
// Validar order: puede ser string "ASC"|"DESC" o objeto { field: "ASC"|"DESC" }
|
|
563
|
+
if (queryOptions.order) {
|
|
564
|
+
if (typeof queryOptions.order === "string") {
|
|
565
|
+
if (!["ASC", "DESC"].includes(queryOptions.order)) {
|
|
566
|
+
throw new Error('order debe ser "ASC" o "DESC"');
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
else if (typeof queryOptions.order === "object") {
|
|
570
|
+
const keys = Object.keys(queryOptions.order);
|
|
571
|
+
if (keys.length !== 1) {
|
|
572
|
+
throw new Error("order object debe tener exactamente un campo");
|
|
573
|
+
}
|
|
574
|
+
const direction = queryOptions.order[keys[0]];
|
|
575
|
+
if (!["ASC", "DESC"].includes(direction)) {
|
|
576
|
+
throw new Error('order direction debe ser "ASC" o "DESC"');
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (!queryOptions._includeTrashed) {
|
|
581
|
+
for (const column_name in schema.columns) {
|
|
582
|
+
if (schema.columns[column_name].store?.softDelete &&
|
|
583
|
+
!(column_name in filters)) {
|
|
584
|
+
// Filtrar registros soft-deleted: donde el campo NO existe (registros activos)
|
|
585
|
+
filters[column_name] = { $notExists: true };
|
|
586
|
+
break;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// ========================================================================
|
|
591
|
+
// FASE 2: CONSTRUCCIÓN DE COMANDO DYNAMODB
|
|
592
|
+
// Construye FilterExpression, ProjectionExpression y parámetros del comando
|
|
593
|
+
// ========================================================================
|
|
594
|
+
const expressions = [];
|
|
595
|
+
const names = {};
|
|
596
|
+
const values = {};
|
|
597
|
+
const aliases = {
|
|
598
|
+
// Literal operators (identity mapping)
|
|
599
|
+
"=": "=",
|
|
600
|
+
"<>": "<>",
|
|
601
|
+
"!=": "<>",
|
|
602
|
+
"<": "<",
|
|
603
|
+
"<=": "<=",
|
|
604
|
+
">": ">",
|
|
605
|
+
">=": ">=",
|
|
606
|
+
in: "in",
|
|
607
|
+
include: "include",
|
|
608
|
+
contains: "include",
|
|
609
|
+
// Sugar syntax
|
|
610
|
+
$eq: "=",
|
|
611
|
+
$ne: "<>",
|
|
612
|
+
$lt: "<",
|
|
613
|
+
$lte: "<=",
|
|
614
|
+
$gt: ">",
|
|
615
|
+
$gte: ">=",
|
|
616
|
+
$in: "in",
|
|
617
|
+
$include: "include",
|
|
618
|
+
$contains: "include",
|
|
619
|
+
"attribute-not-exists": "attribute_not_exists",
|
|
620
|
+
"attribute-exists": "attribute_exists",
|
|
621
|
+
$exists: "attribute_exists",
|
|
622
|
+
$notExists: "attribute_not_exists",
|
|
623
|
+
};
|
|
624
|
+
let idx = 0;
|
|
625
|
+
for (const [field, filter_val] of Object.entries(filters)) {
|
|
626
|
+
if (filter_val === undefined)
|
|
627
|
+
continue;
|
|
628
|
+
// Mapear nombre de propiedad TS → nombre de columna DB
|
|
629
|
+
const column = schema.columns[field];
|
|
630
|
+
const db_field_name = column?.name || field;
|
|
631
|
+
const is_obj = filter_val !== null &&
|
|
632
|
+
typeof filter_val === "object" &&
|
|
633
|
+
!Array.isArray(filter_val) &&
|
|
634
|
+
Object.keys(filter_val).some((k) => k in aliases);
|
|
635
|
+
if (is_obj) {
|
|
636
|
+
for (const [op_key, op_val] of Object.entries(filter_val)) {
|
|
637
|
+
if (op_val === undefined)
|
|
638
|
+
continue;
|
|
639
|
+
const op = aliases[op_key] || op_key;
|
|
640
|
+
if (![
|
|
641
|
+
"=",
|
|
642
|
+
"<>",
|
|
643
|
+
"!=",
|
|
644
|
+
"<",
|
|
645
|
+
"<=",
|
|
646
|
+
">",
|
|
647
|
+
">=",
|
|
648
|
+
"in",
|
|
649
|
+
"include",
|
|
650
|
+
"attribute_not_exists",
|
|
651
|
+
"attribute_exists",
|
|
652
|
+
].includes(op))
|
|
653
|
+
continue;
|
|
654
|
+
const name_key = `#attr${idx}`;
|
|
655
|
+
const val_key = `:val${idx}`;
|
|
656
|
+
names[name_key] = db_field_name;
|
|
657
|
+
if (op === "in" && Array.isArray(op_val)) {
|
|
658
|
+
if (op_val.length === 0) {
|
|
659
|
+
throw new Error(`Operador 'in' requiere un array no vacío. Para buscar sin filtro, omite el operador.`);
|
|
660
|
+
}
|
|
661
|
+
// DynamoDB no soporta IN nativo, usar OR chain
|
|
662
|
+
const or_conditions = op_val.map((v, i) => {
|
|
663
|
+
const k = `${val_key}_${i}`;
|
|
664
|
+
values[k] = v;
|
|
665
|
+
return `${name_key} = ${k}`;
|
|
666
|
+
});
|
|
667
|
+
expressions.push(`(${or_conditions.join(" OR ")})`);
|
|
668
|
+
}
|
|
669
|
+
else if (op === "include") {
|
|
670
|
+
values[val_key] = op_val;
|
|
671
|
+
// $include: if field is array, checks includes; if string, checks contains (LIKE)
|
|
672
|
+
expressions.push(`contains(${name_key}, ${val_key})`);
|
|
673
|
+
}
|
|
674
|
+
else if (op === "attribute_not_exists") {
|
|
675
|
+
expressions.push(`attribute_not_exists(${name_key})`);
|
|
676
|
+
}
|
|
677
|
+
else if (op === "attribute_exists") {
|
|
678
|
+
expressions.push(`attribute_exists(${name_key})`);
|
|
679
|
+
}
|
|
680
|
+
else if (op === "!=" || op === "<>") {
|
|
681
|
+
if (op_val === null) {
|
|
682
|
+
expressions.push(`attribute_exists(${name_key})`);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
values[val_key] = op_val;
|
|
686
|
+
expressions.push(`${name_key} <> ${val_key}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
values[val_key] = op_val;
|
|
691
|
+
expressions.push(`${name_key} ${op} ${val_key}`);
|
|
692
|
+
}
|
|
693
|
+
idx++;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
else if (filter_val === null) {
|
|
697
|
+
const name_key = `#attr${idx}`;
|
|
698
|
+
names[name_key] = db_field_name;
|
|
699
|
+
expressions.push(`attribute_not_exists(${name_key})`);
|
|
700
|
+
idx++;
|
|
701
|
+
}
|
|
702
|
+
else if (filter_val !== null) {
|
|
703
|
+
const name_key = `#attr${idx}`;
|
|
704
|
+
const val_key = `:val${idx}`;
|
|
705
|
+
names[name_key] = db_field_name;
|
|
706
|
+
values[val_key] = filter_val;
|
|
707
|
+
expressions.push(`${name_key} ${operator} ${val_key}`);
|
|
708
|
+
idx++;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const scanParams = {
|
|
712
|
+
TableName: schema.name,
|
|
713
|
+
ExpressionAttributeNames: names,
|
|
714
|
+
};
|
|
715
|
+
if (expressions.length > 0) {
|
|
716
|
+
scanParams.FilterExpression = expressions.join(" AND ");
|
|
717
|
+
// Only add ExpressionAttributeValues if there are actual values
|
|
718
|
+
if (Object.keys(values).length > 0) {
|
|
719
|
+
scanParams.ExpressionAttributeValues = (0, util_dynamodb_1.marshall)(values, {
|
|
720
|
+
removeUndefinedValues: true,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (queryOptions.attributes && queryOptions.attributes.length > 0) {
|
|
725
|
+
const projectionExpressions = queryOptions.attributes.map((attr, index) => {
|
|
726
|
+
const aliasKey = `#proj${index}`;
|
|
727
|
+
// Mapear nombre de propiedad TS → nombre de columna DB
|
|
728
|
+
const column = schema.columns[String(attr)];
|
|
729
|
+
const db_attr_name = column?.name || String(attr);
|
|
730
|
+
scanParams.ExpressionAttributeNames[aliasKey] = db_attr_name;
|
|
731
|
+
return aliasKey;
|
|
732
|
+
});
|
|
733
|
+
scanParams.ProjectionExpression = projectionExpressions.join(", ");
|
|
734
|
+
}
|
|
735
|
+
if (Object.keys(scanParams.ExpressionAttributeNames).length === 0) {
|
|
736
|
+
delete scanParams.ExpressionAttributeNames;
|
|
737
|
+
}
|
|
738
|
+
// ========================================================================
|
|
739
|
+
// FASE 3: EJECUCIÓN Y MAPEO
|
|
740
|
+
// Ejecuta comando(s), ordena resultados completos y aplica paginación
|
|
741
|
+
// ========================================================================
|
|
742
|
+
let allItems = [];
|
|
743
|
+
let lastEvaluatedKey = undefined;
|
|
744
|
+
// 1. Escanear TODOS los items que coinciden con el filtro
|
|
745
|
+
do {
|
|
746
|
+
if (lastEvaluatedKey)
|
|
747
|
+
scanParams.ExclusiveStartKey = lastEvaluatedKey;
|
|
748
|
+
const result = await (0, client_1.requireClient)().send(new client_dynamodb_1.ScanCommand(scanParams));
|
|
749
|
+
if (result.Items) {
|
|
750
|
+
const items = result.Items.map((item) => {
|
|
751
|
+
const raw = (0, util_dynamodb_1.unmarshall)(item);
|
|
752
|
+
for (const k of Object.keys(raw)) {
|
|
753
|
+
if (raw[k] === null || raw[k] === undefined)
|
|
754
|
+
delete raw[k];
|
|
755
|
+
}
|
|
756
|
+
return raw;
|
|
757
|
+
});
|
|
758
|
+
allItems.push(...items);
|
|
759
|
+
}
|
|
760
|
+
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
761
|
+
} while (lastEvaluatedKey);
|
|
762
|
+
// 2. Ordenar ANTES de aplicar paginación (para resultados determinísticos)
|
|
763
|
+
if (queryOptions.order) {
|
|
764
|
+
let sortField = schema.primary_key;
|
|
765
|
+
let sortDirection = "ASC";
|
|
766
|
+
// Determinar campo y dirección de ordenamiento
|
|
767
|
+
if (typeof queryOptions.order === "string") {
|
|
768
|
+
// Sintaxis string: usar created_at o primary_key
|
|
769
|
+
sortDirection = queryOptions.order;
|
|
770
|
+
for (const column_name in schema.columns) {
|
|
771
|
+
if (schema.columns[column_name].store?.createdAt) {
|
|
772
|
+
sortField = column_name;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
// Sintaxis objeto: { fieldName: "ASC"|"DESC" }
|
|
779
|
+
const fieldName = Object.keys(queryOptions.order)[0];
|
|
780
|
+
sortField = fieldName;
|
|
781
|
+
sortDirection = queryOptions.order[fieldName];
|
|
782
|
+
}
|
|
783
|
+
allItems.sort((a, b) => {
|
|
784
|
+
const aVal = a[sortField];
|
|
785
|
+
const bVal = b[sortField];
|
|
786
|
+
if (aVal < bVal)
|
|
787
|
+
return sortDirection === "ASC" ? -1 : 1;
|
|
788
|
+
if (aVal > bVal)
|
|
789
|
+
return sortDirection === "ASC" ? 1 : -1;
|
|
790
|
+
return 0;
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
// 3. Aplicar paginación DESPUÉS de ordenar (soporta skip como alias de offset)
|
|
794
|
+
const targetSkip = queryOptions.skip ?? queryOptions.offset ?? 0;
|
|
795
|
+
const targetLimit = queryOptions.limit ?? allItems.length;
|
|
796
|
+
allItems = allItems.slice(targetSkip, targetSkip + targetLimit);
|
|
797
|
+
const instances = allItems.map((item) => {
|
|
798
|
+
// Mapear nombres de DB → propiedades TS
|
|
799
|
+
const mapped_item = this._mapDBToProperties(item);
|
|
800
|
+
if (queryOptions.attributes) {
|
|
801
|
+
const instance = Object.create(this.prototype);
|
|
802
|
+
for (const attr of queryOptions.attributes) {
|
|
803
|
+
const column = schema.columns[attr];
|
|
804
|
+
if (column) {
|
|
805
|
+
const value = mapped_item[attr] ?? null;
|
|
806
|
+
Object.defineProperty(instance, attr, {
|
|
807
|
+
enumerable: true,
|
|
808
|
+
configurable: true,
|
|
809
|
+
get: () => column.get.reduce((v, fn) => fn(v), value),
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return instance;
|
|
814
|
+
}
|
|
815
|
+
const instance = new this(mapped_item);
|
|
816
|
+
instance.__isPersisted = true;
|
|
817
|
+
return instance;
|
|
818
|
+
});
|
|
819
|
+
if (queryOptions.include) {
|
|
820
|
+
await (0, relations_1.processIncludes)(instances, queryOptions.include, this);
|
|
821
|
+
}
|
|
822
|
+
return instances;
|
|
823
|
+
}
|
|
824
|
+
static async first(field_or_filters, operator_or_value, value_or_options) {
|
|
825
|
+
// Detect call pattern and merge options appropriately
|
|
826
|
+
let results;
|
|
827
|
+
if (arguments.length === 2 &&
|
|
828
|
+
typeof operator_or_value === "object" &&
|
|
829
|
+
!Array.isArray(operator_or_value) &&
|
|
830
|
+
operator_or_value !== null) {
|
|
831
|
+
// Called as first(query, options) - 2 args
|
|
832
|
+
const options = { ...operator_or_value, limit: 1 };
|
|
833
|
+
results = await this.where(field_or_filters, options);
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
// Called as first(field, operator, value, options) - 3-4 args
|
|
837
|
+
const options = typeof value_or_options === "object" && !Array.isArray(value_or_options)
|
|
838
|
+
? { ...value_or_options, limit: 1 }
|
|
839
|
+
: { limit: 1 };
|
|
840
|
+
results = await this.where(field_or_filters, operator_or_value, value_or_options, options);
|
|
841
|
+
}
|
|
842
|
+
return results[0];
|
|
843
|
+
}
|
|
844
|
+
static async last(field_or_filters, operator_or_value) {
|
|
845
|
+
if (field_or_filters === undefined) {
|
|
846
|
+
// Called as last() or last(options)
|
|
847
|
+
const options = typeof operator_or_value === "object" &&
|
|
848
|
+
!Array.isArray(operator_or_value)
|
|
849
|
+
? { ...operator_or_value, order: "DESC", limit: 1 }
|
|
850
|
+
: { order: "DESC", limit: 1 };
|
|
851
|
+
const results = await this.where({}, options);
|
|
852
|
+
return results[0];
|
|
853
|
+
}
|
|
854
|
+
// Called as last(query) or last(query, options) - 1-2 args
|
|
855
|
+
if (typeof operator_or_value === "object" &&
|
|
856
|
+
!Array.isArray(operator_or_value) &&
|
|
857
|
+
operator_or_value !== null) {
|
|
858
|
+
// 2 args: last(query, options)
|
|
859
|
+
const options = { ...operator_or_value, order: "DESC", limit: 1 };
|
|
860
|
+
const results = await this.where(field_or_filters, options);
|
|
861
|
+
return results[0];
|
|
862
|
+
}
|
|
863
|
+
else {
|
|
864
|
+
// 1 arg: last(query) - no options provided
|
|
865
|
+
const results = await this.where(field_or_filters, {
|
|
866
|
+
order: "DESC",
|
|
867
|
+
limit: 1,
|
|
868
|
+
});
|
|
869
|
+
return results[0];
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
static async withTrashed(filters, options) {
|
|
873
|
+
return await this.where(filters ?? {}, {
|
|
874
|
+
...options,
|
|
875
|
+
_includeTrashed: true,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
static async onlyTrashed(filters, options) {
|
|
879
|
+
const schema = this[decorator_1.SCHEMA];
|
|
880
|
+
for (const column_name in schema.columns) {
|
|
881
|
+
if (schema.columns[column_name].store?.softDelete) {
|
|
882
|
+
return await this.where({
|
|
883
|
+
...filters,
|
|
884
|
+
[column_name]: { "!=": null },
|
|
885
|
+
}, { ...options, _includeTrashed: true });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
throw new Error("onlyTrashed() requiere un campo @SoftDelete configurado");
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
exports.default = Table;
|
|
892
|
+
//# sourceMappingURL=table.js.map
|