@gugananuvem/aws-local-simulator 1.0.17 → 1.0.18
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.18",
|
|
4
4
|
"description": "Simulador local completo para serviços AWS",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -120,6 +120,6 @@
|
|
|
120
120
|
"optional": true
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
|
-
"buildDate": "2026-04-
|
|
123
|
+
"buildDate": "2026-04-21T01:06:51.838Z",
|
|
124
124
|
"published": true
|
|
125
125
|
}
|
|
@@ -20,7 +20,10 @@ class DynamoDBServer {
|
|
|
20
20
|
setupMiddlewares() {
|
|
21
21
|
this.app.use(cors());
|
|
22
22
|
this.app.use(express.json({
|
|
23
|
-
type:
|
|
23
|
+
type: (req) => {
|
|
24
|
+
const ct = req.headers['content-type'] || '';
|
|
25
|
+
return ct.includes('application/x-amz-json-1.0') || ct.includes('application/json');
|
|
26
|
+
}
|
|
24
27
|
}));
|
|
25
28
|
|
|
26
29
|
// Logging de requisições
|
|
@@ -53,6 +56,8 @@ class DynamoDBServer {
|
|
|
53
56
|
return res.status(400).json({ message: 'Missing X-Amz-Target header' });
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
logger.debug(`DynamoDB target=${target} content-type=${req.headers['content-type']} body=${JSON.stringify(req.body)}`);
|
|
60
|
+
|
|
56
61
|
try {
|
|
57
62
|
const result = await this.simulator.handleRequest(target, req.body);
|
|
58
63
|
res.json(result);
|
|
@@ -278,13 +278,27 @@ class DynamoDBSimulator {
|
|
|
278
278
|
throw new Error(`Table ${TableName} does not exist`);
|
|
279
279
|
}
|
|
280
280
|
|
|
281
|
-
// Busca o item atual
|
|
281
|
+
// Busca o item atual (upsert: cria se não existir, como o DynamoDB real)
|
|
282
282
|
const items = this.store.read(TableName);
|
|
283
283
|
const itemKey = this.getItemKeyFromKeys(Key, table);
|
|
284
284
|
const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
|
|
285
285
|
|
|
286
|
+
// Se não existe, cria um novo item com as chaves fornecidas
|
|
286
287
|
if (index === -1) {
|
|
287
|
-
|
|
288
|
+
const newItem = this.normalizeItem(Key, table);
|
|
289
|
+
newItem._createdAt = new Date().toISOString();
|
|
290
|
+
newItem._updatedAt = new Date().toISOString();
|
|
291
|
+
if (UpdateExpression) {
|
|
292
|
+
this.processUpdateExpression(newItem, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues, table);
|
|
293
|
+
}
|
|
294
|
+
items.push(newItem);
|
|
295
|
+
this.store.write(TableName, items);
|
|
296
|
+
logger.verboso(`UpdateItem (upsert): ${TableName}/${itemKey}`);
|
|
297
|
+
const response = {};
|
|
298
|
+
if (ReturnValues === "ALL_NEW" || ReturnValues === "UPDATED_NEW") {
|
|
299
|
+
response.Attributes = this.marshallItem(newItem, table);
|
|
300
|
+
}
|
|
301
|
+
return response;
|
|
288
302
|
}
|
|
289
303
|
|
|
290
304
|
const currentItem = items[index];
|
|
@@ -354,6 +368,11 @@ class DynamoDBSimulator {
|
|
|
354
368
|
const { RequestItems } = params;
|
|
355
369
|
const responses = {};
|
|
356
370
|
|
|
371
|
+
if (!RequestItems) {
|
|
372
|
+
logger.debug(`[DEBUG batchWriteItem] params recebido: ${JSON.stringify(params)}`);
|
|
373
|
+
throw new Error(`RequestItems is required for BatchWriteItem. Params received: ${JSON.stringify(params)}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
357
376
|
for (const [tableName, operations] of Object.entries(RequestItems)) {
|
|
358
377
|
const table = this.tables.get(tableName);
|
|
359
378
|
if (!table) continue;
|
|
@@ -585,21 +604,46 @@ class DynamoDBSimulator {
|
|
|
585
604
|
}
|
|
586
605
|
|
|
587
606
|
processUpdateExpression(item, expression, nameMap, valueMap, table) {
|
|
588
|
-
//
|
|
589
|
-
const setMatch = expression.match(/SET\s+([^]+?)(?=\s+(?:REMOVE|ADD|DELETE)|\s*$)/i);
|
|
590
|
-
|
|
607
|
+
// SET clause
|
|
608
|
+
const setMatch = expression.match(/SET\s+([^]+?)(?=\s+(?:REMOVE|ADD|DELETE)\s|\s*$)/i);
|
|
591
609
|
if (setMatch) {
|
|
592
610
|
const assignments = setMatch[1].split(",").map((a) => a.trim());
|
|
593
|
-
|
|
594
611
|
for (const assignment of assignments) {
|
|
595
612
|
const [path, valueExpr] = assignment.split("=").map((s) => s.trim());
|
|
596
|
-
const attributeName = path.replace(/#/g, "");
|
|
613
|
+
const attributeName = nameMap[path] || path.replace(/#/g, "");
|
|
597
614
|
const rawValue = valueMap[valueExpr];
|
|
598
615
|
const value = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
|
|
599
|
-
|
|
600
616
|
item[attributeName] = value;
|
|
601
617
|
}
|
|
602
618
|
}
|
|
619
|
+
|
|
620
|
+
// ADD clause — incrementa números ou adiciona a sets (upsert-friendly)
|
|
621
|
+
const addMatch = expression.match(/ADD\s+([^]+?)(?=\s+(?:SET|REMOVE|DELETE)\s|\s*$)/i);
|
|
622
|
+
if (addMatch) {
|
|
623
|
+
const assignments = addMatch[1].split(",").map((a) => a.trim());
|
|
624
|
+
for (const assignment of assignments) {
|
|
625
|
+
const parts = assignment.split(/\s+/);
|
|
626
|
+
const attributeName = nameMap[parts[0]] || parts[0].replace(/#/g, "");
|
|
627
|
+
const rawValue = valueMap[parts[1]];
|
|
628
|
+
const delta = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
|
|
629
|
+
const current = item[attributeName];
|
|
630
|
+
if (current === undefined || current === null) {
|
|
631
|
+
item[attributeName] = typeof delta === 'number' ? delta : parseFloat(delta) || 0;
|
|
632
|
+
} else {
|
|
633
|
+
item[attributeName] = (parseFloat(current) || 0) + (parseFloat(delta) || 0);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// REMOVE clause
|
|
639
|
+
const removeMatch = expression.match(/REMOVE\s+([^]+?)(?=\s+(?:SET|ADD|DELETE)\s|\s*$)/i);
|
|
640
|
+
if (removeMatch) {
|
|
641
|
+
const attributes = removeMatch[1].split(",").map((a) => a.trim());
|
|
642
|
+
for (const attr of attributes) {
|
|
643
|
+
const attributeName = nameMap[attr] || attr.replace(/#/g, "");
|
|
644
|
+
delete item[attributeName];
|
|
645
|
+
}
|
|
646
|
+
}
|
|
603
647
|
}
|
|
604
648
|
|
|
605
649
|
applyFilter(items, expression, values, table) {
|