@arcaelas/dynamite 1.0.17 → 1.0.19

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 (67) hide show
  1. package/package.json +40 -2
  2. package/src/@types/index.d.ts +116 -75
  3. package/src/core/client.d.ts +36 -0
  4. package/src/core/client.js +80 -27
  5. package/src/core/decorator.d.ts +44 -0
  6. package/src/core/decorator.js +133 -0
  7. package/src/core/method.d.ts +73 -0
  8. package/src/core/method.js +140 -0
  9. package/src/core/table.d.ts +44 -86
  10. package/src/core/table.js +510 -310
  11. package/src/decorators/indexes.d.ts +38 -0
  12. package/src/decorators/indexes.js +67 -0
  13. package/src/decorators/relations.d.ts +55 -0
  14. package/src/decorators/relations.js +84 -0
  15. package/src/decorators/timestamps.d.ts +54 -0
  16. package/src/decorators/timestamps.js +67 -0
  17. package/src/decorators/transforms.d.ts +86 -0
  18. package/src/decorators/transforms.js +154 -0
  19. package/src/index.d.ts +10 -16
  20. package/src/index.js +50 -32
  21. package/src/index.test.d.ts +13 -0
  22. package/src/index.test.js +1992 -0
  23. package/src/utils/relations.d.ts +34 -12
  24. package/src/utils/relations.js +109 -133
  25. package/src/@types/index.js +0 -9
  26. package/src/core/wrapper.d.ts +0 -17
  27. package/src/core/wrapper.js +0 -46
  28. package/src/decorators/belongs_to.d.ts +0 -1
  29. package/src/decorators/belongs_to.js +0 -24
  30. package/src/decorators/created_at.d.ts +0 -1
  31. package/src/decorators/created_at.js +0 -11
  32. package/src/decorators/default.d.ts +0 -1
  33. package/src/decorators/default.js +0 -47
  34. package/src/decorators/has_many.d.ts +0 -1
  35. package/src/decorators/has_many.js +0 -24
  36. package/src/decorators/index.d.ts +0 -11
  37. package/src/decorators/index.js +0 -36
  38. package/src/decorators/index_sort.d.ts +0 -12
  39. package/src/decorators/index_sort.js +0 -43
  40. package/src/decorators/mutate.d.ts +0 -2
  41. package/src/decorators/mutate.js +0 -51
  42. package/src/decorators/name.d.ts +0 -1
  43. package/src/decorators/name.js +0 -28
  44. package/src/decorators/not_null.d.ts +0 -1
  45. package/src/decorators/not_null.js +0 -13
  46. package/src/decorators/primary_key.d.ts +0 -6
  47. package/src/decorators/primary_key.js +0 -30
  48. package/src/decorators/updated_at.d.ts +0 -12
  49. package/src/decorators/updated_at.js +0 -26
  50. package/src/decorators/validate.d.ts +0 -1
  51. package/src/decorators/validate.js +0 -53
  52. package/src/utils/batch-relations.d.ts +0 -14
  53. package/src/utils/batch-relations.js +0 -131
  54. package/src/utils/circular-detector.d.ts +0 -82
  55. package/src/utils/circular-detector.js +0 -212
  56. package/src/utils/memory-manager.d.ts +0 -42
  57. package/src/utils/memory-manager.js +0 -107
  58. package/src/utils/naming.d.ts +0 -8
  59. package/src/utils/naming.js +0 -18
  60. package/src/utils/projection.d.ts +0 -12
  61. package/src/utils/projection.js +0 -51
  62. package/src/utils/security-validator.d.ts +0 -49
  63. package/src/utils/security-validator.js +0 -163
  64. package/src/utils/throttle-manager.d.ts +0 -78
  65. package/src/utils/throttle-manager.js +0 -201
  66. package/src/utils/transaction-manager.d.ts +0 -88
  67. package/src/utils/transaction-manager.js +0 -300
