@arcaelas/dynamite 1.0.15 → 1.0.18

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.
@@ -0,0 +1,659 @@
1
+ "use strict";
2
+ /**
3
+ * @file table.ts
4
+ * @description Tabla autocontenida con arquitectura minimalista y Symbol storage
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-28
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
10
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
11
+ const client_1 = require("./client");
12
+ const decorator_1 = require("./decorator");
13
+ const relations_1 = require("../utils/relations");
14
+ // Alias de operadores para sintaxis objeto
15
+ const OPERATOR_ALIASES = {
16
+ '$eq': '=',
17
+ '$ne': '!=',
18
+ '$lt': '<',
19
+ '$lte': '<=',
20
+ '$gt': '>',
21
+ '$gte': '>=',
22
+ '$in': 'in',
23
+ '$nin': 'not-in',
24
+ 'contains': 'contains',
25
+ 'begins-with': 'begins-with',
26
+ 'beginsWith': 'begins-with',
27
+ // Operadores directos también soportados
28
+ '=': '=',
29
+ '!=': '!=',
30
+ '<': '<',
31
+ '<=': '<=',
32
+ '>': '>',
33
+ '>=': '>=',
34
+ 'in': 'in',
35
+ 'not-in': 'not-in',
36
+ };
37
+ // =============================================================================
38
+ // FUNCIONES UTILITARIAS SIMPLIFICADAS
39
+ // =============================================================================
40
+ function validateOperator(operator) {
41
+ const validOps = ["=", "!=", "<", "<=", ">", ">=", "in", "not-in", "contains", "begins-with"];
42
+ if (!validOps.includes(operator)) {
43
+ throw new Error(`Operador inválido: ${operator}. Válidos: ${validOps.join(", ")}`);
44
+ }
45
+ return operator;
46
+ }
47
+ /**
48
+ * @description Detecta si un valor es un objeto con operadores
49
+ */
50
+ function isOperatorObject(value) {
51
+ if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) {
52
+ return false;
53
+ }
54
+ // Es objeto con operadores si alguna key es un operador conocido
55
+ return Object.keys(value).some(key => key in OPERATOR_ALIASES);
56
+ }
57
+ /**
58
+ * @description Genera expresión para un operador y valor específico
59
+ */
60
+ function buildSingleExpression(nameKey, valueKeyPrefix, op, val, names, values, fieldName) {
61
+ // Manejar arrays vacíos
62
+ if (op === "in" && Array.isArray(val) && val.length === 0) {
63
+ return "1 = 0"; // Ningún resultado coincide
64
+ }
65
+ if (op === "not-in" && Array.isArray(val) && val.length === 0) {
66
+ return null; // Excluir nada = todo pasa
67
+ }
68
+ names[nameKey] = fieldName;
69
+ if (op === "in" && Array.isArray(val)) {
70
+ const inValues = val.map((v, i) => {
71
+ const inValueKey = `${valueKeyPrefix}_${i}`;
72
+ values[inValueKey] = v;
73
+ return inValueKey;
74
+ });
75
+ return `${nameKey} IN (${inValues.join(", ")})`;
76
+ }
77
+ else if (op === "not-in" && Array.isArray(val)) {
78
+ const notInValues = val.map((v, i) => {
79
+ const notInValueKey = `${valueKeyPrefix}_${i}`;
80
+ values[notInValueKey] = v;
81
+ return notInValueKey;
82
+ });
83
+ return `NOT ${nameKey} IN (${notInValues.join(", ")})`;
84
+ }
85
+ else if (op === "contains") {
86
+ values[valueKeyPrefix] = val;
87
+ return `contains(${nameKey}, ${valueKeyPrefix})`;
88
+ }
89
+ else if (op === "begins-with") {
90
+ values[valueKeyPrefix] = val;
91
+ return `begins_with(${nameKey}, ${valueKeyPrefix})`;
92
+ }
93
+ else if (op === "!=") {
94
+ values[valueKeyPrefix] = val;
95
+ return `${nameKey} <> ${valueKeyPrefix}`;
96
+ }
97
+ else {
98
+ values[valueKeyPrefix] = val;
99
+ return `${nameKey} ${op} ${valueKeyPrefix}`;
100
+ }
101
+ }
102
+ function buildConditionExpression(filters, defaultOperator = "=") {
103
+ const expressions = [];
104
+ const names = {};
105
+ const values = {};
106
+ let index = 0;
107
+ for (const [fieldName, filterValue] of Object.entries(filters)) {
108
+ // Ignorar valores undefined
109
+ if (filterValue === undefined)
110
+ continue;
111
+ // Detectar sintaxis objeto con operadores: { campo: { operador: valor } }
112
+ if (isOperatorObject(filterValue)) {
113
+ // Procesar cada operador en el objeto
114
+ for (const [opKey, opValue] of Object.entries(filterValue)) {
115
+ // Ignorar valores undefined dentro del objeto
116
+ if (opValue === undefined)
117
+ continue;
118
+ const resolvedOp = OPERATOR_ALIASES[opKey];
119
+ if (!resolvedOp)
120
+ continue; // Ignorar keys desconocidas
121
+ const nameKey = `#attr${index}`;
122
+ const valueKey = `:val${index}`;
123
+ const expr = buildSingleExpression(nameKey, valueKey, resolvedOp, opValue, names, values, fieldName);
124
+ if (expr)
125
+ expressions.push(expr);
126
+ index++;
127
+ }
128
+ }
129
+ else {
130
+ // Valor simple: usar operador default
131
+ // Ignorar null solo si no hay operador explícito
132
+ if (filterValue === null)
133
+ continue;
134
+ const nameKey = `#attr${index}`;
135
+ const valueKey = `:val${index}`;
136
+ const expr = buildSingleExpression(nameKey, valueKey, defaultOperator, filterValue, names, values, fieldName);
137
+ if (expr)
138
+ expressions.push(expr);
139
+ index++;
140
+ }
141
+ }
142
+ return {
143
+ expression: expressions.join(" AND "),
144
+ names,
145
+ values,
146
+ };
147
+ }
148
+ // =============================================================================
149
+ // CLASE TABLE AUTOCONTENIDA
150
+ // =============================================================================
151
+ class Table {
152
+ constructor(props = {}) {
153
+ (0, client_1.requireClient)();
154
+ const schema = this.constructor[decorator_1.SCHEMA];
155
+ // Inicializar VALUES
156
+ this[decorator_1.VALUES] = {};
157
+ // Procesar cada columna del schema
158
+ for (const column_name in schema.columns) {
159
+ const column = schema.columns[column_name];
160
+ if (column.store?.relation) {
161
+ // ═══════════════════════════════════════════════════════════════
162
+ // RELACIONES: getter lazy que convierte a instancias, setter bloqueado
163
+ // ═══════════════════════════════════════════════════════════════
164
+ let relation_data = props[column_name] ?? undefined;
165
+ Object.defineProperty(this, column_name, {
166
+ enumerable: true,
167
+ configurable: true, // Permite reconfigurar cuando processIncludes carga datos
168
+ set: () => undefined, // Relaciones son read-only
169
+ get: () => {
170
+ if (relation_data === undefined)
171
+ return undefined;
172
+ const RelatedModel = column.store.relation.model();
173
+ const type = column.store.relation.type;
174
+ if (type === 'HasMany') {
175
+ // HasMany: Array de instancias
176
+ return [].concat(relation_data ?? [])
177
+ .filter(Boolean)
178
+ .map((item) => item instanceof RelatedModel ? item : new RelatedModel(item));
179
+ }
180
+ else {
181
+ // HasOne / BelongsTo: Instancia única o null
182
+ if (relation_data === null)
183
+ return null;
184
+ return relation_data instanceof RelatedModel
185
+ ? relation_data
186
+ : new RelatedModel(relation_data);
187
+ }
188
+ },
189
+ });
190
+ }
191
+ else {
192
+ // ═══════════════════════════════════════════════════════════════
193
+ // COLUMNAS REGULARES: getter/setter con pipelines via reduce
194
+ // ═══════════════════════════════════════════════════════════════
195
+ const has_initial_value = column_name in props;
196
+ let value = has_initial_value ? props[column_name] : null;
197
+ // Aplicar set pipeline en inicialización (solo si usuario proporciona valor)
198
+ if (has_initial_value) {
199
+ value = column.set.reduce((v, fn) => fn(v), value);
200
+ }
201
+ // Almacenar en VALUES para compatibilidad con métodos existentes
202
+ this[decorator_1.VALUES][column_name] = value;
203
+ Object.defineProperty(this, column_name, {
204
+ enumerable: true,
205
+ configurable: true,
206
+ get: () => {
207
+ // Aplicar get pipeline
208
+ let v = column.get.reduce((val, fn) => fn(val), value);
209
+ // Almacenar si se generó default (para consistencia)
210
+ if (value === null && v !== null) {
211
+ value = v;
212
+ this[decorator_1.VALUES][column_name] = v;
213
+ }
214
+ return v;
215
+ },
216
+ set: (new_value) => {
217
+ // Aplicar set pipeline
218
+ value = column.set.reduce((v, fn) => fn(v), new_value ?? null);
219
+ this[decorator_1.VALUES][column_name] = value;
220
+ },
221
+ });
222
+ }
223
+ }
224
+ }
225
+ // **Métodos de instancia con API preservada**
226
+ /**
227
+ * @description Serializa la instancia a JSON incluyendo relaciones recursivamente
228
+ * @returns Objeto JSON con todas las propiedades y relaciones
229
+ */
230
+ toJSON() {
231
+ const schema = this.constructor[decorator_1.SCHEMA];
232
+ const result = {};
233
+ for (const column_name in schema.columns) {
234
+ const column = schema.columns[column_name];
235
+ const value = this[column_name];
236
+ if (value === null || value === undefined)
237
+ continue;
238
+ if (column.store?.relation) {
239
+ // Serializar relaciones recursivamente
240
+ if (Array.isArray(value)) {
241
+ result[column_name] = value.map(item => item?.toJSON ? item.toJSON() : item);
242
+ }
243
+ else {
244
+ result[column_name] = value?.toJSON ? value.toJSON() : value;
245
+ }
246
+ }
247
+ else {
248
+ result[column_name] = value;
249
+ }
250
+ }
251
+ return result;
252
+ }
253
+ /**
254
+ * @description Serializa la instancia a string JSON
255
+ * @returns String JSON con todas las propiedades y relaciones
256
+ */
257
+ toString() {
258
+ return JSON.stringify(this);
259
+ }
260
+ /**
261
+ * @description Genera payload para operaciones de DB (excluye relaciones)
262
+ */
263
+ _toDBPayload() {
264
+ const schema = this.constructor[decorator_1.SCHEMA];
265
+ const result = {};
266
+ for (const column_name in schema.columns) {
267
+ const column = schema.columns[column_name];
268
+ if (column.store?.relation)
269
+ continue;
270
+ const value = this[column_name];
271
+ if (value !== null && value !== undefined) {
272
+ result[column_name] = value;
273
+ }
274
+ }
275
+ return result;
276
+ }
277
+ async save() {
278
+ const schema = this.constructor[decorator_1.SCHEMA];
279
+ const id = this[decorator_1.VALUES][schema.primary_key];
280
+ const is_new = id === undefined || id === null;
281
+ // Validación lazy
282
+ for (const column_name in schema.columns) {
283
+ const column = schema.columns[column_name];
284
+ if (column.store?.lazy_validators && !column.store?.relation) {
285
+ const value = this[decorator_1.VALUES][column_name];
286
+ for (const validator of column.store.lazy_validators) {
287
+ const result = validator(value);
288
+ if (result !== true) {
289
+ throw new Error(typeof result === "string" ? result : "Validación fallida");
290
+ }
291
+ }
292
+ }
293
+ }
294
+ // Timestamps automáticos (updatedAt solamente, createdAt se genera via pipeline)
295
+ const now = new Date().toISOString();
296
+ for (const column_name in schema.columns) {
297
+ const column = schema.columns[column_name];
298
+ if (column.store?.updatedAt) {
299
+ // Usar setter para sincronizar closure y VALUES
300
+ this[column_name] = now;
301
+ }
302
+ }
303
+ // Preparar datos para DB (omitir relaciones)
304
+ const db_data = this._toDBPayload();
305
+ if (is_new) {
306
+ // Insertar nuevo registro
307
+ await this.insertIntoDynamoDB(db_data);
308
+ }
309
+ else {
310
+ // Actualizar registro existente
311
+ await this.updateInDynamoDB(db_data);
312
+ }
313
+ return this;
314
+ }
315
+ async destroy() {
316
+ const schema = this.constructor[decorator_1.SCHEMA];
317
+ const id = this[decorator_1.VALUES][schema.primary_key];
318
+ if (!id) {
319
+ throw new Error('Cannot destroy record without ID');
320
+ }
321
+ // Buscar si hay soft delete configurado
322
+ let softDeleteColumn = null;
323
+ for (const column_name in schema.columns) {
324
+ const column = schema.columns[column_name];
325
+ if (column.store?.softDelete) {
326
+ softDeleteColumn = column_name;
327
+ break;
328
+ }
329
+ }
330
+ if (softDeleteColumn) {
331
+ // Soft delete
332
+ this[decorator_1.VALUES][softDeleteColumn] = new Date().toISOString();
333
+ await this.save();
334
+ }
335
+ else {
336
+ // Hard delete
337
+ await this.deleteFromDynamoDB(id);
338
+ }
339
+ return null;
340
+ }
341
+ async forceDestroy() {
342
+ const schema = this.constructor[decorator_1.SCHEMA];
343
+ const id = this[decorator_1.VALUES][schema.primary_key];
344
+ if (!id) {
345
+ throw new Error('Cannot destroy record without ID');
346
+ }
347
+ await this.deleteFromDynamoDB(id);
348
+ return null;
349
+ }
350
+ // **Métodos estáticos con API preservada**
351
+ static async create(data, tx) {
352
+ const instance = new this(data);
353
+ // Establecer timestamps si corresponde
354
+ const schema = this[decorator_1.SCHEMA];
355
+ const now = new Date().toISOString();
356
+ // Solo updatedAt, createdAt se genera via pipeline
357
+ for (const column_name in schema.columns) {
358
+ const column = schema.columns[column_name];
359
+ if (column.store?.updatedAt) {
360
+ // Usar setter para sincronizar closure y VALUES
361
+ instance[column_name] = now;
362
+ }
363
+ }
364
+ const payload = instance._toDBPayload();
365
+ if (tx) {
366
+ tx.addPut(schema.name, payload);
367
+ }
368
+ else {
369
+ const client = (0, client_1.requireClient)();
370
+ await client.send(new client_dynamodb_1.PutItemCommand({
371
+ TableName: schema.name,
372
+ Item: (0, util_dynamodb_1.marshall)(payload, { removeUndefinedValues: true }),
373
+ }));
374
+ }
375
+ return instance;
376
+ }
377
+ static async update(updates, filters, tx) {
378
+ const records = await this.where(filters);
379
+ if (records.length === 0) {
380
+ return 0;
381
+ }
382
+ let updatedCount = 0;
383
+ const schema = this[decorator_1.SCHEMA];
384
+ const client = tx ? null : (0, client_1.requireClient)();
385
+ for (const record of records) {
386
+ // Aplicar actualizaciones
387
+ for (const [key, value] of Object.entries(updates)) {
388
+ record[key] = value;
389
+ }
390
+ // Actualizar timestamp
391
+ const now = new Date().toISOString();
392
+ for (const column_name in schema.columns) {
393
+ const column = schema.columns[column_name];
394
+ if (column.store?.updatedAt) {
395
+ // Usar setter para sincronizar closure y VALUES
396
+ record[column_name] = now;
397
+ break;
398
+ }
399
+ }
400
+ const updated_data = record._toDBPayload();
401
+ if (tx) {
402
+ tx.addPut(schema.name, updated_data);
403
+ }
404
+ else {
405
+ await client.send(new client_dynamodb_1.PutItemCommand({
406
+ TableName: schema.name,
407
+ Item: (0, util_dynamodb_1.marshall)(updated_data, { removeUndefinedValues: true }),
408
+ }));
409
+ }
410
+ updatedCount++;
411
+ }
412
+ return updatedCount;
413
+ }
414
+ static async delete(filters, tx) {
415
+ const records = await this.where(filters);
416
+ if (records.length === 0) {
417
+ return 0;
418
+ }
419
+ const schema = this[decorator_1.SCHEMA];
420
+ const client = tx ? null : (0, client_1.requireClient)();
421
+ let deletedCount = 0;
422
+ for (const record of records) {
423
+ const id = record[decorator_1.VALUES][schema.primary_key];
424
+ if (id) {
425
+ if (tx) {
426
+ tx.addDelete(schema.name, { [schema.primary_key]: id });
427
+ }
428
+ else {
429
+ await client.send(new client_dynamodb_1.DeleteItemCommand({
430
+ TableName: schema.name,
431
+ Key: (0, util_dynamodb_1.marshall)({ [schema.primary_key]: id }),
432
+ }));
433
+ }
434
+ deletedCount++;
435
+ }
436
+ }
437
+ return deletedCount;
438
+ }
439
+ static async where(field_or_filters, operator_or_value, value, options) {
440
+ const client = (0, client_1.requireClient)();
441
+ const schema = this[decorator_1.SCHEMA];
442
+ // Parsear argumentos dinámicamente (como el original)
443
+ let filters;
444
+ let queryOptions;
445
+ let operator = "=";
446
+ if (typeof operator_or_value === 'string') {
447
+ // where(field, operator, value, options?)
448
+ operator = validateOperator(operator_or_value);
449
+ filters = { [field_or_filters]: value };
450
+ queryOptions = options || {};
451
+ }
452
+ else if (value !== undefined) {
453
+ // where(field, value, options?)
454
+ filters = { [field_or_filters]: Array.isArray(operator_or_value) ? { in: operator_or_value } : operator_or_value };
455
+ queryOptions = value || {};
456
+ }
457
+ else if (operator_or_value !== undefined && typeof operator_or_value === 'object') {
458
+ // where(filters, options?)
459
+ filters = field_or_filters;
460
+ queryOptions = operator_or_value;
461
+ }
462
+ else {
463
+ // where(filters?)
464
+ filters = field_or_filters;
465
+ queryOptions = {};
466
+ }
467
+ // Validar opciones
468
+ if (queryOptions.limit === 0)
469
+ return [];
470
+ if (queryOptions.limit && queryOptions.limit < 0) {
471
+ throw new Error("limit debe ser mayor o igual a 0");
472
+ }
473
+ if (queryOptions.skip && queryOptions.skip < 0) {
474
+ throw new Error("skip debe ser mayor o igual a 0");
475
+ }
476
+ if (queryOptions.order && !["ASC", "DESC"].includes(queryOptions.order)) {
477
+ throw new Error('order debe ser "ASC" o "DESC"');
478
+ }
479
+ // Auto-excluir soft deleted
480
+ if (!queryOptions._includeTrashed) {
481
+ for (const column_name in schema.columns) {
482
+ const column = schema.columns[column_name];
483
+ if (column.store?.softDelete && !(column_name in filters)) {
484
+ filters[column_name] = null;
485
+ break;
486
+ }
487
+ }
488
+ }
489
+ // Construir consulta DynamoDB
490
+ const { expression, names, values } = buildConditionExpression(filters, operator);
491
+ const scanParams = {
492
+ TableName: schema.name,
493
+ ExpressionAttributeNames: names || {},
494
+ };
495
+ if (expression) {
496
+ scanParams.FilterExpression = expression;
497
+ scanParams.ExpressionAttributeValues = (0, util_dynamodb_1.marshall)(values || {}, { removeUndefinedValues: true });
498
+ }
499
+ // Proyección solo si hay atributos específicos (ignorar array vacío)
500
+ if (queryOptions.attributes && queryOptions.attributes.length > 0) {
501
+ const projectionExpressions = queryOptions.attributes.map((attr, index) => {
502
+ const aliasKey = `#proj${index}`;
503
+ scanParams.ExpressionAttributeNames[aliasKey] = String(attr);
504
+ return aliasKey;
505
+ });
506
+ scanParams.ProjectionExpression = projectionExpressions.join(", ");
507
+ }
508
+ // Limpiar ExpressionAttributeNames si está vacío (DynamoDB lo rechaza)
509
+ if (Object.keys(scanParams.ExpressionAttributeNames).length === 0) {
510
+ delete scanParams.ExpressionAttributeNames;
511
+ }
512
+ // Ejecutar consulta con paginación
513
+ let allItems = [];
514
+ let lastEvaluatedKey = undefined;
515
+ let scannedCount = 0;
516
+ const targetSkip = queryOptions.skip || 0;
517
+ const targetLimit = queryOptions.limit ?? 100;
518
+ do {
519
+ if (lastEvaluatedKey) {
520
+ scanParams.ExclusiveStartKey = lastEvaluatedKey;
521
+ }
522
+ const result = await client.send(new client_dynamodb_1.ScanCommand(scanParams));
523
+ if (result.Items) {
524
+ const items = result.Items.map((item) => {
525
+ const raw = (0, util_dynamodb_1.unmarshall)(item);
526
+ // Limpiar null/undefined
527
+ for (const k of Object.keys(raw)) {
528
+ if (raw[k] === null || raw[k] === undefined) {
529
+ delete raw[k];
530
+ }
531
+ }
532
+ return raw;
533
+ });
534
+ for (const item of items) {
535
+ if (scannedCount < targetSkip) {
536
+ scannedCount++;
537
+ continue;
538
+ }
539
+ if (allItems.length >= targetLimit)
540
+ break;
541
+ allItems.push(item);
542
+ scannedCount++;
543
+ }
544
+ }
545
+ lastEvaluatedKey = result.LastEvaluatedKey;
546
+ } while (lastEvaluatedKey && allItems.length < targetLimit);
547
+ // Aplicar ordenamiento
548
+ if (queryOptions.order) {
549
+ allItems.sort((a, b) => {
550
+ const sortField = Object.keys(filters)[0] || schema.primary_key;
551
+ const aVal = a[sortField];
552
+ const bVal = b[sortField];
553
+ if (aVal < bVal)
554
+ return queryOptions.order === "ASC" ? -1 : 1;
555
+ if (aVal > bVal)
556
+ return queryOptions.order === "ASC" ? 1 : -1;
557
+ return 0;
558
+ });
559
+ }
560
+ // Convertir a instancias (schema ya definido arriba)
561
+ const instances = allItems.map((item) => {
562
+ if (queryOptions.attributes) {
563
+ // Proyección parcial: crear instancia con solo los atributos solicitados
564
+ const instance = Object.create(this.prototype);
565
+ instance[decorator_1.VALUES] = item;
566
+ // Definir getters para los atributos proyectados
567
+ for (const attr of queryOptions.attributes) {
568
+ const column = schema.columns[attr];
569
+ if (column) {
570
+ Object.defineProperty(instance, attr, {
571
+ enumerable: true,
572
+ configurable: true,
573
+ get: () => {
574
+ let value = instance[decorator_1.VALUES][attr] ?? null;
575
+ // Aplicar get pipeline
576
+ for (const fn of column.get) {
577
+ value = fn(value);
578
+ }
579
+ return value;
580
+ }
581
+ });
582
+ }
583
+ }
584
+ return instance;
585
+ }
586
+ else {
587
+ return new this(item);
588
+ }
589
+ });
590
+ // Procesar includes para cargar relaciones
591
+ if (queryOptions.include) {
592
+ await (0, relations_1.processIncludes)(instances, queryOptions.include, this);
593
+ }
594
+ return instances;
595
+ }
596
+ static async first(field_or_filters, operator_or_value, value_or_options) {
597
+ const results = await this.where(field_or_filters, operator_or_value, value_or_options, { limit: 1 });
598
+ return results[0];
599
+ }
600
+ static async last(field_or_filters, operator_or_value) {
601
+ if (field_or_filters === undefined) {
602
+ const results = await this.where({}, { order: 'DESC', limit: 1 });
603
+ return results[0];
604
+ }
605
+ else {
606
+ const results = await this.where(field_or_filters, operator_or_value, undefined, { order: 'DESC', limit: 1 });
607
+ return results[0];
608
+ }
609
+ }
610
+ static async withTrashed(filters, options) {
611
+ return await this.where(filters ?? {}, { ...options, _includeTrashed: true });
612
+ }
613
+ static async onlyTrashed(filters, options) {
614
+ const schema = this[decorator_1.SCHEMA];
615
+ let softDeleteColumn = null;
616
+ for (const column_name in schema.columns) {
617
+ const column = schema.columns[column_name];
618
+ if (column.store?.softDelete) {
619
+ softDeleteColumn = column_name;
620
+ break;
621
+ }
622
+ }
623
+ if (!softDeleteColumn) {
624
+ throw new Error("onlyTrashed() requiere un campo @SoftDelete configurado");
625
+ }
626
+ const merged_filters = {
627
+ ...filters,
628
+ [softDeleteColumn]: { "!=": null },
629
+ };
630
+ return await this.where(merged_filters, { ...options, _includeTrashed: true });
631
+ }
632
+ // **Métodos privados para DynamoDB**
633
+ async insertIntoDynamoDB(data) {
634
+ const client = (0, client_1.requireClient)();
635
+ const schema = this.constructor[decorator_1.SCHEMA];
636
+ await client.send(new client_dynamodb_1.PutItemCommand({
637
+ TableName: schema.name,
638
+ Item: (0, util_dynamodb_1.marshall)(data, { removeUndefinedValues: true }),
639
+ }));
640
+ }
641
+ async updateInDynamoDB(data) {
642
+ const client = (0, client_1.requireClient)();
643
+ const schema = this.constructor[decorator_1.SCHEMA];
644
+ await client.send(new client_dynamodb_1.PutItemCommand({
645
+ TableName: schema.name,
646
+ Item: (0, util_dynamodb_1.marshall)(data, { removeUndefinedValues: true }),
647
+ }));
648
+ }
649
+ async deleteFromDynamoDB(id) {
650
+ const client = (0, client_1.requireClient)();
651
+ const schema = this.constructor[decorator_1.SCHEMA];
652
+ await client.send(new client_dynamodb_1.DeleteItemCommand({
653
+ TableName: schema.name,
654
+ Key: (0, util_dynamodb_1.marshall)({ [schema.primary_key]: id }),
655
+ }));
656
+ }
657
+ }
658
+ exports.default = Table;
659
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @file indexes.ts
3
+ * @description Decoradores de índices con Symbol storage
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-28
6
+ */
7
+ /**
8
+ * @description Decorador para marcar propiedad como Partition Key
9
+ * @example
10
+ * ```typescript
11
+ * class User extends Table<User> {
12
+ * @Index() id: string;
13
+ * }
14
+ * ```
15
+ */
16
+ export declare const Index: (...params: any[]) => (target: any, propertyKey: string | symbol) => void;
17
+ /**
18
+ * @description Decorador para marcar propiedad como Sort Key
19
+ * @example
20
+ * ```typescript
21
+ * class Post extends Table<Post> {
22
+ * @Index() user_id: string;
23
+ * @IndexSort() created_at: string;
24
+ * }
25
+ * ```
26
+ */
27
+ export declare const IndexSort: (...params: any[]) => (target: any, propertyKey: string | symbol) => void;
28
+ /**
29
+ * @description Decorador para marcar una propiedad como clave primaria
30
+ * @example
31
+ * ```typescript
32
+ * class User extends Table<User> {
33
+ * @PrimaryKey() id: string;
34
+ * name: string;
35
+ * }
36
+ * ```
37
+ */
38
+ export declare function PrimaryKey(): PropertyDecorator;