@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.
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,300 @@
1
+ "use strict";
2
+ /**
3
+ * @file transaction-manager.ts
4
+ * @description Transaction simulation and rollback system for DynamoDB
5
+ * @author Miguel Alejandro
6
+ * @fecha 2025-08-31
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.TransactionManager = exports.TransactionError = void 0;
13
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
14
+ const util_dynamodb_1 = require("@aws-sdk/util-dynamodb");
15
+ const security_validator_1 = __importDefault(require("./security-validator"));
16
+ const throttle_manager_1 = __importDefault(require("./throttle-manager"));
17
+ class TransactionManager {
18
+ static { this.DEFAULT_CONFIG = {
19
+ maxOperationsPerTransaction: 25, // DynamoDB limit
20
+ snapshotTTL: 3600000, // 1 hour
21
+ enableOptimisticLocking: true,
22
+ maxRetries: 3,
23
+ }; }
24
+ constructor(client, config) {
25
+ this.activeTransactions = new Map();
26
+ this.snapshots = new Map();
27
+ this.client = client;
28
+ this.config = { ...TransactionManager.DEFAULT_CONFIG, ...config };
29
+ // Cleanup expired snapshots
30
+ setInterval(() => this.cleanupExpiredSnapshots(), 300000); // 5 minutes
31
+ }
32
+ /**
33
+ * Iniciar nueva transacción
34
+ */
35
+ async beginTransaction(operations) {
36
+ const transactionId = this.generateTransactionId();
37
+ // Validar operaciones
38
+ this.validateOperations(operations);
39
+ // Crear snapshot del estado actual para rollback
40
+ const snapshot = await this.createSnapshot(transactionId, operations);
41
+ this.activeTransactions.set(transactionId, snapshot);
42
+ this.snapshots.set(transactionId, snapshot);
43
+ return transactionId;
44
+ }
45
+ /**
46
+ * Ejecutar transacción usando DynamoDB TransactWrite
47
+ */
48
+ async commitTransaction(transactionId) {
49
+ const snapshot = this.activeTransactions.get(transactionId);
50
+ if (!snapshot) {
51
+ throw new TransactionError(`Transaction ${transactionId} not found`);
52
+ }
53
+ try {
54
+ snapshot.status = "pending";
55
+ // Agrupar operaciones en lotes de 25 (límite DynamoDB)
56
+ const batches = this.splitIntoBatches(snapshot.operations);
57
+ for (const batch of batches) {
58
+ await this.executeBatch(batch);
59
+ }
60
+ snapshot.status = "committed";
61
+ this.activeTransactions.delete(transactionId);
62
+ }
63
+ catch (error) {
64
+ snapshot.status = "failed";
65
+ // Intentar rollback automático
66
+ try {
67
+ await this.rollbackTransaction(transactionId);
68
+ }
69
+ catch (rollbackError) {
70
+ console.error("Rollback failed after commit error:", rollbackError);
71
+ }
72
+ throw new TransactionError(`Transaction commit failed: ${error.message}`);
73
+ }
74
+ }
75
+ /**
76
+ * Rollback manual de transacción
77
+ */
78
+ async rollbackTransaction(transactionId) {
79
+ const snapshot = this.snapshots.get(transactionId);
80
+ if (!snapshot) {
81
+ throw new TransactionError(`Transaction snapshot ${transactionId} not found`);
82
+ }
83
+ try {
84
+ // Crear operaciones inversas para rollback
85
+ const rollbackOps = this.createRollbackOperations(snapshot.operations);
86
+ if (rollbackOps.length > 0) {
87
+ const batches = this.splitIntoBatches(rollbackOps);
88
+ for (const batch of batches) {
89
+ await this.executeBatch(batch);
90
+ }
91
+ }
92
+ snapshot.status = "rolled_back";
93
+ this.activeTransactions.delete(transactionId);
94
+ }
95
+ catch (error) {
96
+ throw new TransactionError(`Rollback failed: ${error.message}`);
97
+ }
98
+ }
99
+ /**
100
+ * Crear snapshot del estado actual para rollback
101
+ */
102
+ async createSnapshot(transactionId, operations) {
103
+ const snapshot = {
104
+ id: transactionId,
105
+ operations: [],
106
+ timestamp: Date.now(),
107
+ status: "pending",
108
+ };
109
+ // Para cada operación, guardar el estado actual del item
110
+ for (const op of operations) {
111
+ try {
112
+ const currentItem = await this.getCurrentItemState(op.tableName, op.key);
113
+ const snapshotOp = {
114
+ ...op,
115
+ rollbackData: currentItem || undefined, // Estado actual para rollback
116
+ };
117
+ snapshot.operations.push(snapshotOp);
118
+ }
119
+ catch (error) {
120
+ if (error.name !== "ResourceNotFoundException") {
121
+ throw error;
122
+ }
123
+ // Item no existe, sin datos de rollback necesarios
124
+ snapshot.operations.push(op);
125
+ }
126
+ }
127
+ return snapshot;
128
+ }
129
+ /**
130
+ * Obtener estado actual de un item
131
+ */
132
+ async getCurrentItemState(tableName, key) {
133
+ const params = {
134
+ TransactItems: [
135
+ {
136
+ Get: {
137
+ TableName: tableName,
138
+ Key: (0, util_dynamodb_1.marshall)(key),
139
+ },
140
+ },
141
+ ],
142
+ };
143
+ const result = await throttle_manager_1.default.executeWithRetry(() => this.client.send(new client_dynamodb_1.TransactGetItemsCommand(params)), `GetItem-${tableName}`);
144
+ return result.Responses?.[0]?.Item
145
+ ? (0, util_dynamodb_1.unmarshall)(result.Responses[0].Item)
146
+ : null;
147
+ }
148
+ /**
149
+ * Crear operaciones inversas para rollback
150
+ */
151
+ createRollbackOperations(operations) {
152
+ const rollbackOps = [];
153
+ // Procesar en orden inverso
154
+ for (let i = operations.length - 1; i >= 0; i--) {
155
+ const op = operations[i];
156
+ switch (op.type) {
157
+ case "create":
158
+ // Para rollback de create: delete el item
159
+ rollbackOps.push({
160
+ type: "delete",
161
+ tableName: op.tableName,
162
+ key: op.key,
163
+ });
164
+ break;
165
+ case "update":
166
+ if (op.rollbackData) {
167
+ // Para rollback de update: restore estado anterior
168
+ rollbackOps.push({
169
+ type: "update",
170
+ tableName: op.tableName,
171
+ key: op.key,
172
+ item: op.rollbackData,
173
+ });
174
+ }
175
+ break;
176
+ case "delete":
177
+ if (op.rollbackData) {
178
+ // Para rollback de delete: recrear el item
179
+ rollbackOps.push({
180
+ type: "create",
181
+ tableName: op.tableName,
182
+ key: op.key,
183
+ item: op.rollbackData,
184
+ });
185
+ }
186
+ break;
187
+ }
188
+ }
189
+ return rollbackOps;
190
+ }
191
+ /**
192
+ * Ejecutar lote de operaciones usando TransactWrite
193
+ */
194
+ async executeBatch(operations) {
195
+ const transactItems = operations.map((op) => {
196
+ security_validator_1.default.validateItemSize(op.item || op.key);
197
+ switch (op.type) {
198
+ case "create":
199
+ case "update":
200
+ return {
201
+ Put: {
202
+ TableName: op.tableName,
203
+ Item: (0, util_dynamodb_1.marshall)(op.item, { removeUndefinedValues: true }),
204
+ ConditionExpression: op.conditionExpression,
205
+ },
206
+ };
207
+ case "delete":
208
+ return {
209
+ Delete: {
210
+ TableName: op.tableName,
211
+ Key: (0, util_dynamodb_1.marshall)(op.key),
212
+ ConditionExpression: op.conditionExpression,
213
+ },
214
+ };
215
+ default:
216
+ throw new TransactionError(`Unknown operation type: ${op.type}`);
217
+ }
218
+ });
219
+ const command = new client_dynamodb_1.TransactWriteItemsCommand({
220
+ TransactItems: transactItems,
221
+ });
222
+ await throttle_manager_1.default.executeWithRetry(() => this.client.send(command), "TransactWrite");
223
+ }
224
+ /**
225
+ * Dividir operaciones en lotes según límites DynamoDB
226
+ */
227
+ splitIntoBatches(operations) {
228
+ const batches = [];
229
+ const batchSize = this.config.maxOperationsPerTransaction;
230
+ for (let i = 0; i < operations.length; i += batchSize) {
231
+ batches.push(operations.slice(i, i + batchSize));
232
+ }
233
+ return batches;
234
+ }
235
+ /**
236
+ * Validar operaciones antes de transacción
237
+ */
238
+ validateOperations(operations) {
239
+ if (operations.length === 0) {
240
+ throw new TransactionError("Transaction must contain at least one operation");
241
+ }
242
+ if (operations.length > this.config.maxOperationsPerTransaction * 10) {
243
+ throw new TransactionError(`Too many operations: ${operations.length}`);
244
+ }
245
+ for (const op of operations) {
246
+ // Validar estructura de la operación
247
+ if (!op.tableName || !op.key) {
248
+ throw new TransactionError("Invalid operation: missing tableName or key");
249
+ }
250
+ // Validar datos de seguridad
251
+ security_validator_1.default.validateQueryFilters(op.key);
252
+ if (op.item) {
253
+ security_validator_1.default.validateValue(op.item);
254
+ }
255
+ }
256
+ }
257
+ /**
258
+ * Limpiar snapshots expirados
259
+ */
260
+ cleanupExpiredSnapshots() {
261
+ const now = Date.now();
262
+ const expired = [];
263
+ for (const [id, snapshot] of this.snapshots.entries()) {
264
+ if (now - snapshot.timestamp > this.config.snapshotTTL) {
265
+ expired.push(id);
266
+ }
267
+ }
268
+ expired.forEach((id) => {
269
+ this.snapshots.delete(id);
270
+ this.activeTransactions.delete(id);
271
+ });
272
+ if (expired.length > 0) {
273
+ console.log(`Cleaned up ${expired.length} expired transaction snapshots`);
274
+ }
275
+ }
276
+ /**
277
+ * Obtener estado de transacción
278
+ */
279
+ getTransactionStatus(transactionId) {
280
+ return this.snapshots.get(transactionId) || null;
281
+ }
282
+ /**
283
+ * Listar transacciones activas
284
+ */
285
+ getActiveTransactions() {
286
+ return Array.from(this.activeTransactions.values());
287
+ }
288
+ generateTransactionId() {
289
+ return `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
290
+ }
291
+ }
292
+ exports.TransactionManager = TransactionManager;
293
+ class TransactionError extends Error {
294
+ constructor(message) {
295
+ super(message);
296
+ this.name = "TransactionError";
297
+ }
298
+ }
299
+ exports.TransactionError = TransactionError;
300
+ //# sourceMappingURL=transaction-manager.js.map