@arcaelas/dynamite 1.0.10 → 1.0.13
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/LICENSE.txt +32 -0
- package/README.txt +206 -0
- package/package.json +5 -4
- package/src/@types/index.d.ts +96 -0
- package/src/@types/index.js +9 -0
- package/{build/src → src}/core/client.d.ts +4 -0
- package/{build/src → src}/core/client.js +14 -2
- package/src/core/table.d.ts +98 -0
- package/src/core/table.js +459 -0
- package/src/core/wrapper.d.ts +17 -0
- package/src/core/wrapper.js +46 -0
- package/src/decorators/belongs_to.d.ts +1 -0
- package/src/decorators/belongs_to.js +24 -0
- package/src/decorators/created_at.d.ts +1 -0
- package/{build/src → src}/decorators/created_at.js +0 -7
- package/src/decorators/default.d.ts +1 -0
- package/{build/src → src}/decorators/default.js +2 -12
- package/src/decorators/has_many.d.ts +1 -0
- package/src/decorators/has_many.js +24 -0
- package/{build/src → src}/decorators/index.d.ts +4 -1
- package/src/decorators/index.js +36 -0
- package/src/decorators/index_sort.d.ts +12 -0
- package/src/decorators/index_sort.js +43 -0
- package/src/decorators/mutate.d.ts +2 -0
- package/{build/src → src}/decorators/mutate.js +2 -11
- package/src/decorators/name.d.ts +1 -0
- package/src/decorators/name.js +28 -0
- package/src/decorators/not_null.d.ts +1 -0
- package/{build/src → src}/decorators/not_null.js +0 -7
- package/src/decorators/primary_key.d.ts +6 -0
- package/src/decorators/primary_key.js +30 -0
- package/src/decorators/updated_at.d.ts +12 -0
- package/src/decorators/updated_at.js +26 -0
- package/src/decorators/validate.d.ts +1 -0
- package/{build/src → src}/decorators/validate.js +0 -7
- package/{build/src → src}/index.d.ts +9 -0
- package/{build/src → src}/index.js +14 -5
- package/{build/src → src}/utils/batch-relations.js +2 -1
- package/src/utils/circular-detector.d.ts +82 -0
- package/src/utils/circular-detector.js +209 -0
- package/src/utils/memory-manager.d.ts +42 -0
- package/src/utils/memory-manager.js +108 -0
- package/{build/src → src}/utils/projection.js +3 -2
- package/src/utils/relations.d.ts +17 -0
- package/src/utils/relations.js +166 -0
- package/src/utils/security-validator.d.ts +49 -0
- package/src/utils/security-validator.js +152 -0
- package/src/utils/throttle-manager.d.ts +78 -0
- package/src/utils/throttle-manager.js +196 -0
- package/src/utils/transaction-manager.d.ts +88 -0
- package/src/utils/transaction-manager.js +298 -0
- package/build/__tests__/crud.spec.d.ts +0 -7
- package/build/__tests__/crud.spec.js +0 -287
- package/build/__tests__/crud.spec.js.map +0 -1
- package/build/__tests__/debug-decorators.spec.d.ts +0 -7
- package/build/__tests__/debug-decorators.spec.js +0 -143
- package/build/__tests__/debug-decorators.spec.js.map +0 -1
- package/build/__tests__/decorators.spec.d.ts +0 -7
- package/build/__tests__/decorators.spec.js +0 -203
- package/build/__tests__/decorators.spec.js.map +0 -1
- package/build/__tests__/instance-crud.spec.d.ts +0 -7
- package/build/__tests__/instance-crud.spec.js +0 -184
- package/build/__tests__/instance-crud.spec.js.map +0 -1
- package/build/src/core/client.js.map +0 -1
- package/build/src/core/table.d.ts +0 -164
- package/build/src/core/table.js +0 -406
- package/build/src/core/table.js.map +0 -1
- package/build/src/core/wrapper.d.ts +0 -54
- package/build/src/core/wrapper.js +0 -27
- package/build/src/core/wrapper.js.map +0 -1
- package/build/src/decorators/created_at.d.ts +0 -8
- package/build/src/decorators/created_at.js.map +0 -1
- package/build/src/decorators/default.d.ts +0 -8
- package/build/src/decorators/default.js.map +0 -1
- package/build/src/decorators/index.js +0 -26
- package/build/src/decorators/index.js.map +0 -1
- package/build/src/decorators/index_sort.d.ts +0 -8
- package/build/src/decorators/index_sort.js +0 -30
- package/build/src/decorators/index_sort.js.map +0 -1
- package/build/src/decorators/mutate.d.ts +0 -9
- package/build/src/decorators/mutate.js.map +0 -1
- package/build/src/decorators/name.d.ts +0 -8
- package/build/src/decorators/name.js +0 -42
- package/build/src/decorators/name.js.map +0 -1
- package/build/src/decorators/not_null.d.ts +0 -8
- package/build/src/decorators/not_null.js.map +0 -1
- package/build/src/decorators/primary_key.d.ts +0 -8
- package/build/src/decorators/primary_key.js +0 -26
- package/build/src/decorators/primary_key.js.map +0 -1
- package/build/src/decorators/updated_at.d.ts +0 -8
- package/build/src/decorators/updated_at.js +0 -18
- package/build/src/decorators/updated_at.js.map +0 -1
- package/build/src/decorators/validate.d.ts +0 -8
- package/build/src/decorators/validate.js.map +0 -1
- package/build/src/index.js.map +0 -1
- package/build/src/utils/batch-relations.js.map +0 -1
- package/build/src/utils/naming.js.map +0 -1
- package/build/src/utils/projection.js.map +0 -1
- package/build/src/utils/relations.d.ts +0 -23
- package/build/src/utils/relations.js +0 -205
- package/build/src/utils/relations.js.map +0 -1
- /package/{build/src → src}/utils/batch-relations.d.ts +0 -0
- /package/{build/src → src}/utils/naming.d.ts +0 -0
- /package/{build/src → src}/utils/naming.js +0 -0
- /package/{build/src → src}/utils/projection.d.ts +0 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file table.ts
|
|
4
|
+
* @descripcion Clase Table rediseñada con API completa y tipado estricto
|
|
5
|
+
* @autor Miguel Alejandro
|
|
6
|
+
* @fecha 2025-07-30
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.STORE = void 0;
|
|
10
|
+
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
11
|
+
const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
|
|
12
|
+
const relations_1 = require("../utils/relations");
|
|
13
|
+
const client_1 = require("./client");
|
|
14
|
+
const wrapper_1 = require("./wrapper");
|
|
15
|
+
Object.defineProperty(exports, "STORE", { enumerable: true, get: function () { return wrapper_1.STORE; } });
|
|
16
|
+
/** Tipos importados desde @types */
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// FUNCIONES UTILITARIAS
|
|
19
|
+
// =============================================================================
|
|
20
|
+
function validateOperator(operator) {
|
|
21
|
+
const validOps = [
|
|
22
|
+
"=",
|
|
23
|
+
"!=",
|
|
24
|
+
"<",
|
|
25
|
+
"<=",
|
|
26
|
+
">",
|
|
27
|
+
">=",
|
|
28
|
+
"in",
|
|
29
|
+
"not-in",
|
|
30
|
+
"contains",
|
|
31
|
+
"begins-with",
|
|
32
|
+
];
|
|
33
|
+
if (!validOps.includes(operator)) {
|
|
34
|
+
throw new Error(`Operador inválido: ${operator}. Válidos: ${validOps.join(", ")}`);
|
|
35
|
+
}
|
|
36
|
+
return operator;
|
|
37
|
+
}
|
|
38
|
+
function buildConditionExpression(filters, operator = "=") {
|
|
39
|
+
const expressions = [];
|
|
40
|
+
const names = {};
|
|
41
|
+
const values = {};
|
|
42
|
+
Object.entries(filters).forEach(([key, value], index) => {
|
|
43
|
+
const nameKey = `#attr${index}`;
|
|
44
|
+
const valueKey = `:val${index}`;
|
|
45
|
+
names[nameKey] = key;
|
|
46
|
+
if (operator === "in" && Array.isArray(value)) {
|
|
47
|
+
const inValues = value.map((v, i) => {
|
|
48
|
+
const inValueKey = `:val${index}_${i}`;
|
|
49
|
+
values[inValueKey] = v;
|
|
50
|
+
return inValueKey;
|
|
51
|
+
});
|
|
52
|
+
expressions.push(`${nameKey} IN (${inValues.join(", ")})`);
|
|
53
|
+
}
|
|
54
|
+
else if (operator === "not-in" && Array.isArray(value)) {
|
|
55
|
+
const notInValues = value.map((v, i) => {
|
|
56
|
+
const notInValueKey = `:val${index}_${i}`;
|
|
57
|
+
values[notInValueKey] = v;
|
|
58
|
+
return notInValueKey;
|
|
59
|
+
});
|
|
60
|
+
expressions.push(`NOT ${nameKey} IN (${notInValues.join(", ")})`);
|
|
61
|
+
}
|
|
62
|
+
else if (operator === "contains") {
|
|
63
|
+
values[valueKey] = value;
|
|
64
|
+
expressions.push(`contains(${nameKey}, ${valueKey})`);
|
|
65
|
+
}
|
|
66
|
+
else if (operator === "begins-with") {
|
|
67
|
+
values[valueKey] = value;
|
|
68
|
+
expressions.push(`begins_with(${nameKey}, ${valueKey})`);
|
|
69
|
+
}
|
|
70
|
+
else if (operator === "!=") {
|
|
71
|
+
// DynamoDB uses <> for not equal
|
|
72
|
+
values[valueKey] = value;
|
|
73
|
+
expressions.push(`${nameKey} <> ${valueKey}`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
values[valueKey] = value;
|
|
77
|
+
expressions.push(`${nameKey} ${operator} ${valueKey}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
expression: expressions.join(" AND "),
|
|
82
|
+
names,
|
|
83
|
+
values,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// CLASE TABLE REDISEÑADA
|
|
88
|
+
// =============================================================================
|
|
89
|
+
class Table {
|
|
90
|
+
constructor(data) {
|
|
91
|
+
(0, client_1.requireClient)();
|
|
92
|
+
const meta = (0, wrapper_1.mustMeta)(Object.getPrototypeOf(this).constructor);
|
|
93
|
+
// Inicializar propiedades con valores por defecto
|
|
94
|
+
meta.columns.forEach((col, key) => {
|
|
95
|
+
if (typeof key === "string" && !(key in data)) {
|
|
96
|
+
// Forzar aplicación de valor por defecto mediante setter
|
|
97
|
+
if (col.default !== undefined) {
|
|
98
|
+
const defaultValue = typeof col.default === "function" ? col.default() : col.default;
|
|
99
|
+
this[key] = defaultValue;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this[key] = undefined;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
Object.assign(this, data);
|
|
107
|
+
}
|
|
108
|
+
/** Serializar instancia a JSON plano */
|
|
109
|
+
toJSON() {
|
|
110
|
+
const meta = (0, wrapper_1.mustMeta)(Object.getPrototypeOf(this).constructor);
|
|
111
|
+
const result = {};
|
|
112
|
+
meta.columns.forEach((column, key) => {
|
|
113
|
+
if (typeof key === "string") {
|
|
114
|
+
// Acceder a la propiedad directamente para activar getters virtuales
|
|
115
|
+
const value = this[key];
|
|
116
|
+
if (value !== undefined) {
|
|
117
|
+
result[key] = value;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// Incluir propiedades enumerables ad-hoc no registradas como relaciones
|
|
122
|
+
// Esto permite persistir campos opcionales sin decoradores (p.ej. 'notes')
|
|
123
|
+
for (const key of Object.keys(this)) {
|
|
124
|
+
if (result[key] !== undefined)
|
|
125
|
+
continue;
|
|
126
|
+
if (meta.relations.has && meta.relations.has(key))
|
|
127
|
+
continue;
|
|
128
|
+
const val = this[key];
|
|
129
|
+
if (val === undefined)
|
|
130
|
+
continue;
|
|
131
|
+
if (typeof val === "function")
|
|
132
|
+
continue;
|
|
133
|
+
result[key] = val;
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
/** Guardar instancia (crear o actualizar) */
|
|
138
|
+
async save() {
|
|
139
|
+
const id = this.id;
|
|
140
|
+
const Ctor = this.constructor;
|
|
141
|
+
const meta = (0, wrapper_1.mustMeta)(Ctor);
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
const isNew = id === undefined || id === null;
|
|
144
|
+
// Actualizar campos de timestamp
|
|
145
|
+
meta.columns.forEach((col, key) => {
|
|
146
|
+
if (col.createdAt && isNew) {
|
|
147
|
+
// Solo establecer createdAt si es un nuevo registro
|
|
148
|
+
this[key] = now;
|
|
149
|
+
}
|
|
150
|
+
else if (col.updatedAt) {
|
|
151
|
+
// Actualizar updatedAt en cada guardado
|
|
152
|
+
this[key] = now;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
const payload = this.toJSON();
|
|
156
|
+
const client = (0, client_1.requireClient)();
|
|
157
|
+
if (isNew) {
|
|
158
|
+
// Crear nuevo registro
|
|
159
|
+
const created = await Ctor.create(payload);
|
|
160
|
+
Object.assign(this, created);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// Actualizar registro existente
|
|
164
|
+
await client.send(new client_dynamodb_1.PutItemCommand({
|
|
165
|
+
TableName: meta.name,
|
|
166
|
+
Item: (0, util_dynamodb_1.marshall)(payload, { removeUndefinedValues: true }),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
/** Actualizar instancia */
|
|
172
|
+
async update(patch) {
|
|
173
|
+
Object.assign(this, patch);
|
|
174
|
+
return await this.save();
|
|
175
|
+
}
|
|
176
|
+
/** Eliminar instancia */
|
|
177
|
+
async destroy() {
|
|
178
|
+
const id = this.id;
|
|
179
|
+
if (id === undefined || id === null) {
|
|
180
|
+
throw new Error("destroy() requiere que la instancia tenga un id");
|
|
181
|
+
}
|
|
182
|
+
const Ctor = this.constructor;
|
|
183
|
+
return await Ctor.delete({ id: String(id) });
|
|
184
|
+
}
|
|
185
|
+
// ===========================================================================
|
|
186
|
+
// MÉTODOS ESTÁTICOS SEGÚN ESPECIFICACIONES
|
|
187
|
+
// ===========================================================================
|
|
188
|
+
/**
|
|
189
|
+
* Crear un nuevo registro en la base de datos
|
|
190
|
+
*/
|
|
191
|
+
static async create(data) {
|
|
192
|
+
const client = (0, client_1.requireClient)();
|
|
193
|
+
const meta = (0, wrapper_1.mustMeta)(this);
|
|
194
|
+
const instance = new this(data);
|
|
195
|
+
// Establecer timestamps si corresponde
|
|
196
|
+
const now = new Date().toISOString();
|
|
197
|
+
meta.columns.forEach((col, key) => {
|
|
198
|
+
if (col.createdAt && instance[key] === undefined) {
|
|
199
|
+
instance[key] = now;
|
|
200
|
+
}
|
|
201
|
+
if (col.updatedAt) {
|
|
202
|
+
instance[key] = now;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
const payload = instance.toJSON();
|
|
206
|
+
await client.send(new client_dynamodb_1.PutItemCommand({
|
|
207
|
+
TableName: meta.name,
|
|
208
|
+
Item: (0, util_dynamodb_1.marshall)(payload, { removeUndefinedValues: true }),
|
|
209
|
+
}));
|
|
210
|
+
return instance;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Actualizar registros en la base de datos
|
|
214
|
+
* @param updates - Campos a actualizar. Los campos con valor `undefined` se ignoran.
|
|
215
|
+
* @param filters - Filtros para seleccionar los registros a actualizar
|
|
216
|
+
* @returns Número de registros actualizados
|
|
217
|
+
*/
|
|
218
|
+
static async update(updates, filters) {
|
|
219
|
+
// Obtener metadatos del modelo para manejar timestamps
|
|
220
|
+
const meta = (0, wrapper_1.mustMeta)(this);
|
|
221
|
+
const client = (0, client_1.requireClient)();
|
|
222
|
+
// Buscar registros que coincidan con los filtros
|
|
223
|
+
const recordsToUpdate = await this.where(filters);
|
|
224
|
+
if (recordsToUpdate.length === 0) {
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
// No filtrar campos undefined aquí para permitir establecer valores nulos/explicitos
|
|
228
|
+
const cleanUpdates = { ...updates };
|
|
229
|
+
// Verificar si hay campos para actualizar
|
|
230
|
+
if (Object.keys(cleanUpdates).length === 0) {
|
|
231
|
+
return recordsToUpdate.length; // No hay cambios que hacer
|
|
232
|
+
}
|
|
233
|
+
// Actualizar cada registro
|
|
234
|
+
const updatePromises = recordsToUpdate.map(async (record) => {
|
|
235
|
+
// Obtener datos actuales
|
|
236
|
+
const currentData = record.toJSON();
|
|
237
|
+
// Aplicar actualizaciones, preservando valores existentes
|
|
238
|
+
const updatedData = { ...currentData };
|
|
239
|
+
// Aplicar solo los campos que no son undefined
|
|
240
|
+
for (const [key, value] of Object.entries(cleanUpdates)) {
|
|
241
|
+
if (value !== undefined) {
|
|
242
|
+
updatedData[key] = value;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Actualizar timestamp updated_at si está configurado
|
|
246
|
+
let updatedAtKey = undefined;
|
|
247
|
+
for (const [k, col] of meta.columns.entries()) {
|
|
248
|
+
if (col.updatedAt === true) {
|
|
249
|
+
updatedAtKey = k;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (updatedAtKey !== undefined) {
|
|
254
|
+
updatedData[String(updatedAtKey)] = new Date().toISOString();
|
|
255
|
+
}
|
|
256
|
+
// Actualizar en la base de datos
|
|
257
|
+
await client.send(new client_dynamodb_1.PutItemCommand({
|
|
258
|
+
TableName: meta.name,
|
|
259
|
+
Item: (0, util_dynamodb_1.marshall)(updatedData, { removeUndefinedValues: true }),
|
|
260
|
+
}));
|
|
261
|
+
});
|
|
262
|
+
await Promise.all(updatePromises);
|
|
263
|
+
return recordsToUpdate.length;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Eliminar registros de la base de datos
|
|
267
|
+
*/
|
|
268
|
+
static async delete(filters) {
|
|
269
|
+
const recordsToDelete = await this.where(filters);
|
|
270
|
+
if (recordsToDelete.length === 0) {
|
|
271
|
+
return 0;
|
|
272
|
+
}
|
|
273
|
+
const client = (0, client_1.requireClient)();
|
|
274
|
+
const meta = (0, wrapper_1.mustMeta)(this);
|
|
275
|
+
const deletePromises = recordsToDelete.map(async (record) => {
|
|
276
|
+
const id = record.id;
|
|
277
|
+
if (id) {
|
|
278
|
+
await client.send(new client_dynamodb_1.DeleteItemCommand({
|
|
279
|
+
TableName: meta.name,
|
|
280
|
+
Key: (0, util_dynamodb_1.marshall)({ id: String(id) }),
|
|
281
|
+
}));
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
await Promise.all(deletePromises);
|
|
285
|
+
return recordsToDelete.length;
|
|
286
|
+
}
|
|
287
|
+
/** Implementación del método where */
|
|
288
|
+
static async where(...args) {
|
|
289
|
+
const client = (0, client_1.requireClient)();
|
|
290
|
+
const meta = (0, wrapper_1.mustMeta)(this);
|
|
291
|
+
let filters;
|
|
292
|
+
let options = {};
|
|
293
|
+
let operator = "=";
|
|
294
|
+
// Parsear argumentos según la sobrecarga utilizada
|
|
295
|
+
if (args.length === 2) {
|
|
296
|
+
if (typeof args[0] === "string") {
|
|
297
|
+
// where(field, value) - check if value is array for IN operation
|
|
298
|
+
const value = args[1];
|
|
299
|
+
if (Array.isArray(value)) {
|
|
300
|
+
operator = "in";
|
|
301
|
+
}
|
|
302
|
+
filters = { [args[0]]: value };
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// where(filters, options)
|
|
306
|
+
filters = args[0];
|
|
307
|
+
options = args[1] || {};
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
else if (args.length === 3) {
|
|
311
|
+
// where(field, operator, value)
|
|
312
|
+
const [field, op, value] = args;
|
|
313
|
+
operator = validateOperator(op);
|
|
314
|
+
filters = { [field]: value };
|
|
315
|
+
}
|
|
316
|
+
else if (args.length === 1) {
|
|
317
|
+
// where(filters)
|
|
318
|
+
filters = args[0];
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
throw new Error("Argumentos inválidos para where()");
|
|
322
|
+
}
|
|
323
|
+
// Validar límites y opciones
|
|
324
|
+
if (options.limit && options.limit < 0) {
|
|
325
|
+
throw new Error("limit debe ser mayor o igual a 0");
|
|
326
|
+
}
|
|
327
|
+
if (options.skip && options.skip < 0) {
|
|
328
|
+
throw new Error("skip debe ser mayor o igual a 0");
|
|
329
|
+
}
|
|
330
|
+
if (options.order && !["ASC", "DESC"].includes(options.order)) {
|
|
331
|
+
throw new Error('order debe ser "ASC" o "DESC"');
|
|
332
|
+
}
|
|
333
|
+
// Construir la consulta DynamoDB
|
|
334
|
+
const { expression, names, values } = buildConditionExpression(filters, operator);
|
|
335
|
+
const scanParams = {
|
|
336
|
+
TableName: meta.name,
|
|
337
|
+
};
|
|
338
|
+
// Initialize ExpressionAttributeNames to avoid conflicts
|
|
339
|
+
if (!scanParams.ExpressionAttributeNames) {
|
|
340
|
+
scanParams.ExpressionAttributeNames = {};
|
|
341
|
+
}
|
|
342
|
+
if (expression) {
|
|
343
|
+
scanParams.FilterExpression = expression;
|
|
344
|
+
// Merge filter attribute names
|
|
345
|
+
Object.assign(scanParams.ExpressionAttributeNames, names);
|
|
346
|
+
scanParams.ExpressionAttributeValues = (0, util_dynamodb_1.marshall)(values);
|
|
347
|
+
}
|
|
348
|
+
if (options.attributes) {
|
|
349
|
+
// Handle projection attributes with proper aliases
|
|
350
|
+
const projectionExpressions = options.attributes.map((attr, index) => {
|
|
351
|
+
const aliasKey = `#proj${index}`;
|
|
352
|
+
scanParams.ExpressionAttributeNames[aliasKey] = attr;
|
|
353
|
+
return aliasKey;
|
|
354
|
+
});
|
|
355
|
+
scanParams.ProjectionExpression = projectionExpressions.join(", ");
|
|
356
|
+
}
|
|
357
|
+
// Ejecutar consulta con paginación
|
|
358
|
+
let allItems = [];
|
|
359
|
+
let lastEvaluatedKey = undefined;
|
|
360
|
+
let scannedCount = 0;
|
|
361
|
+
const targetSkip = options.skip || 0;
|
|
362
|
+
const targetLimit = options.limit || 100;
|
|
363
|
+
do {
|
|
364
|
+
if (lastEvaluatedKey) {
|
|
365
|
+
scanParams.ExclusiveStartKey = lastEvaluatedKey;
|
|
366
|
+
}
|
|
367
|
+
const result = await client.send(new client_dynamodb_1.ScanCommand(scanParams));
|
|
368
|
+
if (result.Items) {
|
|
369
|
+
const items = result.Items.map((item) => (0, util_dynamodb_1.unmarshall)(item));
|
|
370
|
+
for (const item of items) {
|
|
371
|
+
if (scannedCount < targetSkip) {
|
|
372
|
+
scannedCount++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (allItems.length >= targetLimit) {
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
allItems.push(item);
|
|
379
|
+
scannedCount++;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
383
|
+
} while (lastEvaluatedKey && allItems.length < targetLimit);
|
|
384
|
+
// Aplicar ordenamiento
|
|
385
|
+
if (options.order) {
|
|
386
|
+
allItems.sort((a, b) => {
|
|
387
|
+
const sortField = Object.keys(filters)[0] || "id";
|
|
388
|
+
const aVal = a[sortField];
|
|
389
|
+
const bVal = b[sortField];
|
|
390
|
+
if (aVal < bVal)
|
|
391
|
+
return options.order === "ASC" ? -1 : 1;
|
|
392
|
+
if (aVal > bVal)
|
|
393
|
+
return options.order === "ASC" ? 1 : -1;
|
|
394
|
+
return 0;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
// Convertir a instancias del modelo
|
|
398
|
+
const instances = allItems.map((item) => {
|
|
399
|
+
if (options.attributes) {
|
|
400
|
+
// When using attribute selection, create minimal instances
|
|
401
|
+
// only with the requested fields to avoid default value population
|
|
402
|
+
const instance = Object.create(this.prototype);
|
|
403
|
+
Object.assign(instance, item);
|
|
404
|
+
return instance;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
// Normal instantiation with all defaults
|
|
408
|
+
return new this(item);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
// Procesar includes si están presentes
|
|
412
|
+
if (options.include) {
|
|
413
|
+
return await (0, relations_1.processIncludes)(this, instances, options.include);
|
|
414
|
+
}
|
|
415
|
+
return instances;
|
|
416
|
+
}
|
|
417
|
+
static async first(...args) {
|
|
418
|
+
const results = await this.where(...args);
|
|
419
|
+
return results[0] || undefined;
|
|
420
|
+
}
|
|
421
|
+
static async last(...args) {
|
|
422
|
+
// Soporte de firmas sin generar una cuarta sobrecarga inválida para where()
|
|
423
|
+
if (args.length === 0) {
|
|
424
|
+
const results = await this.where({}, { order: "DESC", limit: 1 });
|
|
425
|
+
return results[0] || undefined;
|
|
426
|
+
}
|
|
427
|
+
if (args.length === 1) {
|
|
428
|
+
if (typeof args[0] === "object" && !Array.isArray(args[0])) {
|
|
429
|
+
const results = await this.where(args[0], {
|
|
430
|
+
order: "DESC",
|
|
431
|
+
limit: 1,
|
|
432
|
+
});
|
|
433
|
+
return results[0] || undefined;
|
|
434
|
+
}
|
|
435
|
+
throw new Error("Se requiere un valor para el campo de filtro");
|
|
436
|
+
}
|
|
437
|
+
if (args.length === 2) {
|
|
438
|
+
// field, value
|
|
439
|
+
const results = await this.where(args[0], args[1]);
|
|
440
|
+
return results[results.length - 1];
|
|
441
|
+
}
|
|
442
|
+
if (args.length === 3) {
|
|
443
|
+
// field, operator, value
|
|
444
|
+
const results = await this.where(args[0], args[1], args[2]);
|
|
445
|
+
return results[results.length - 1];
|
|
446
|
+
}
|
|
447
|
+
if (args.length === 2 && typeof args[1] === "object") {
|
|
448
|
+
const results = await this.where(args[0], {
|
|
449
|
+
...(args[1] || {}),
|
|
450
|
+
order: "DESC",
|
|
451
|
+
limit: 1,
|
|
452
|
+
});
|
|
453
|
+
return results[0] || undefined;
|
|
454
|
+
}
|
|
455
|
+
throw new Error("Argumentos no válidos para last()");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
exports.default = Table;
|
|
459
|
+
//# sourceMappingURL=table.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file wrapper.ts
|
|
3
|
+
* @descripcion Sistema de tipos y wrapper optimizado
|
|
4
|
+
* @autor Miguel Alejandro
|
|
5
|
+
* @fecha 2025-01-28
|
|
6
|
+
*/
|
|
7
|
+
import type { Column, WrapperEntry } from "../@types/index";
|
|
8
|
+
export type { Column, WrapperEntry, HasMany, BelongsTo, NonAttribute, CreationOptional, InferAttributes, FilterableAttributes, QueryResult, WhereOptions, WhereOptionsWithoutWhere, QueryOperator, IncludeOptions, IncludeRelationOptions, WhereQueryOptions, Mutate, Validate, } from "../@types/index";
|
|
9
|
+
export declare const STORE: unique symbol;
|
|
10
|
+
declare const wrapper: Map<Function, WrapperEntry>;
|
|
11
|
+
/** Obtener o crear entrada wrapper para constructor */
|
|
12
|
+
export declare const ensureConfig: (ctor: Function, table_name: string) => WrapperEntry;
|
|
13
|
+
/** Obtener o crear configuración de columna para propiedad */
|
|
14
|
+
export declare const ensureColumn: (entry: WrapperEntry, prop: string | symbol, column_name: string) => Column;
|
|
15
|
+
/** Obtener metadatos requeridos (throws si no existen) */
|
|
16
|
+
export declare const mustMeta: (ctor: Function) => WrapperEntry;
|
|
17
|
+
export default wrapper;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file wrapper.ts
|
|
4
|
+
* @descripcion Sistema de tipos y wrapper optimizado
|
|
5
|
+
* @autor Miguel Alejandro
|
|
6
|
+
* @fecha 2025-01-28
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.mustMeta = exports.ensureColumn = exports.ensureConfig = exports.STORE = void 0;
|
|
10
|
+
exports.STORE = Symbol("dynamite:values");
|
|
11
|
+
const wrapper = new Map();
|
|
12
|
+
/** Obtener o crear entrada wrapper para constructor */
|
|
13
|
+
const ensureConfig = (ctor, table_name) => {
|
|
14
|
+
const existing = wrapper.get(ctor);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
const entry = {
|
|
18
|
+
name: table_name,
|
|
19
|
+
columns: new Map(),
|
|
20
|
+
relations: new Map(),
|
|
21
|
+
};
|
|
22
|
+
wrapper.set(ctor, entry);
|
|
23
|
+
return entry;
|
|
24
|
+
};
|
|
25
|
+
exports.ensureConfig = ensureConfig;
|
|
26
|
+
/** Obtener o crear configuración de columna para propiedad */
|
|
27
|
+
const ensureColumn = (entry, prop, column_name) => {
|
|
28
|
+
const existing = entry.columns.get(prop);
|
|
29
|
+
if (existing)
|
|
30
|
+
return existing;
|
|
31
|
+
const column = { name: column_name, mutate: [], validate: [] };
|
|
32
|
+
entry.columns.set(prop, column);
|
|
33
|
+
return column;
|
|
34
|
+
};
|
|
35
|
+
exports.ensureColumn = ensureColumn;
|
|
36
|
+
/** Obtener metadatos requeridos (throws si no existen) */
|
|
37
|
+
const mustMeta = (ctor) => {
|
|
38
|
+
const meta = wrapper.get(ctor);
|
|
39
|
+
if (!meta) {
|
|
40
|
+
throw new Error(`Metadatos no encontrados para ${ctor.name}. ¿Usaste decoradores @Index, @PrimaryKey, etc.?`);
|
|
41
|
+
}
|
|
42
|
+
return meta;
|
|
43
|
+
};
|
|
44
|
+
exports.mustMeta = mustMeta;
|
|
45
|
+
exports.default = wrapper;
|
|
46
|
+
//# sourceMappingURL=wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function BelongsTo(targetModel: () => any, localKey: string, foreignKey?: string): PropertyDecorator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = BelongsTo;
|
|
4
|
+
const wrapper_1 = require("../core/wrapper");
|
|
5
|
+
function BelongsTo(targetModel, localKey, foreignKey) {
|
|
6
|
+
return (target, prop) => {
|
|
7
|
+
const entry = (0, wrapper_1.ensureConfig)(target.constructor, target.constructor.name);
|
|
8
|
+
entry.relations.set(prop, {
|
|
9
|
+
type: "belongsTo",
|
|
10
|
+
targetModel,
|
|
11
|
+
foreignKey: foreignKey || "id",
|
|
12
|
+
localKey,
|
|
13
|
+
});
|
|
14
|
+
// Crear getter dinámico para la relación
|
|
15
|
+
Object.defineProperty(target, prop, {
|
|
16
|
+
get() {
|
|
17
|
+
return this[`_${prop.toString()}`] || null;
|
|
18
|
+
},
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=belongs_to.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function CreatedAt(): PropertyDecorator;
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file created_at.ts
|
|
4
|
-
* @descripcion Decorador @CreatedAt para timestamp de creación
|
|
5
|
-
* @autor Miguel Alejandro
|
|
6
|
-
* @fecha 2025-01-27
|
|
7
|
-
*/
|
|
8
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
9
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
10
4
|
};
|
|
11
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
6
|
exports.default = CreatedAt;
|
|
13
7
|
const default_1 = __importDefault(require("./default"));
|
|
14
|
-
/** Decorador para establecer timestamp automático de creación */
|
|
15
8
|
function CreatedAt() {
|
|
16
9
|
return (0, default_1.default)(() => new Date().toISOString());
|
|
17
10
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function Default(factory: any): PropertyDecorator;
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* @file default.ts
|
|
4
|
-
* @descripcion Decorador @Default para valores por defecto
|
|
5
|
-
* @autor Miguel Alejandro
|
|
6
|
-
* @fecha 2025-01-27
|
|
7
|
-
*/
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.default = Default;
|
|
10
4
|
const wrapper_1 = require("../core/wrapper");
|
|
11
5
|
const naming_1 = require("../utils/naming");
|
|
12
|
-
/** Decorador para establecer valores por defecto en propiedades */
|
|
13
6
|
function Default(factory) {
|
|
14
7
|
return (target, prop) => {
|
|
15
8
|
const ctor = target.constructor;
|
|
@@ -22,7 +15,6 @@ function Default(factory) {
|
|
|
22
15
|
defineVirtual(ctor.prototype, column, prop);
|
|
23
16
|
};
|
|
24
17
|
}
|
|
25
|
-
/** Define propiedad virtual con getter/setter para manejo de defaults */
|
|
26
18
|
function defineVirtual(proto, col, prop) {
|
|
27
19
|
Object.defineProperty(proto, prop, {
|
|
28
20
|
get() {
|
|
@@ -42,10 +34,8 @@ function defineVirtual(proto, col, prop) {
|
|
|
42
34
|
if (col.validate) {
|
|
43
35
|
for (const v of col.validate) {
|
|
44
36
|
const r = v(val);
|
|
45
|
-
r !== true
|
|
46
|
-
(
|
|
47
|
-
throw new Error(typeof r === "string" ? r : "Validación fallida");
|
|
48
|
-
})();
|
|
37
|
+
if (r !== true)
|
|
38
|
+
throw new Error(typeof r === "string" ? r : "Validación fallida");
|
|
49
39
|
}
|
|
50
40
|
}
|
|
51
41
|
buf[prop] = val;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function HasMany(targetModel: () => any, foreignKey: string, localKey?: string): PropertyDecorator;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = HasMany;
|
|
4
|
+
const wrapper_1 = require("../core/wrapper");
|
|
5
|
+
function HasMany(targetModel, foreignKey, localKey) {
|
|
6
|
+
return (target, prop) => {
|
|
7
|
+
const entry = (0, wrapper_1.ensureConfig)(target.constructor, target.constructor.name);
|
|
8
|
+
entry.relations.set(prop, {
|
|
9
|
+
type: "hasMany",
|
|
10
|
+
targetModel,
|
|
11
|
+
foreignKey,
|
|
12
|
+
localKey: localKey || "id",
|
|
13
|
+
});
|
|
14
|
+
// Crear getter dinámico para la relación
|
|
15
|
+
Object.defineProperty(target, prop, {
|
|
16
|
+
get() {
|
|
17
|
+
return this[`_${prop.toString()}`] || [];
|
|
18
|
+
},
|
|
19
|
+
enumerable: true,
|
|
20
|
+
configurable: true,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=has_many.js.map
|
|
@@ -4,5 +4,8 @@
|
|
|
4
4
|
* @autor Miguel Alejandro
|
|
5
5
|
* @fecha 2025-01-27
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Decorador para marcar propiedad como Partition Key.
|
|
9
|
+
* Configura la propiedad como índice principal para consultas.
|
|
10
|
+
*/
|
|
8
11
|
export default function Index(): PropertyDecorator;
|