@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.17",
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-20T21:57:52.362Z",
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: 'application/x-amz-json-1.0'
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
- throw new Error(`Item not found in ${TableName}`);
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
- // Implementação simplificada - expandir conforme necessário
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) {