@@ -1,51 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = Mutate;
4
- const wrapper_1 = require("../core/wrapper");
5
- const naming_1 = require("../utils/naming");
6
- function Mutate(fn) {
7
- if (typeof fn !== "function")
8
- throw new TypeError("@Mutate requiere función");
9
- return (target, prop) => {
10
- const ctor = target.constructor;
11
- const entry = (0, wrapper_1.ensureConfig)(ctor, (0, naming_1.toSnakePlural)(ctor.name));
12
- const col = (0, wrapper_1.ensureColumn)(entry, prop, String(prop));
13
- col.mutate ??= [];
14
- col.mutate.push(fn);
15
- !Object.getOwnPropertyDescriptor(ctor.prototype, prop)?.set &&
16
- defineVirtual(ctor.prototype, col, prop);
17
- };
18
- }
19
- /** Define propiedad virtual con getter/setter para manejo de mutaciones */
20
- function defineVirtual(proto, col, prop) {
21
- Object.defineProperty(proto, prop, {
22
- get() {
23
- return (this[wrapper_1.STORE] ?? {})[prop];
24
- },
25
- set(val) {
26
- const buf = (this[wrapper_1.STORE] ??= {});
27
- val =
28
- val === undefined && col.default !== undefined
29
- ? typeof col.default === "function"
30
- ? col.default()
31
- : col.default
32
- : val;
33
- if (col.mutate)
34
- for (const m of col.mutate)
35
- val = m(val);
36
- if (col.validate) {
37
- for (const v of col.validate) {
38
- const r = v(val);
39
- r !== true &&
40
- (() => {
41
- throw new Error(typeof r === "string" ? r : "Validación fallida");
42
- })();
43
- }
44
- }
45
- buf[prop] = val;
46
- },
47
- enumerable: true,
48
- configurable: true,
49
- });
50
- }
51
- //# sourceMappingURL=mutate.js.map
@@ -1 +0,0 @@
1
- export default function Name(label: string): ClassDecorator & PropertyDecorator;
@@ -1,28 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = Name;
4
- const wrapper_1 = require("../core/wrapper");
5
- const naming_1 = require("../utils/naming");
6
- function Name(label) {
7
- if (!label || typeof label !== "string")
8
- throw new TypeError("@Name requiere una cadena no vacía");
9
- return (target, prop) => {
10
- const ctor = prop === undefined ? target : target.constructor;
11
- const entry = (0, wrapper_1.ensureConfig)(ctor, (0, naming_1.toSnakePlural)(ctor.name));
12
- if (prop === undefined) {
13
- const auto = (0, naming_1.toSnakePlural)(ctor.name);
14
- if (entry.name !== auto && entry.name !== label && entry.name) {
15
- throw new Error(`La clase ${ctor.name} ya tiene un @Name distinto (${entry.name})`);
16
- }
17
- entry.name = label;
18
- }
19
- else {
20
- const col = (0, wrapper_1.ensureColumn)(entry, prop, label);
21
- if (col.name && col.name !== label) {
22
- throw new Error(`La columna '${String(prop)}' ya tiene @Name distinto (${col.name})`);
23
- }
24
- col.name = label;
25
- }
26
- };
27
- }
28
- //# sourceMappingURL=name.js.map
@@ -1 +0,0 @@
1
- export default function NotNull(): PropertyDecorator;
@@ -1,13 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = NotNull;
7
- const validate_1 = __importDefault(require("./validate"));
8
- function NotNull() {
9
- return validate_1.default((value) => value !== null &&
10
- value !== undefined &&
11
- (typeof value !== "string" || value.trim() !== ""));
12
- }
13
- //# sourceMappingURL=not_null.js.map
@@ -1,6 +0,0 @@
1
- /**
2
- * Decorador para marcar una propiedad como clave primaria.
3
- * Aplica automáticamente @Index y @IndexSort y marca el campo como primaryKey en los metadatos.
4
- * @param name - Nombre opcional para el índice (no utilizado actualmente, mantenido por compatibilidad)
5
- */
6
- export default function PrimaryKey(name?: string): PropertyDecorator;
@@ -1,30 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = PrimaryKey;
7
- const wrapper_1 = require("../core/wrapper");
8
- const naming_1 = require("../utils/naming");
9
- const index_1 = __importDefault(require("./index"));
10
- const index_sort_1 = __importDefault(require("./index_sort"));
11
- /**
12
- * Decorador para marcar una propiedad como clave primaria.
13
- * Aplica automáticamente @Index y @IndexSort y marca el campo como primaryKey en los metadatos.
14
- * @param name - Nombre opcional para el índice (no utilizado actualmente, mantenido por compatibilidad)
15
- */
16
- function PrimaryKey(name = "primary") {
17
- return (target, prop) => {
18
- // Aplicar decoradores de índice
19
- (0, index_1.default)()(target, prop);
20
- (0, index_sort_1.default)()(target, prop);
21
- // Marcar explícitamente como clave primaria en los metadatos
22
- const ctor = target.constructor;
23
- const entry = (0, wrapper_1.ensureConfig)(ctor, (0, naming_1.toSnakePlural)(ctor.name));
24
- const column = (0, wrapper_1.ensureColumn)(entry, prop, String(prop));
25
- // Establecer metadatos adicionales para clave primaria
26
- column.primaryKey = true;
27
- column.nullable = false; // Las claves primarias no pueden ser nulas
28
- };
29
- }
30
- //# sourceMappingURL=primary_key.js.map
@@ -1,12 +0,0 @@
1
- /**
2
- * Decorador que marca una propiedad para que se actualice automáticamente con la fecha/hora actual
3
- * cada vez que se guarde el modelo.
4
- *
5
- * @example
6
- * class User extends Table<User> {
7
- * @PrimaryKey() id: string;
8
- * name: string;
9
- * @UpdatedAt() updated_at: string;
10
- * }
11
- */
12
- export default function UpdatedAt(): (target: any, propertyKey: string | symbol) => void;
@@ -1,26 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = UpdatedAt;
4
- const wrapper_1 = require("../core/wrapper");
5
- /**
6
- * Decorador que marca una propiedad para que se actualice automáticamente con la fecha/hora actual
7
- * cada vez que se guarde el modelo.
8
- *
9
- * @example
10
- * class User extends Table<User> {
11
- * @PrimaryKey() id: string;
12
- * name: string;
13
- * @UpdatedAt() updated_at: string;
14
- * }
15
- */
16
- function UpdatedAt() {
17
- return function (target, propertyKey) {
18
- const ctor = target.constructor;
19
- const entry = (0, wrapper_1.ensureConfig)(ctor, ctor.name);
20
- const column = (0, wrapper_1.ensureColumn)(entry, propertyKey, propertyKey);
21
- // Marcamos la columna como updatedAt para que se actualice automáticamente al guardar
22
- column.updatedAt = true;
23
- // No establecemos un valor por defecto aquí, se establecerá en el método save()
24
- };
25
- }
26
- //# sourceMappingURL=updated_at.js.map
@@ -1 +0,0 @@
1
- export default function Validate(validators: ((v: unknown) => true | string) | ((v: unknown) => true | string)[]): PropertyDecorator;
@@ -1,53 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = Validate;
4
- const wrapper_1 = require("../core/wrapper");
5
- const naming_1 = require("../utils/naming");
6
- function Validate(validators) {
7
- const list = Array.isArray(validators) ? validators : [validators];
8
- if (!list.length || list.some((v) => typeof v !== "function")) {
9
- throw new TypeError("@Validate requiere funciones");
10
- }
11
- return (target, prop) => {
12
- const ctor = target.constructor;
13
- const entry = (0, wrapper_1.ensureConfig)(ctor, (0, naming_1.toSnakePlural)(ctor.name));
14
- const col = (0, wrapper_1.ensureColumn)(entry, prop, String(prop));
15
- col.validate ??= [];
16
- col.validate.push(...list);
17
- !Object.getOwnPropertyDescriptor(ctor.prototype, prop)?.set &&
18
- defineVirtual(ctor.prototype, col, prop);
19
- };
20
- }
21
- /** Define propiedad virtual con getter/setter para manejo de validaciones */
22
- function defineVirtual(proto, col, prop) {
23
- Object.defineProperty(proto, prop, {
24
- get() {
25
- return (this[wrapper_1.STORE] ?? {})[prop];
26
- },
27
- set(val) {
28
- const buf = (this[wrapper_1.STORE] ??= {});
29
- val =
30
- val === undefined && col.default !== undefined
31
- ? typeof col.default === "function"
32
- ? col.default()
33
- : col.default
34
- : val;
35
- if (col.mutate)
36
- for (const m of col.mutate)
37
- val = m(val);
38
- if (col.validate) {
39
- for (const v of col.validate) {
40
- const r = v(val);
41
- r !== true &&
42
- (() => {
43
- throw new Error(typeof r === "string" ? r : "Validación fallida");
44
- })();
45
- }
46
- }
47
- buf[prop] = val;
48
- },
49
- enumerable: true,
50
- configurable: true,
51
- });
52
- }
53
- //# sourceMappingURL=validate.js.map
@@ -1,14 +0,0 @@
1
- /**
2
- * @file batch-relations.ts
3
- * @description Optimized batch loading for relations
4
- * @autor Miguel Alejandro
5
- * @fecha 2025-01-27
6
- */
7
- /** Batch loader para hasMany relations */
8
- export declare const batchLoadHasMany: (Model: any, parentItems: any[], relation: any) => Promise<Map<any, any>>;
9
- /** Batch loader para belongsTo relations con cache */
10
- export declare const batchLoadBelongsTo: (Model: any, parentItems: any[], relation: any) => Promise<Map<any, any>>;
11
- /** Optimized processIncludes con batch loading */
12
- export declare const processIncludesBatch: (Model: any, items: any[], include: any, depth?: number) => Promise<any[]>;
13
- /** Clear expired cache entries */
14
- export declare const cleanupRelationCache: () => void;
@@ -1,131 +0,0 @@
1
- "use strict";
2
- /**
3
- * @file batch-relations.ts
4
- * @description Optimized batch loading for relations
5
- * @autor Miguel Alejandro
6
- * @fecha 2025-01-27
7
- */
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.cleanupRelationCache = exports.processIncludesBatch = exports.batchLoadBelongsTo = exports.batchLoadHasMany = void 0;
10
- const wrapper_1 = require("../core/wrapper");
11
- /** Cache multi-nivel con TTL */
12
- const relCache = new Map();
13
- /** Batch loader para hasMany relations */
14
- const batchLoadHasMany = async (Model, parentItems, relation) => {
15
- const { targetModel, foreignKey, localKey } = relation;
16
- const TargetModel = targetModel();
17
- // Agrupar claves para batch query
18
- const parentKeys = parentItems.map((item) => item[localKey]).filter(Boolean);
19
- if (!parentKeys.length)
20
- return new Map();
21
- // Single query con 'in' operator
22
- const relatedItems = await TargetModel.where({
23
- [foreignKey]: { in: parentKeys },
24
- });
25
- // Group by foreign key
26
- const grouped = new Map();
27
- relatedItems.forEach((item) => {
28
- const key = item[foreignKey];
29
- if (!grouped.has(key))
30
- grouped.set(key, []);
31
- grouped.get(key).push(item);
32
- });
33
- return grouped;
34
- };
35
- exports.batchLoadHasMany = batchLoadHasMany;
36
- /** Batch loader para belongsTo relations con cache */
37
- const batchLoadBelongsTo = async (Model, parentItems, relation) => {
38
- const { targetModel, localKey, foreignKey } = relation;
39
- const TargetModel = targetModel();
40
- const cacheKey = `${TargetModel.name}_belongsTo`;
41
- // Verificar cache existente
42
- const cache = relCache.get(cacheKey) || new Map();
43
- relCache.set(cacheKey, cache);
44
- const now = Date.now();
45
- // Separar keys en cache vs no-cache
46
- const keysToFetch = [];
47
- const cachedResults = new Map();
48
- parentItems.forEach((item) => {
49
- const key = item[localKey];
50
- if (!key)
51
- return;
52
- const cached = cache.get(key);
53
- if (cached && cached.expires > now) {
54
- cachedResults.set(key, cached.data);
55
- }
56
- else {
57
- keysToFetch.push(key);
58
- }
59
- });
60
- // Fetch missing items
61
- if (keysToFetch.length) {
62
- const fetchedItems = await TargetModel.where({
63
- [foreignKey]: { in: keysToFetch },
64
- });
65
- // Update cache (5min TTL)
66
- const expires = now + 300000;
67
- fetchedItems.forEach((item) => {
68
- const key = item[foreignKey];
69
- cache.set(key, { data: item, expires });
70
- cachedResults.set(key, item);
71
- });
72
- }
73
- return cachedResults;
74
- };
75
- exports.batchLoadBelongsTo = batchLoadBelongsTo;
76
- /** Optimized processIncludes con batch loading */
77
- const processIncludesBatch = async (Model, items, include, depth = 0) => {
78
- if (!include || depth > 10 || !items.length)
79
- return items;
80
- const meta = (0, wrapper_1.mustMeta)(Model);
81
- // Process all relations in parallel
82
- const relationPromises = Object.entries(include).map(async ([relationKey, relationOptions]) => {
83
- const relation = meta.relations.get(relationKey);
84
- if (!relation)
85
- return;
86
- let relatedData;
87
- // Use batch loading based on relation type
88
- if (relation.type === "hasMany") {
89
- relatedData = await (0, exports.batchLoadHasMany)(Model, items, relation);
90
- }
91
- else {
92
- relatedData = await (0, exports.batchLoadBelongsTo)(Model, items, relation);
93
- }
94
- // Assign relations to items
95
- items.forEach((item) => {
96
- const key = item[relation.localKey];
97
- const related = relatedData.get(key);
98
- if (relation.type === "hasMany") {
99
- item[relationKey] = related || [];
100
- }
101
- else {
102
- item[relationKey] = related || null;
103
- }
104
- });
105
- // Recursive processing for nested includes
106
- if (relationOptions?.include && relatedData.size) {
107
- const allRelated = Array.from(relatedData.values()).flat();
108
- const TargetModel = relation.targetModel();
109
- await (0, exports.processIncludesBatch)(TargetModel, allRelated, relationOptions.include, depth + 1);
110
- }
111
- });
112
- await Promise.all(relationPromises);
113
- return items;
114
- };
115
- exports.processIncludesBatch = processIncludesBatch;
116
- /** Clear expired cache entries */
117
- const cleanupRelationCache = () => {
118
- const now = Date.now();
119
- relCache.forEach((cache, modelKey) => {
120
- cache.forEach((entry, key) => {
121
- if (entry.expires <= now)
122
- cache.delete(key);
123
- });
124
- if (cache.size === 0)
125
- relCache.delete(modelKey);
126
- });
127
- };
128
- exports.cleanupRelationCache = cleanupRelationCache;
129
- // Auto cleanup every 5 minutes
130
- setInterval(exports.cleanupRelationCache, 300000);
131
- //# sourceMappingURL=batch-relations.js.map
@@ -1,82 +0,0 @@
1
- /**
2
- * @file circular-detector.ts
3
- * @description Detection and prevention of circular references in relations
4
- * @author Miguel Alejandro
5
- * @fecha 2025-08-31
6
- */
7
- interface TraversalPath {
8
- modelName: string;
9
- relationKey: string;
10
- depth: number;
11
- }
12
- interface CircularDetectionConfig {
13
- maxDepth: number;
14
- maxIncludeDepth: number;
15
- trackingEnabled: boolean;
16
- }
17
- declare class CircularReferenceDetector {
18
- private static readonly DEFAULT_CONFIG;
19
- private config;
20
- private activePaths;
21
- private pathHistory;
22
- constructor(config?: Partial<CircularDetectionConfig>);
23
- /**
24
- * Verificar si una ruta de inclusión es segura
25
- */
26
- validateIncludePath(modelName: string, includeOptions: any, currentDepth?: number, visitedModels?: Set<string>): void;
27
- /**
28
- * Validar estructura de relaciones en tiempo de definición
29
- */
30
- validateRelationStructure(models: Map<string, any>, maxAllowedCycles?: number): ValidationResult;
31
- /**
32
- * Construir grafo de relaciones
33
- */
34
- private buildRelationGraph;
35
- /**
36
- * Detectar ciclos usando DFS
37
- */
38
- private detectCycles;
39
- /**
40
- * Generar sugerencias para resolver ciclos
41
- */
42
- private generateSuggestions;
43
- /**
44
- * Inferir nombre del modelo target (simplificado)
45
- */
46
- private inferTargetModelName;
47
- /**
48
- * Crear contexto de rastreo para includes anidados
49
- */
50
- createTrackingContext(): CircularTracker;
51
- /**
52
- * Obtener historial de paths para debugging
53
- */
54
- getPathHistory(): TraversalPath[];
55
- /**
56
- * Limpiar historial
57
- */
58
- clearHistory(): void;
59
- }
60
- /**
61
- * Tracker específico para una operación de include
62
- */
63
- declare class CircularTracker {
64
- private visitedModels;
65
- private currentPath;
66
- private maxDepth;
67
- constructor(maxDepth: number);
68
- enter(modelName: string): void;
69
- exit(modelName: string): void;
70
- getCurrentPath(): string[];
71
- }
72
- interface ValidationResult {
73
- isValid: boolean;
74
- cycles: string[][];
75
- suggestions: string[];
76
- }
77
- declare class CircularReferenceError extends Error {
78
- constructor(message: string);
79
- }
80
- declare const circularDetector: CircularReferenceDetector;
81
- export { CircularReferenceDetector, CircularReferenceError, CircularTracker, ValidationResult, circularDetector, };
82
- export default circularDetector;
@@ -1,212 +0,0 @@
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