@gugananuvem/aws-local-simulator 1.0.27 → 1.0.28
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gugananuvem/aws-local-simulator",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
4
4
|
"description": "Simulador local completo para serviços AWS",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -66,6 +66,6 @@
|
|
|
66
66
|
"publishConfig": {
|
|
67
67
|
"directory": "dist"
|
|
68
68
|
},
|
|
69
|
-
"buildDate": "2026-04-
|
|
69
|
+
"buildDate": "2026-04-30T14:28:21.971Z",
|
|
70
70
|
"published": true
|
|
71
71
|
}
|
|
@@ -21,6 +21,20 @@ class DynamoDBSimulator {
|
|
|
21
21
|
this.store = new LocalStore(this.dataDir);
|
|
22
22
|
this.tables = new Map();
|
|
23
23
|
this.audit = new CloudTrailAudit("dynamodb.amazonaws.com");
|
|
24
|
+
// Mutex por tabela para evitar race condition em escritas concorrentes
|
|
25
|
+
this._writeLocks = new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Executa fn com exclusão mútua por tableName.
|
|
30
|
+
* Garante que escritas na mesma tabela não se sobreponham.
|
|
31
|
+
*/
|
|
32
|
+
_withTableLock(tableName, fn) {
|
|
33
|
+
const prev = this._writeLocks.get(tableName) || Promise.resolve();
|
|
34
|
+
const next = prev.then(() => fn());
|
|
35
|
+
// Guarda apenas a tail da cadeia (sem acumular referências)
|
|
36
|
+
this._writeLocks.set(tableName, next.catch(() => {}));
|
|
37
|
+
return next;
|
|
24
38
|
}
|
|
25
39
|
async initialize() {
|
|
26
40
|
logger.debug("Inicializando DynamoDB Simulator...");
|
|
@@ -179,10 +193,10 @@ class DynamoDBSimulator {
|
|
|
179
193
|
case "DescribeTable": return this.describeTable(params.TableName);
|
|
180
194
|
case "ListTables": return this.listTables(params);
|
|
181
195
|
case "DeleteTable": return this.deleteTable(params);
|
|
182
|
-
case "PutItem": return this.putItem(params);
|
|
196
|
+
case "PutItem": return this._withTableLock(params.TableName, () => this.putItem(params));
|
|
183
197
|
case "GetItem": return this.getItem(params);
|
|
184
|
-
case "UpdateItem": return this.updateItem(params);
|
|
185
|
-
case "DeleteItem": return this.deleteItem(params);
|
|
198
|
+
case "UpdateItem": return this._withTableLock(params.TableName, () => this.updateItem(params));
|
|
199
|
+
case "DeleteItem": return this._withTableLock(params.TableName, () => this.deleteItem(params));
|
|
186
200
|
case "BatchWriteItem": return this.batchWriteItem(params);
|
|
187
201
|
case "BatchGetItem": return this.batchGetItem(params);
|
|
188
202
|
case "Query": return this.query(params);
|
|
@@ -446,48 +460,67 @@ class DynamoDBSimulator {
|
|
|
446
460
|
throw new Error(`RequestItems is required for BatchWriteItem. Params received: ${JSON.stringify(params)}`);
|
|
447
461
|
}
|
|
448
462
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
if (op.PutRequest) {
|
|
458
|
-
const item = this.normalizeItem(op.PutRequest.Item, table);
|
|
459
|
-
const itemKey = this.getItemKey(item, table);
|
|
460
|
-
const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
|
|
463
|
+
// Serializa por tabela usando o mutex para evitar race condition
|
|
464
|
+
const tablePromises = Object.entries(RequestItems).map(([tableName, operations]) =>
|
|
465
|
+
this._withTableLock(tableName, () => {
|
|
466
|
+
const table = this.tables.get(tableName);
|
|
467
|
+
if (!table) {
|
|
468
|
+
logger.info(`[BATCH-DEBUG] tabela não encontrada: ${tableName}`);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
461
471
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
items.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
472
|
+
const itemsBefore = this.store.read(tableName);
|
|
473
|
+
logger.info(`[BATCH-DEBUG] ${tableName} | antes=${itemsBefore.length} | ops=${operations.length}`);
|
|
474
|
+
|
|
475
|
+
let items = [...itemsBefore];
|
|
476
|
+
const unprocessedItems = [];
|
|
477
|
+
let inserts = 0;
|
|
478
|
+
let updates = 0;
|
|
479
|
+
|
|
480
|
+
for (const op of operations) {
|
|
481
|
+
if (op.PutRequest) {
|
|
482
|
+
const item = this.normalizeItem(op.PutRequest.Item, table);
|
|
483
|
+
const itemKey = this.getItemKey(item, table);
|
|
484
|
+
const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
|
|
485
|
+
|
|
486
|
+
if (index !== -1) {
|
|
487
|
+
items[index] = item;
|
|
488
|
+
updates++;
|
|
489
|
+
} else {
|
|
490
|
+
items.push(item);
|
|
491
|
+
table.itemCount++;
|
|
492
|
+
inserts++;
|
|
493
|
+
}
|
|
494
|
+
} else if (op.DeleteRequest) {
|
|
495
|
+
const key = op.DeleteRequest.Key;
|
|
496
|
+
const itemKey = this.getItemKeyFromKeys(key, table);
|
|
497
|
+
const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
|
|
498
|
+
|
|
499
|
+
if (index !== -1) {
|
|
500
|
+
items.splice(index, 1);
|
|
501
|
+
table.itemCount--;
|
|
502
|
+
} else {
|
|
503
|
+
unprocessedItems.push(op);
|
|
504
|
+
}
|
|
478
505
|
}
|
|
479
506
|
}
|
|
480
|
-
}
|
|
481
507
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
}
|
|
508
|
+
this.store.write(tableName, items);
|
|
509
|
+
const itemsAfter = this.store.read(tableName);
|
|
510
|
+
logger.info(`[BATCH-DEBUG] ${tableName} | depois=${itemsAfter.length} | esperado=${items.length} | match=${itemsAfter.length === items.length} | inserts=${inserts} | updates=${updates}`);
|
|
487
511
|
|
|
488
|
-
|
|
512
|
+
if (unprocessedItems.length > 0) {
|
|
513
|
+
responses[tableName] = unprocessedItems;
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
);
|
|
489
517
|
|
|
490
|
-
return
|
|
518
|
+
return Promise.all(tablePromises).then(() => {
|
|
519
|
+
this.persistTables();
|
|
520
|
+
const finalCount = this.store.read(Object.keys(RequestItems)[0]).length;
|
|
521
|
+
//logger.info(`[BATCH-DEBUG] FINAL | tabela=${Object.keys(RequestItems)[0]} | total=${finalCount}`);
|
|
522
|
+
return { UnprocessedItems: responses };
|
|
523
|
+
});
|
|
491
524
|
}
|
|
492
525
|
|
|
493
526
|
batchGetItem(params) {
|