@gugananuvem/aws-local-simulator 1.0.17 → 1.0.19

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.19",
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-22T01:57:09.515Z",
124
124
  "published": true
125
125
  }
@@ -223,7 +223,7 @@ class CognitoSimulator {
223
223
  Enabled: u.Enabled,
224
224
  UserCreateDate: u.CreatedDate,
225
225
  UserLastModifiedDate: u.LastModifiedDate,
226
- Attributes: this.formatUserAttributes(u.Attributes),
226
+ Attributes: this._formatUserAttributesWithSub(u),
227
227
  })),
228
228
  PaginationToken: nextToken,
229
229
  };
@@ -570,9 +570,10 @@ class CognitoSimulator {
570
570
  if (!this.sessions.has(session.Id)) throw new Error("Token has been revoked");
571
571
  const user = this.users.get(session.UserId);
572
572
  if (!user) throw new Error("User not found");
573
+ const attributes = this._formatUserAttributesWithSub(user);
573
574
  return {
574
575
  Username: user.Username,
575
- UserAttributes: this.formatUserAttributes(user.Attributes),
576
+ UserAttributes: attributes,
576
577
  UserStatus: user.UserStatus,
577
578
  };
578
579
  }
@@ -1155,7 +1156,7 @@ class CognitoSimulator {
1155
1156
 
1156
1157
  return {
1157
1158
  Username: user.Username,
1158
- UserAttributes: this.formatUserAttributes(user.Attributes),
1159
+ UserAttributes: this._formatUserAttributesWithSub(user),
1159
1160
  UserCreateDate: user.CreatedDate,
1160
1161
  UserLastModifiedDate: user.LastModifiedDate,
1161
1162
  Enabled: user.Enabled,
@@ -1214,7 +1215,7 @@ class CognitoSimulator {
1214
1215
  return {
1215
1216
  User: {
1216
1217
  Username: user.Username,
1217
- UserAttributes: this.formatUserAttributes(user.Attributes),
1218
+ UserAttributes: this._formatUserAttributesWithSub(user),
1218
1219
  UserCreateDate: user.CreatedDate,
1219
1220
  UserLastModifiedDate: user.LastModifiedDate,
1220
1221
  Enabled: user.Enabled,
@@ -1321,6 +1322,14 @@ class CognitoSimulator {
1321
1322
  return Object.entries(attributes).map(([Name, Value]) => ({ Name, Value }));
1322
1323
  }
1323
1324
 
1325
+ _formatUserAttributesWithSub(user) {
1326
+ const attrs = this.formatUserAttributes(user.Attributes);
1327
+ if (!attrs.find(a => a.Name === 'sub')) {
1328
+ attrs.unshift({ Name: 'sub', Value: user.UserId });
1329
+ }
1330
+ return attrs;
1331
+ }
1332
+
1324
1333
  hashPassword(password) {
1325
1334
  // Simulação de hash (não usar em produção real)
1326
1335
  return crypto.createHash("sha256").update(password).digest("hex");
@@ -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) {