@arcaelas/dynamite 1.0.14 → 1.0.17

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.
Files changed (53) hide show
  1. package/package.json +1 -1
  2. package/src/@types/index.d.ts +96 -0
  3. package/src/@types/index.js +9 -0
  4. package/src/core/client.d.ts +69 -0
  5. package/src/core/client.js +164 -0
  6. package/src/core/table.d.ts +98 -0
  7. package/src/core/table.js +459 -0
  8. package/src/core/wrapper.d.ts +17 -0
  9. package/src/core/wrapper.js +46 -0
  10. package/src/decorators/belongs_to.d.ts +1 -0
  11. package/src/decorators/belongs_to.js +24 -0
  12. package/src/decorators/created_at.d.ts +1 -0
  13. package/src/decorators/created_at.js +11 -0
  14. package/src/decorators/default.d.ts +1 -0
  15. package/src/decorators/default.js +47 -0
  16. package/src/decorators/has_many.d.ts +1 -0
  17. package/src/decorators/has_many.js +24 -0
  18. package/src/decorators/index.d.ts +11 -0
  19. package/src/decorators/index.js +36 -0
  20. package/src/decorators/index_sort.d.ts +12 -0
  21. package/src/decorators/index_sort.js +43 -0
  22. package/src/decorators/mutate.d.ts +2 -0
  23. package/src/decorators/mutate.js +51 -0
  24. package/src/decorators/name.d.ts +1 -0
  25. package/src/decorators/name.js +28 -0
  26. package/src/decorators/not_null.d.ts +1 -0
  27. package/src/decorators/not_null.js +13 -0
  28. package/src/decorators/primary_key.d.ts +6 -0
  29. package/src/decorators/primary_key.js +30 -0
  30. package/src/decorators/updated_at.d.ts +12 -0
  31. package/src/decorators/updated_at.js +26 -0
  32. package/src/decorators/validate.d.ts +1 -0
  33. package/src/decorators/validate.js +53 -0
  34. package/src/index.d.ts +22 -0
  35. package/src/index.js +47 -0
  36. package/src/utils/batch-relations.d.ts +14 -0
  37. package/src/utils/batch-relations.js +131 -0
  38. package/src/utils/circular-detector.d.ts +82 -0
  39. package/src/utils/circular-detector.js +212 -0
  40. package/src/utils/memory-manager.d.ts +42 -0
  41. package/src/utils/memory-manager.js +107 -0
  42. package/src/utils/naming.d.ts +8 -0
  43. package/src/utils/naming.js +18 -0
  44. package/src/utils/projection.d.ts +12 -0
  45. package/src/utils/projection.js +51 -0
  46. package/src/utils/relations.d.ts +17 -0
  47. package/src/utils/relations.js +165 -0
  48. package/src/utils/security-validator.d.ts +49 -0
  49. package/src/utils/security-validator.js +163 -0
  50. package/src/utils/throttle-manager.d.ts +78 -0
  51. package/src/utils/throttle-manager.js +201 -0
  52. package/src/utils/transaction-manager.d.ts +88 -0
  53. package/src/utils/transaction-manager.js +300 -0
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * @file circular-detector.ts
4
+ * @description Detection and prevention of circular references in relations
5
+ * @author Miguel Alejandro
6
+ * @fecha 2025-08-31
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.circularDetector = exports.CircularTracker = exports.CircularReferenceError = exports.CircularReferenceDetector = void 0;
10
+ class CircularReferenceDetector {
11
+ static { this.DEFAULT_CONFIG = {
12
+ maxDepth: 10,
13
+ maxIncludeDepth: 5,
14
+ trackingEnabled: true,
15
+ }; }
16
+ constructor(config) {
17
+ this.activePaths = new Set();
18
+ this.pathHistory = [];
19
+ this.config = { ...CircularReferenceDetector.DEFAULT_CONFIG, ...config };
20
+ }
21
+ /**
22
+ * Verificar si una ruta de inclusión es segura
23
+ */
24
+ validateIncludePath(modelName, includeOptions, currentDepth = 0, visitedModels = new Set()) {
25
+ // Verificar profundidad máxima
26
+ if (currentDepth > this.config.maxIncludeDepth) {
27
+ throw new CircularReferenceError(`Include depth exceeded limit of ${this.config.maxIncludeDepth} at model: ${modelName}`);
28
+ }
29
+ // Detectar referencia circular directa
30
+ if (visitedModels.has(modelName)) {
31
+ const path = Array.from(visitedModels).join(" -> ") + ` -> ${modelName}`;
32
+ throw new CircularReferenceError(`Circular reference detected in include path: ${path}`);
33
+ }
34
+ if (!includeOptions || typeof includeOptions !== "object") {
35
+ return;
36
+ }
37
+ const newVisited = new Set(visitedModels);
38
+ newVisited.add(modelName);
39
+ // Validar cada relación incluida
40
+ for (const [relationKey, relationOptions] of Object.entries(includeOptions)) {
41
+ if (this.config.trackingEnabled) {
42
+ this.pathHistory.push({
43
+ modelName,
44
+ relationKey,
45
+ depth: currentDepth,
46
+ });
47
+ }
48
+ // Si la relación tiene sus propios includes, validar recursivamente
49
+ if (relationOptions &&
50
+ typeof relationOptions === "object" &&
51
+ "include" in relationOptions) {
52
+ // Aquí necesitaríamos obtener el modelo target de la relación
53
+ // Por simplicidad, usamos el relationKey como modelo (esto debería mejorar)
54
+ const targetModelName = this.inferTargetModelName(relationKey);
55
+ this.validateIncludePath(targetModelName, relationOptions.include, currentDepth + 1, newVisited);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Validar estructura de relaciones en tiempo de definición
61
+ */
62
+ validateRelationStructure(models, maxAllowedCycles = 0) {
63
+ const graph = this.buildRelationGraph(models);
64
+ const cycles = this.detectCycles(graph);
65
+ return {
66
+ isValid: cycles.length <= maxAllowedCycles,
67
+ cycles,
68
+ suggestions: this.generateSuggestions(cycles),
69
+ };
70
+ }
71
+ /**
72
+ * Construir grafo de relaciones
73
+ */
74
+ buildRelationGraph(models) {
75
+ const graph = new Map();
76
+ for (const [modelName, model] of models.entries()) {
77
+ const relations = [];
78
+ // Esto necesitaría acceso a los metadatos del modelo
79
+ // Por ahora simulamos la extracción de relaciones
80
+ const meta = model.getMeta?.();
81
+ if (meta?.relations) {
82
+ for (const [relationKey, relation] of meta.relations.entries()) {
83
+ const targetModel = relation.targetModel?.()?.name || relationKey;
84
+ relations.push(targetModel);
85
+ }
86
+ }
87
+ graph.set(modelName, relations);
88
+ }
89
+ return graph;
90
+ }
91
+ /**
92
+ * Detectar ciclos usando DFS
93
+ */
94
+ detectCycles(graph) {
95
+ const cycles = [];
96
+ const visited = new Set();
97
+ const recursionStack = new Set();
98
+ const dfs = (node, path) => {
99
+ if (recursionStack.has(node)) {
100
+ // Ciclo detectado
101
+ const cycleStart = path.indexOf(node);
102
+ const cycle = path.slice(cycleStart).concat(node);
103
+ cycles.push(cycle);
104
+ return;
105
+ }
106
+ if (visited.has(node)) {
107
+ return;
108
+ }
109
+ visited.add(node);
110
+ recursionStack.add(node);
111
+ path.push(node);
112
+ const neighbors = graph.get(node) || [];
113
+ for (const neighbor of neighbors) {
114
+ dfs(neighbor, [...path]);
115
+ }
116
+ recursionStack.delete(node);
117
+ };
118
+ for (const node of graph.keys()) {
119
+ if (!visited.has(node)) {
120
+ dfs(node, []);
121
+ }
122
+ }
123
+ return cycles;
124
+ }
125
+ /**
126
+ * Generar sugerencias para resolver ciclos
127
+ */
128
+ generateSuggestions(cycles) {
129
+ const suggestions = [];
130
+ for (const cycle of cycles) {
131
+ if (cycle.length === 2) {
132
+ suggestions.push(`Circular reference between ${cycle[0]} and ${cycle[1]}. Consider using lazy loading or removing one direction.`);
133
+ }
134
+ else {
135
+ suggestions.push(`Complex circular reference detected: ${cycle.join(" -> ")}. Consider breaking the cycle at the weakest relationship.`);
136
+ }
137
+ }
138
+ if (cycles.length > 0) {
139
+ suggestions.push("General: Use @NonAttribute for computed relationships or implement lazy loading to break cycles.");
140
+ }
141
+ return suggestions;
142
+ }
143
+ /**
144
+ * Inferir nombre del modelo target (simplificado)
145
+ */
146
+ inferTargetModelName(relationKey) {
147
+ // Conversión simple: posts -> Post, user -> User
148
+ return (relationKey.charAt(0).toUpperCase() +
149
+ relationKey.slice(1).replace(/s$/, ""));
150
+ }
151
+ /**
152
+ * Crear contexto de rastreo para includes anidados
153
+ */
154
+ createTrackingContext() {
155
+ return new CircularTracker(this.config.maxIncludeDepth);
156
+ }
157
+ /**
158
+ * Obtener historial de paths para debugging
159
+ */
160
+ getPathHistory() {
161
+ return [...this.pathHistory];
162
+ }
163
+ /**
164
+ * Limpiar historial
165
+ */
166
+ clearHistory() {
167
+ this.pathHistory = [];
168
+ this.activePaths.clear();
169
+ }
170
+ }
171
+ exports.CircularReferenceDetector = CircularReferenceDetector;
172
+ /**
173
+ * Tracker específico para una operación de include
174
+ */
175
+ class CircularTracker {
176
+ constructor(maxDepth) {
177
+ this.visitedModels = new Set();
178
+ this.currentPath = [];
179
+ this.maxDepth = maxDepth;
180
+ }
181
+ enter(modelName) {
182
+ if (this.currentPath.length >= this.maxDepth) {
183
+ throw new CircularReferenceError(`Maximum include depth ${this.maxDepth} exceeded at: ${modelName}`);
184
+ }
185
+ if (this.visitedModels.has(modelName)) {
186
+ const cyclePath = this.currentPath.join(" -> ") + ` -> ${modelName}`;
187
+ throw new CircularReferenceError(`Circular reference detected: ${cyclePath}`);
188
+ }
189
+ this.visitedModels.add(modelName);
190
+ this.currentPath.push(modelName);
191
+ }
192
+ exit(modelName) {
193
+ this.currentPath.pop();
194
+ this.visitedModels.delete(modelName);
195
+ }
196
+ getCurrentPath() {
197
+ return [...this.currentPath];
198
+ }
199
+ }
200
+ exports.CircularTracker = CircularTracker;
201
+ class CircularReferenceError extends Error {
202
+ constructor(message) {
203
+ super(message);
204
+ this.name = "CircularReferenceError";
205
+ }
206
+ }
207
+ exports.CircularReferenceError = CircularReferenceError;
208
+ // Instancia singleton
209
+ const circularDetector = new CircularReferenceDetector();
210
+ exports.circularDetector = circularDetector;
211
+ exports.default = circularDetector;
212
+ //# sourceMappingURL=circular-detector.js.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @file memory-manager.ts
3
+ * @description Memory management and leak prevention
4
+ * @author Miguel Alejandro
5
+ * @fecha 2025-08-31
6
+ */
7
+ declare class MemoryManager {
8
+ private static instance;
9
+ private cleanupTasks;
10
+ private maxCacheSize;
11
+ private maxCacheAge;
12
+ static getInstance(): MemoryManager;
13
+ /**
14
+ * Registrar una tarea de limpieza
15
+ */
16
+ registerCleanup(id: string, cleanup: () => void, intervalMs?: number): void;
17
+ /**
18
+ * Desregistrar y limpiar tarea
19
+ */
20
+ unregisterCleanup(id: string): void;
21
+ /**
22
+ * Limpiar cache con límites de tamaño y edad
23
+ */
24
+ cleanCache<T>(cache: Map<string, {
25
+ data: T;
26
+ expires: number;
27
+ created: number;
28
+ }>, maxSize?: number): void;
29
+ /**
30
+ * Monitorear uso de memoria
31
+ */
32
+ getMemoryUsage(): NodeJS.MemoryUsage;
33
+ /**
34
+ * Forzar garbage collection si está disponible
35
+ */
36
+ forceGC(): void;
37
+ /**
38
+ * Cleanup completo al shutdown
39
+ */
40
+ shutdown(): void;
41
+ }
42
+ export default MemoryManager;
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * @file memory-manager.ts
4
+ * @description Memory management and leak prevention
5
+ * @author Miguel Alejandro
6
+ * @fecha 2025-08-31
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ class MemoryManager {
10
+ constructor() {
11
+ this.cleanupTasks = new Map();
12
+ this.maxCacheSize = 1000;
13
+ this.maxCacheAge = 300000; // 5 minutos
14
+ }
15
+ static getInstance() {
16
+ if (!MemoryManager.instance) {
17
+ MemoryManager.instance = new MemoryManager();
18
+ }
19
+ return MemoryManager.instance;
20
+ }
21
+ /**
22
+ * Registrar una tarea de limpieza
23
+ */
24
+ registerCleanup(id, cleanup, intervalMs) {
25
+ // Limpiar tarea previa si existe
26
+ this.unregisterCleanup(id);
27
+ const task = { id, cleanup };
28
+ if (intervalMs) {
29
+ task.interval = setInterval(() => {
30
+ try {
31
+ cleanup();
32
+ }
33
+ catch (error) {
34
+ console.warn(`Cleanup task ${id} failed:`, error);
35
+ }
36
+ }, intervalMs);
37
+ }
38
+ this.cleanupTasks.set(id, task);
39
+ }
40
+ /**
41
+ * Desregistrar y limpiar tarea
42
+ */
43
+ unregisterCleanup(id) {
44
+ const task = this.cleanupTasks.get(id);
45
+ if (task) {
46
+ if (task.interval) {
47
+ clearInterval(task.interval);
48
+ }
49
+ task.cleanup();
50
+ this.cleanupTasks.delete(id);
51
+ }
52
+ }
53
+ /**
54
+ * Limpiar cache con límites de tamaño y edad
55
+ */
56
+ cleanCache(cache, maxSize = this.maxCacheSize) {
57
+ const now = Date.now();
58
+ // Remover entradas expiradas
59
+ for (const [key, entry] of cache.entries()) {
60
+ if (entry.expires <= now || now - entry.created > this.maxCacheAge) {
61
+ cache.delete(key);
62
+ }
63
+ }
64
+ // Si aún excede el tamaño, remover las más antiguas
65
+ if (cache.size > maxSize) {
66
+ const entries = Array.from(cache.entries()).sort((a, b) => a[1].created - b[1].created);
67
+ const toRemove = entries.slice(0, cache.size - maxSize);
68
+ toRemove.forEach(([key]) => cache.delete(key));
69
+ }
70
+ }
71
+ /**
72
+ * Monitorear uso de memoria
73
+ */
74
+ getMemoryUsage() {
75
+ return process.memoryUsage();
76
+ }
77
+ /**
78
+ * Forzar garbage collection si está disponible
79
+ */
80
+ forceGC() {
81
+ if (global.gc) {
82
+ global.gc();
83
+ }
84
+ }
85
+ /**
86
+ * Cleanup completo al shutdown
87
+ */
88
+ shutdown() {
89
+ console.log("MemoryManager: Iniciando cleanup completo...");
90
+ for (const [id, task] of this.cleanupTasks.entries()) {
91
+ try {
92
+ this.unregisterCleanup(id);
93
+ }
94
+ catch (error) {
95
+ console.warn(`Error cleaning up task ${id}:`, error);
96
+ }
97
+ }
98
+ this.cleanupTasks.clear();
99
+ this.forceGC();
100
+ }
101
+ }
102
+ // Cleanup automático en shutdown
103
+ process.on("SIGTERM", () => MemoryManager.getInstance().shutdown());
104
+ process.on("SIGINT", () => MemoryManager.getInstance().shutdown());
105
+ process.on("uncaughtException", () => MemoryManager.getInstance().shutdown());
106
+ exports.default = MemoryManager;
107
+ //# sourceMappingURL=memory-manager.js.map
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @file naming.ts
3
+ * @descripcion Utilidades de conversión de nombres
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-27
6
+ */
7
+ /** Convierte nombre de clase a formato snake_case plural para tablas */
8
+ export declare function toSnakePlural(input: string): string;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ /**
3
+ * @file naming.ts
4
+ * @descripcion Utilidades de conversión de nombres
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-27
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.toSnakePlural = toSnakePlural;
13
+ const pluralize_1 = __importDefault(require("pluralize"));
14
+ /** Convierte nombre de clase a formato snake_case plural para tablas */
15
+ function toSnakePlural(input) {
16
+ return (0, pluralize_1.default)(input.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase());
17
+ }
18
+ //# sourceMappingURL=naming.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @file projection.ts
3
+ * @description Selective field loading and projection optimization
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-27
6
+ */
7
+ /** Build DynamoDB ProjectionExpression */
8
+ export declare const buildProjection: (attributes?: string[], meta?: any) => string | undefined;
9
+ /** Optimize attributes loading for relations */
10
+ export declare const optimizeRelationAttributes: (include: any, baseAttributes?: string[]) => string[];
11
+ /** Smart attribute selection for common patterns */
12
+ export declare const getSmartAttributes: (Model: any, scenario: "list" | "detail" | "minimal") => string[];
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * @file projection.ts
4
+ * @description Selective field loading and projection optimization
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-27
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getSmartAttributes = exports.optimizeRelationAttributes = exports.buildProjection = void 0;
10
+ const wrapper_1 = require("../core/wrapper");
11
+ /** Build DynamoDB ProjectionExpression */
12
+ const buildProjection = (attributes, meta) => {
13
+ if (!attributes?.length || !meta)
14
+ return undefined;
15
+ // Validate attributes exist in model
16
+ const validAttributes = attributes.filter((attr) => meta.columns.has(attr) || meta.relations.has(attr));
17
+ return validAttributes.length ? validAttributes.join(", ") : undefined;
18
+ };
19
+ exports.buildProjection = buildProjection;
20
+ /** Optimize attributes loading for relations */
21
+ const optimizeRelationAttributes = (include, baseAttributes) => {
22
+ const attributes = new Set(baseAttributes || []);
23
+ // Add foreign keys needed for relations
24
+ Object.entries(include || {}).forEach(([relationKey, options]) => {
25
+ attributes.add(relationKey);
26
+ // Add foreign key fields
27
+ if (options?.foreignKey)
28
+ attributes.add(options.foreignKey);
29
+ if (options?.localKey)
30
+ attributes.add(options.localKey);
31
+ });
32
+ return Array.from(attributes);
33
+ };
34
+ exports.optimizeRelationAttributes = optimizeRelationAttributes;
35
+ /** Smart attribute selection for common patterns */
36
+ const getSmartAttributes = (Model, scenario) => {
37
+ const meta = (0, wrapper_1.mustMeta)(Model);
38
+ const columns = Array.from(meta.columns.keys());
39
+ switch (scenario) {
40
+ case "minimal":
41
+ return columns.filter((col) => meta.columns.get(col)?.primaryKey ||
42
+ ["id", "name", "title", "email"].includes(col));
43
+ case "list":
44
+ return columns.filter((col) => !["description", "content", "body", "metadata"].includes(col));
45
+ case "detail":
46
+ default:
47
+ return columns;
48
+ }
49
+ };
50
+ exports.getSmartAttributes = getSmartAttributes;
51
+ //# sourceMappingURL=projection.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file relations.ts
3
+ * @descripcion Sistema de relaciones optimizado
4
+ * @autor Miguel Alejandro
5
+ * @fecha 2025-01-28
6
+ */
7
+ /** Decorador @hasMany para relaciones 1:N */
8
+ export declare const hasMany: (targetModel: () => any, foreignKey: string, localKey?: string) => (target: any, propertyKey: string) => void;
9
+ /** Decorador @belongsTo para relaciones N:1 */
10
+ export declare const belongsTo: (targetModel: () => any, localKey: string, foreignKey?: string) => (target: any, propertyKey: string) => void;
11
+ /** Procesamiento optimizado de includes con batch loading transparente */
12
+ export declare const processIncludes: (Model: any, items: any[], include: Record<string, any>, depth?: number) => Promise<any[]>;
13
+ /** Separar opciones de query e include */
14
+ export declare const separateQueryOptions: (options: Record<string, any>) => {
15
+ queryOptions: Record<string, any>;
16
+ includeOptions: Record<string, any> | undefined;
17
+ };
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * @file relations.ts
4
+ * @descripcion Sistema de relaciones optimizado
5
+ * @autor Miguel Alejandro
6
+ * @fecha 2025-01-28
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.separateQueryOptions = exports.processIncludes = exports.belongsTo = exports.hasMany = void 0;
10
+ const wrapper_1 = require("../core/wrapper");
11
+ const naming_1 = require("./naming");
12
+ // =============================================================================
13
+ // DECORATORS
14
+ // =============================================================================
15
+ /** Decorador @hasMany para relaciones 1:N */
16
+ const hasMany = (targetModel, foreignKey, localKey = "id") => (target, propertyKey) => {
17
+ (0, wrapper_1.ensureConfig)(target.constructor, (0, naming_1.toSnakePlural)(target.constructor.name)).relations.set(propertyKey, {
18
+ type: "hasMany",
19
+ targetModel,
20
+ foreignKey,
21
+ localKey,
22
+ });
23
+ Object.defineProperty(target, propertyKey, {
24
+ get() {
25
+ const cached = this[`_${propertyKey}`];
26
+ return cached !== undefined
27
+ ? cached
28
+ : (console.warn(`Relación ${propertyKey} no cargada. Use includes en la consulta.`),
29
+ []);
30
+ },
31
+ configurable: true,
32
+ enumerable: false,
33
+ });
34
+ };
35
+ exports.hasMany = hasMany;
36
+ /** Decorador @belongsTo para relaciones N:1 */
37
+ const belongsTo = (targetModel, localKey, foreignKey = "id") => (target, propertyKey) => {
38
+ (0, wrapper_1.ensureConfig)(target.constructor, (0, naming_1.toSnakePlural)(target.constructor.name)).relations.set(propertyKey, {
39
+ type: "belongsTo",
40
+ targetModel,
41
+ localKey,
42
+ foreignKey,
43
+ });
44
+ Object.defineProperty(target, propertyKey, {
45
+ get() {
46
+ const cached = this[`_${propertyKey}`];
47
+ return cached !== undefined
48
+ ? cached
49
+ : (console.warn(`Relación ${propertyKey} no cargada. Use includes en la consulta.`),
50
+ null);
51
+ },
52
+ configurable: true,
53
+ enumerable: false,
54
+ });
55
+ };
56
+ exports.belongsTo = belongsTo;
57
+ // =============================================================================
58
+ // FUNCTIONS
59
+ // =============================================================================
60
+ /** Batch loading para relaciones hasMany */
61
+ const batchLoadHasMany = async (Model, items, relation, options = {}) => {
62
+ const { targetModel, foreignKey, localKey = "id" } = relation;
63
+ const parent_keys = items.map((item) => item[localKey]).filter(Boolean);
64
+ if (!parent_keys.length)
65
+ return new Map();
66
+ // Build query with relation options
67
+ let query = targetModel().where(foreignKey, "in", parent_keys);
68
+ // Apply additional filters if specified
69
+ if (options.where) {
70
+ const additionalFilters = Object.entries(options.where);
71
+ for (const [key, value] of additionalFilters) {
72
+ const currentResults = await query;
73
+ query = Promise.resolve(currentResults.filter((item) => item[key] === value));
74
+ }
75
+ }
76
+ const related_items = await query;
77
+ // TODO: Apply attributes selection
78
+ // For now, skip attributes filtering to ensure basic relations work
79
+ let processedItems = related_items;
80
+ // Apply other options like limit, order
81
+ let filteredItems = processedItems;
82
+ if (options.order === "DESC") {
83
+ filteredItems.sort((a, b) => b.id.localeCompare(a.id));
84
+ }
85
+ else if (options.order === "ASC") {
86
+ filteredItems.sort((a, b) => a.id.localeCompare(b.id));
87
+ }
88
+ if (options.limit) {
89
+ filteredItems = filteredItems.slice(0, options.limit);
90
+ }
91
+ const grouped = new Map();
92
+ filteredItems.forEach((item) => {
93
+ const key = item[foreignKey];
94
+ grouped.has(key) ? grouped.get(key).push(item) : grouped.set(key, [item]);
95
+ });
96
+ return grouped;
97
+ };
98
+ /** Batch loading para relaciones belongsTo */
99
+ const batchLoadBelongsTo = async (Model, items, relation, options = {}) => {
100
+ const { targetModel, localKey, foreignKey = "id" } = relation;
101
+ // Para BelongsTo: obtener valores de localKey (ej: category_id) de los items
102
+ const keys = items
103
+ .map((item) => (localKey ? item[localKey] : null))
104
+ .filter(Boolean);
105
+ if (!keys.length)
106
+ return new Map();
107
+ // Buscar en targetModel donde foreignKey (ej: id) esté en los keys
108
+ const fetched_items = await targetModel().where(foreignKey, "in", keys);
109
+ // TODO: Apply attributes selection
110
+ // For now, skip attributes filtering to ensure basic relations work
111
+ let processedItems = fetched_items;
112
+ const results = new Map();
113
+ // Mapear por foreignKey para que coincida con localKey de los items
114
+ processedItems.forEach((item) => results.set(item[foreignKey], item));
115
+ return results;
116
+ };
117
+ /** Procesamiento optimizado de includes con batch loading transparente */
118
+ const processIncludes = async (Model, items, include, depth = 0) => {
119
+ if (!include || depth > 10 || !items.length)
120
+ return items;
121
+ const meta = (0, wrapper_1.mustMeta)(Model);
122
+ const relation_promises = Object.entries(include).map(async ([relation_key, relation_options]) => {
123
+ const relation = meta.relations.get(relation_key);
124
+ if (!relation)
125
+ return;
126
+ const related_data = relation.type === "hasMany"
127
+ ? await batchLoadHasMany(Model, items, relation, relation_options)
128
+ : await batchLoadBelongsTo(Model, items, relation, relation_options);
129
+ items.forEach((item) => {
130
+ let key;
131
+ if (relation.type === "hasMany") {
132
+ // Para HasMany: usar localKey (ej: "id") del item actual
133
+ key = item[relation.localKey || "id"];
134
+ }
135
+ else {
136
+ // Para BelongsTo: usar localKey (ej: "category_id") del item actual
137
+ key = relation.localKey ? item[relation.localKey] : null;
138
+ }
139
+ const related = related_data.get(key);
140
+ // Usar una propiedad temporal para evitar conflictos con getters
141
+ Object.defineProperty(item, relation_key, {
142
+ value: relation.type === "hasMany" ? related || [] : related || null,
143
+ writable: true,
144
+ enumerable: true,
145
+ configurable: true,
146
+ });
147
+ });
148
+ if (relation_options?.include && related_data.size) {
149
+ const all_related = Array.from(related_data.values())
150
+ .flat()
151
+ .filter(Boolean);
152
+ await (0, exports.processIncludes)(relation.targetModel(), all_related, relation_options.include, depth + 1);
153
+ }
154
+ });
155
+ await Promise.all(relation_promises);
156
+ return items;
157
+ };
158
+ exports.processIncludes = processIncludes;
159
+ /** Separar opciones de query e include */
160
+ const separateQueryOptions = (options) => {
161
+ const { include, ...queryOptions } = options;
162
+ return { queryOptions, includeOptions: include };
163
+ };
164
+ exports.separateQueryOptions = separateQueryOptions;
165
+ //# sourceMappingURL=relations.js.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @file security-validator.ts
3
+ * @description Security validation and NoSQL injection prevention
4
+ * @author Miguel Alejandro
5
+ * @fecha 2025-08-31
6
+ */
7
+ import { QueryOperator } from "../@types/index";
8
+ interface SecurityConfig {
9
+ maxStringLength: number;
10
+ maxArrayLength: number;
11
+ maxNestedDepth: number;
12
+ allowedOperators: QueryOperator[];
13
+ blockedPatterns: RegExp[];
14
+ }
15
+ declare class SecurityValidator {
16
+ private static readonly DEFAULT_CONFIG;
17
+ private config;
18
+ constructor(config?: Partial<SecurityConfig>);
19
+ /**
20
+ * Validar nombre de atributo/tabla
21
+ */
22
+ validateAttributeName(name: string): void;
23
+ /**
24
+ * Validar operador de query
25
+ */
26
+ validateOperator(operator: string): QueryOperator;
27
+ /**
28
+ * Validar valor de campo
29
+ */
30
+ validateValue(value: any, depth?: number): any;
31
+ /**
32
+ * Validar filtros de query completos
33
+ */
34
+ validateQueryFilters(filters: Record<string, any>): Record<string, any>;
35
+ /**
36
+ * Sanitizar string para DynamoDB
37
+ */
38
+ sanitizeString(input: string): string;
39
+ /**
40
+ * Validar tamaño de item para DynamoDB (límite 400KB)
41
+ */
42
+ validateItemSize(item: any): void;
43
+ }
44
+ declare class SecurityError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ declare const securityValidator: SecurityValidator;
48
+ export { SecurityError, SecurityValidator, securityValidator };
49
+ export default securityValidator;