@arcaelas/dynamite 1.0.15 → 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.
- package/package.json +1 -1
- package/src/@types/index.d.ts +96 -0
- package/src/@types/index.js +9 -0
- package/src/core/client.d.ts +69 -0
- package/src/core/client.js +164 -0
- 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/src/decorators/created_at.js +11 -0
- package/src/decorators/default.d.ts +1 -0
- package/src/decorators/default.js +47 -0
- package/src/decorators/has_many.d.ts +1 -0
- package/src/decorators/has_many.js +24 -0
- package/src/decorators/index.d.ts +11 -0
- 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/src/decorators/mutate.js +51 -0
- 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/src/decorators/not_null.js +13 -0
- 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/src/decorators/validate.js +53 -0
- package/src/index.d.ts +22 -0
- package/src/index.js +47 -0
- package/src/utils/batch-relations.d.ts +14 -0
- package/src/utils/batch-relations.js +131 -0
- package/src/utils/circular-detector.d.ts +82 -0
- package/src/utils/circular-detector.js +212 -0
- package/src/utils/memory-manager.d.ts +42 -0
- package/src/utils/memory-manager.js +107 -0
- package/src/utils/naming.d.ts +8 -0
- package/src/utils/naming.js +18 -0
- package/src/utils/projection.d.ts +12 -0
- package/src/utils/projection.js +51 -0
- package/src/utils/relations.d.ts +17 -0
- package/src/utils/relations.js +165 -0
- package/src/utils/security-validator.d.ts +49 -0
- package/src/utils/security-validator.js +163 -0
- package/src/utils/throttle-manager.d.ts +78 -0
- package/src/utils/throttle-manager.js +201 -0
- package/src/utils/transaction-manager.d.ts +88 -0
- package/src/utils/transaction-manager.js +300 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file security-validator.ts
|
|
4
|
+
* @description Security validation and NoSQL injection prevention
|
|
5
|
+
* @author Miguel Alejandro
|
|
6
|
+
* @fecha 2025-08-31
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.securityValidator = exports.SecurityValidator = exports.SecurityError = void 0;
|
|
10
|
+
class SecurityValidator {
|
|
11
|
+
static { this.DEFAULT_CONFIG = {
|
|
12
|
+
maxStringLength: 10000,
|
|
13
|
+
maxArrayLength: 1000,
|
|
14
|
+
maxNestedDepth: 10,
|
|
15
|
+
allowedOperators: [
|
|
16
|
+
"=",
|
|
17
|
+
"!=",
|
|
18
|
+
"<",
|
|
19
|
+
"<=",
|
|
20
|
+
">",
|
|
21
|
+
">=",
|
|
22
|
+
"in",
|
|
23
|
+
"not-in",
|
|
24
|
+
"contains",
|
|
25
|
+
"begins-with",
|
|
26
|
+
],
|
|
27
|
+
blockedPatterns: [
|
|
28
|
+
/\$\w+/g, // MongoDB operators
|
|
29
|
+
/javascript:/gi, // JavaScript injection
|
|
30
|
+
/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, // Script tags
|
|
31
|
+
/eval\s*\(/gi, // eval() calls
|
|
32
|
+
/function\s*\(/gi, // function declarations
|
|
33
|
+
/\{\s*\$\w+/g, // NoSQL operators
|
|
34
|
+
/\.\.\//g, // Path traversal
|
|
35
|
+
/union\s+select/gi, // SQL injection patterns
|
|
36
|
+
/drop\s+table/gi, // Destructive SQL
|
|
37
|
+
],
|
|
38
|
+
}; }
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = { ...SecurityValidator.DEFAULT_CONFIG, ...config };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validar nombre de atributo/tabla
|
|
44
|
+
*/
|
|
45
|
+
validateAttributeName(name) {
|
|
46
|
+
if (!name || typeof name !== "string") {
|
|
47
|
+
throw new SecurityError("Nombre de atributo inválido");
|
|
48
|
+
}
|
|
49
|
+
if (name.length > 255) {
|
|
50
|
+
throw new SecurityError("Nombre de atributo muy largo");
|
|
51
|
+
}
|
|
52
|
+
// Solo permitir caracteres alfanuméricos, guiones y guiones bajos
|
|
53
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
54
|
+
throw new SecurityError("Nombre de atributo contiene caracteres inválidos");
|
|
55
|
+
}
|
|
56
|
+
// Verificar patrones bloqueados
|
|
57
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
58
|
+
if (pattern.test(name)) {
|
|
59
|
+
throw new SecurityError("Nombre de atributo contiene patrones peligrosos");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validar operador de query
|
|
65
|
+
*/
|
|
66
|
+
validateOperator(operator) {
|
|
67
|
+
if (!this.config.allowedOperators.includes(operator)) {
|
|
68
|
+
throw new SecurityError(`Operador no permitido: ${operator}`);
|
|
69
|
+
}
|
|
70
|
+
return operator;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validar valor de campo
|
|
74
|
+
*/
|
|
75
|
+
validateValue(value, depth = 0) {
|
|
76
|
+
if (depth > this.config.maxNestedDepth) {
|
|
77
|
+
throw new SecurityError("Estructura de datos anidada muy profunda");
|
|
78
|
+
}
|
|
79
|
+
// Valores null/undefined son válidos
|
|
80
|
+
if (value === null || value === undefined) {
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
// Validar strings
|
|
84
|
+
if (typeof value === "string") {
|
|
85
|
+
if (value.length > this.config.maxStringLength) {
|
|
86
|
+
throw new SecurityError("String muy largo");
|
|
87
|
+
}
|
|
88
|
+
for (const pattern of this.config.blockedPatterns) {
|
|
89
|
+
if (pattern.test(value)) {
|
|
90
|
+
throw new SecurityError("Valor contiene patrones peligrosos");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
// Validar arrays
|
|
96
|
+
if (Array.isArray(value)) {
|
|
97
|
+
if (value.length > this.config.maxArrayLength) {
|
|
98
|
+
throw new SecurityError("Array muy largo");
|
|
99
|
+
}
|
|
100
|
+
return value.map((item) => this.validateValue(item, depth + 1));
|
|
101
|
+
}
|
|
102
|
+
// Validar objetos
|
|
103
|
+
if (typeof value === "object") {
|
|
104
|
+
const validated = {};
|
|
105
|
+
for (const [key, val] of Object.entries(value)) {
|
|
106
|
+
this.validateAttributeName(key);
|
|
107
|
+
validated[key] = this.validateValue(val, depth + 1);
|
|
108
|
+
}
|
|
109
|
+
return validated;
|
|
110
|
+
}
|
|
111
|
+
// Números, booleans son válidos
|
|
112
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
throw new SecurityError(`Tipo de valor no permitido: ${typeof value}`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Validar filtros de query completos
|
|
119
|
+
*/
|
|
120
|
+
validateQueryFilters(filters) {
|
|
121
|
+
const validated = {};
|
|
122
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
123
|
+
this.validateAttributeName(key);
|
|
124
|
+
validated[key] = this.validateValue(value);
|
|
125
|
+
}
|
|
126
|
+
return validated;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Sanitizar string para DynamoDB
|
|
130
|
+
*/
|
|
131
|
+
sanitizeString(input) {
|
|
132
|
+
if (typeof input !== "string") {
|
|
133
|
+
return String(input);
|
|
134
|
+
}
|
|
135
|
+
return input
|
|
136
|
+
.replace(/[\x00-\x1F\x7F]/g, "") // Remover caracteres de control
|
|
137
|
+
.trim()
|
|
138
|
+
.slice(0, this.config.maxStringLength);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Validar tamaño de item para DynamoDB (límite 400KB)
|
|
142
|
+
*/
|
|
143
|
+
validateItemSize(item) {
|
|
144
|
+
const size = JSON.stringify(item).length;
|
|
145
|
+
const maxSize = 400 * 1024; // 400KB en bytes
|
|
146
|
+
if (size > maxSize) {
|
|
147
|
+
throw new SecurityError(`Item muy grande: ${size} bytes (máximo: ${maxSize} bytes)`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
exports.SecurityValidator = SecurityValidator;
|
|
152
|
+
class SecurityError extends Error {
|
|
153
|
+
constructor(message) {
|
|
154
|
+
super(message);
|
|
155
|
+
this.name = "SecurityError";
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
exports.SecurityError = SecurityError;
|
|
159
|
+
// Instancia singleton
|
|
160
|
+
const securityValidator = new SecurityValidator();
|
|
161
|
+
exports.securityValidator = securityValidator;
|
|
162
|
+
exports.default = securityValidator;
|
|
163
|
+
//# sourceMappingURL=security-validator.js.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file throttle-manager.ts
|
|
3
|
+
* @description AWS DynamoDB throttling and retry management
|
|
4
|
+
* @author Miguel Alejandro
|
|
5
|
+
* @fecha 2025-08-31
|
|
6
|
+
*/
|
|
7
|
+
interface RetryConfig {
|
|
8
|
+
maxRetries: number;
|
|
9
|
+
baseDelay: number;
|
|
10
|
+
maxDelay: number;
|
|
11
|
+
backoffMultiplier: number;
|
|
12
|
+
jitterFactor: number;
|
|
13
|
+
}
|
|
14
|
+
interface ThrottleStats {
|
|
15
|
+
totalRequests: number;
|
|
16
|
+
throttledRequests: number;
|
|
17
|
+
retriedRequests: number;
|
|
18
|
+
averageLatency: number;
|
|
19
|
+
}
|
|
20
|
+
declare class ThrottleManager {
|
|
21
|
+
private static readonly DEFAULT_CONFIG;
|
|
22
|
+
private config;
|
|
23
|
+
private stats;
|
|
24
|
+
private requestQueue;
|
|
25
|
+
private isProcessingQueue;
|
|
26
|
+
private concurrentRequests;
|
|
27
|
+
private maxConcurrentRequests;
|
|
28
|
+
constructor(config?: Partial<RetryConfig>);
|
|
29
|
+
/**
|
|
30
|
+
* Ejecutar operación con retry automático
|
|
31
|
+
*/
|
|
32
|
+
executeWithRetry<T>(operation: () => Promise<T>, operationName?: string): Promise<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Verificar si el error es reintentable
|
|
35
|
+
*/
|
|
36
|
+
private isRetryableError;
|
|
37
|
+
/**
|
|
38
|
+
* Calcular delay con exponential backoff + jitter
|
|
39
|
+
*/
|
|
40
|
+
private calculateDelay;
|
|
41
|
+
/**
|
|
42
|
+
* Controlar concurrencia de requests
|
|
43
|
+
*/
|
|
44
|
+
private acquireConcurrencySlot;
|
|
45
|
+
private releaseConcurrencySlot;
|
|
46
|
+
/**
|
|
47
|
+
* Queue para operaciones batch con rate limiting
|
|
48
|
+
*/
|
|
49
|
+
queueOperation<T>(operation: () => Promise<T>): Promise<T>;
|
|
50
|
+
private processQueue;
|
|
51
|
+
/**
|
|
52
|
+
* Obtener estadísticas de throttling
|
|
53
|
+
*/
|
|
54
|
+
getStats(): ThrottleStats;
|
|
55
|
+
/**
|
|
56
|
+
* Resetear estadísticas
|
|
57
|
+
*/
|
|
58
|
+
resetStats(): void;
|
|
59
|
+
private updateLatencyStats;
|
|
60
|
+
private sleep;
|
|
61
|
+
/**
|
|
62
|
+
* Configurar límites de concurrencia dinámicamente
|
|
63
|
+
*/
|
|
64
|
+
setConcurrencyLimit(limit: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Obtener métricas de salud del throttling
|
|
67
|
+
*/
|
|
68
|
+
getHealthMetrics(): {
|
|
69
|
+
throttleRate: number;
|
|
70
|
+
retryRate: number;
|
|
71
|
+
avgLatency: number;
|
|
72
|
+
concurrentRequests: number;
|
|
73
|
+
queueSize: number;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
declare const throttleManager: ThrottleManager;
|
|
77
|
+
export { RetryConfig, ThrottleManager, ThrottleStats, throttleManager };
|
|
78
|
+
export default throttleManager;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @file throttle-manager.ts
|
|
4
|
+
* @description AWS DynamoDB throttling and retry management
|
|
5
|
+
* @author Miguel Alejandro
|
|
6
|
+
* @fecha 2025-08-31
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.throttleManager = exports.ThrottleManager = void 0;
|
|
10
|
+
class ThrottleManager {
|
|
11
|
+
static { this.DEFAULT_CONFIG = {
|
|
12
|
+
maxRetries: 10,
|
|
13
|
+
baseDelay: 100, // 100ms base delay
|
|
14
|
+
maxDelay: 30000, // 30s max delay
|
|
15
|
+
backoffMultiplier: 2, // Exponential backoff
|
|
16
|
+
jitterFactor: 0.1, // 10% jitter
|
|
17
|
+
}; }
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.requestQueue = [];
|
|
20
|
+
this.isProcessingQueue = false;
|
|
21
|
+
this.concurrentRequests = 0;
|
|
22
|
+
this.maxConcurrentRequests = 25; // DynamoDB default
|
|
23
|
+
this.config = { ...ThrottleManager.DEFAULT_CONFIG, ...config };
|
|
24
|
+
this.stats = {
|
|
25
|
+
totalRequests: 0,
|
|
26
|
+
throttledRequests: 0,
|
|
27
|
+
retriedRequests: 0,
|
|
28
|
+
averageLatency: 0,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ejecutar operación con retry automático
|
|
33
|
+
*/
|
|
34
|
+
async executeWithRetry(operation, operationName = "DynamoDB Operation") {
|
|
35
|
+
const startTime = Date.now();
|
|
36
|
+
let lastError = null;
|
|
37
|
+
this.stats.totalRequests++;
|
|
38
|
+
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
39
|
+
try {
|
|
40
|
+
// Control de concurrencia
|
|
41
|
+
await this.acquireConcurrencySlot();
|
|
42
|
+
const result = await operation();
|
|
43
|
+
this.releaseConcurrencySlot();
|
|
44
|
+
this.updateLatencyStats(Date.now() - startTime);
|
|
45
|
+
if (attempt > 0) {
|
|
46
|
+
console.log(`✅ ${operationName} succeeded after ${attempt} retries`);
|
|
47
|
+
}
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.releaseConcurrencySlot();
|
|
52
|
+
lastError = error;
|
|
53
|
+
// Verificar si es un error que podemos reintentar
|
|
54
|
+
if (!this.isRetryableError(error)) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
this.stats.throttledRequests++;
|
|
58
|
+
if (attempt === this.config.maxRetries) {
|
|
59
|
+
console.error(`❌ ${operationName} failed after ${this.config.maxRetries} retries:`, error.message);
|
|
60
|
+
throw new Error(`Max retries exceeded for ${operationName}: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
this.stats.retriedRequests++;
|
|
63
|
+
const delay = this.calculateDelay(attempt);
|
|
64
|
+
console.warn(`⚠️ ${operationName} throttled, retrying in ${delay}ms (attempt ${attempt + 1}/${this.config.maxRetries})`);
|
|
65
|
+
await this.sleep(delay);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
throw lastError;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verificar si el error es reintentable
|
|
72
|
+
*/
|
|
73
|
+
isRetryableError(error) {
|
|
74
|
+
if (!error.name)
|
|
75
|
+
return false;
|
|
76
|
+
const retryableErrors = [
|
|
77
|
+
"ProvisionedThroughputExceededException",
|
|
78
|
+
"ThrottlingException",
|
|
79
|
+
"RequestLimitExceeded",
|
|
80
|
+
"ServiceUnavailableException",
|
|
81
|
+
"InternalServerError",
|
|
82
|
+
"NetworkingError",
|
|
83
|
+
"TimeoutError",
|
|
84
|
+
];
|
|
85
|
+
return retryableErrors.includes(error.name) || error.retryable === true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Calcular delay con exponential backoff + jitter
|
|
89
|
+
*/
|
|
90
|
+
calculateDelay(attempt) {
|
|
91
|
+
const exponentialDelay = this.config.baseDelay * Math.pow(this.config.backoffMultiplier, attempt);
|
|
92
|
+
const cappedDelay = Math.min(exponentialDelay, this.config.maxDelay);
|
|
93
|
+
// Agregar jitter para evitar "thundering herd"
|
|
94
|
+
const jitter = cappedDelay * this.config.jitterFactor * Math.random();
|
|
95
|
+
return Math.floor(cappedDelay + jitter);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Controlar concurrencia de requests
|
|
99
|
+
*/
|
|
100
|
+
async acquireConcurrencySlot() {
|
|
101
|
+
while (this.concurrentRequests >= this.maxConcurrentRequests) {
|
|
102
|
+
await this.sleep(10); // Esperar 10ms
|
|
103
|
+
}
|
|
104
|
+
this.concurrentRequests++;
|
|
105
|
+
}
|
|
106
|
+
releaseConcurrencySlot() {
|
|
107
|
+
this.concurrentRequests = Math.max(0, this.concurrentRequests - 1);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Queue para operaciones batch con rate limiting
|
|
111
|
+
*/
|
|
112
|
+
async queueOperation(operation) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
this.requestQueue.push(async () => {
|
|
115
|
+
try {
|
|
116
|
+
const result = await this.executeWithRetry(operation);
|
|
117
|
+
resolve(result);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
reject(error);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
this.processQueue();
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async processQueue() {
|
|
127
|
+
if (this.isProcessingQueue || this.requestQueue.length === 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.isProcessingQueue = true;
|
|
131
|
+
while (this.requestQueue.length > 0) {
|
|
132
|
+
const operation = this.requestQueue.shift();
|
|
133
|
+
try {
|
|
134
|
+
await operation();
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
console.error("Queue operation failed:", error);
|
|
138
|
+
}
|
|
139
|
+
// Rate limiting: pequeña pausa entre operaciones
|
|
140
|
+
if (this.requestQueue.length > 0) {
|
|
141
|
+
await this.sleep(50); // 50ms entre requests
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
this.isProcessingQueue = false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Obtener estadísticas de throttling
|
|
148
|
+
*/
|
|
149
|
+
getStats() {
|
|
150
|
+
return { ...this.stats };
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Resetear estadísticas
|
|
154
|
+
*/
|
|
155
|
+
resetStats() {
|
|
156
|
+
this.stats = {
|
|
157
|
+
totalRequests: 0,
|
|
158
|
+
throttledRequests: 0,
|
|
159
|
+
retriedRequests: 0,
|
|
160
|
+
averageLatency: 0,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
updateLatencyStats(latency) {
|
|
164
|
+
const totalRequests = this.stats.totalRequests;
|
|
165
|
+
this.stats.averageLatency =
|
|
166
|
+
(this.stats.averageLatency * (totalRequests - 1) + latency) /
|
|
167
|
+
totalRequests;
|
|
168
|
+
}
|
|
169
|
+
sleep(ms) {
|
|
170
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Configurar límites de concurrencia dinámicamente
|
|
174
|
+
*/
|
|
175
|
+
setConcurrencyLimit(limit) {
|
|
176
|
+
this.maxConcurrentRequests = Math.max(1, Math.min(limit, 100));
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Obtener métricas de salud del throttling
|
|
180
|
+
*/
|
|
181
|
+
getHealthMetrics() {
|
|
182
|
+
const stats = this.getStats();
|
|
183
|
+
return {
|
|
184
|
+
throttleRate: stats.totalRequests > 0
|
|
185
|
+
? stats.throttledRequests / stats.totalRequests
|
|
186
|
+
: 0,
|
|
187
|
+
retryRate: stats.totalRequests > 0
|
|
188
|
+
? stats.retriedRequests / stats.totalRequests
|
|
189
|
+
: 0,
|
|
190
|
+
avgLatency: stats.averageLatency,
|
|
191
|
+
concurrentRequests: this.concurrentRequests,
|
|
192
|
+
queueSize: this.requestQueue.length,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.ThrottleManager = ThrottleManager;
|
|
197
|
+
// Instancia singleton
|
|
198
|
+
const throttleManager = new ThrottleManager();
|
|
199
|
+
exports.throttleManager = throttleManager;
|
|
200
|
+
exports.default = throttleManager;
|
|
201
|
+
//# sourceMappingURL=throttle-manager.js.map
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file transaction-manager.ts
|
|
3
|
+
* @description Transaction simulation and rollback system for DynamoDB
|
|
4
|
+
* @author Miguel Alejandro
|
|
5
|
+
* @fecha 2025-08-31
|
|
6
|
+
*/
|
|
7
|
+
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
8
|
+
interface TransactionOperation {
|
|
9
|
+
type: "create" | "update" | "delete";
|
|
10
|
+
tableName: string;
|
|
11
|
+
key: Record<string, any>;
|
|
12
|
+
item?: Record<string, any>;
|
|
13
|
+
conditionExpression?: string;
|
|
14
|
+
rollbackData?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
interface TransactionSnapshot {
|
|
17
|
+
id: string;
|
|
18
|
+
operations: TransactionOperation[];
|
|
19
|
+
timestamp: number;
|
|
20
|
+
status: "pending" | "committed" | "rolled_back" | "failed";
|
|
21
|
+
}
|
|
22
|
+
interface TransactionConfig {
|
|
23
|
+
maxOperationsPerTransaction: number;
|
|
24
|
+
snapshotTTL: number;
|
|
25
|
+
enableOptimisticLocking: boolean;
|
|
26
|
+
maxRetries: number;
|
|
27
|
+
}
|
|
28
|
+
declare class TransactionManager {
|
|
29
|
+
private static readonly DEFAULT_CONFIG;
|
|
30
|
+
private client;
|
|
31
|
+
private config;
|
|
32
|
+
private activeTransactions;
|
|
33
|
+
private snapshots;
|
|
34
|
+
constructor(client: DynamoDBClient, config?: Partial<TransactionConfig>);
|
|
35
|
+
/**
|
|
36
|
+
* Iniciar nueva transacción
|
|
37
|
+
*/
|
|
38
|
+
beginTransaction(operations: TransactionOperation[]): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Ejecutar transacción usando DynamoDB TransactWrite
|
|
41
|
+
*/
|
|
42
|
+
commitTransaction(transactionId: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Rollback manual de transacción
|
|
45
|
+
*/
|
|
46
|
+
rollbackTransaction(transactionId: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Crear snapshot del estado actual para rollback
|
|
49
|
+
*/
|
|
50
|
+
private createSnapshot;
|
|
51
|
+
/**
|
|
52
|
+
* Obtener estado actual de un item
|
|
53
|
+
*/
|
|
54
|
+
private getCurrentItemState;
|
|
55
|
+
/**
|
|
56
|
+
* Crear operaciones inversas para rollback
|
|
57
|
+
*/
|
|
58
|
+
private createRollbackOperations;
|
|
59
|
+
/**
|
|
60
|
+
* Ejecutar lote de operaciones usando TransactWrite
|
|
61
|
+
*/
|
|
62
|
+
private executeBatch;
|
|
63
|
+
/**
|
|
64
|
+
* Dividir operaciones en lotes según límites DynamoDB
|
|
65
|
+
*/
|
|
66
|
+
private splitIntoBatches;
|
|
67
|
+
/**
|
|
68
|
+
* Validar operaciones antes de transacción
|
|
69
|
+
*/
|
|
70
|
+
private validateOperations;
|
|
71
|
+
/**
|
|
72
|
+
* Limpiar snapshots expirados
|
|
73
|
+
*/
|
|
74
|
+
private cleanupExpiredSnapshots;
|
|
75
|
+
/**
|
|
76
|
+
* Obtener estado de transacción
|
|
77
|
+
*/
|
|
78
|
+
getTransactionStatus(transactionId: string): TransactionSnapshot | null;
|
|
79
|
+
/**
|
|
80
|
+
* Listar transacciones activas
|
|
81
|
+
*/
|
|
82
|
+
getActiveTransactions(): TransactionSnapshot[];
|
|
83
|
+
private generateTransactionId;
|
|
84
|
+
}
|
|
85
|
+
declare class TransactionError extends Error {
|
|
86
|
+
constructor(message: string);
|
|
87
|
+
}
|
|
88
|
+
export { TransactionConfig, TransactionError, TransactionManager, TransactionOperation, TransactionSnapshot, };
|