@gugananuvem/aws-local-simulator 1.0.15 → 1.0.16

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 (77) hide show
  1. package/README.md +789 -594
  2. package/bin/aws-local-simulator.js +63 -63
  3. package/package.json +2 -2
  4. package/src/config/config-loader.js +114 -114
  5. package/src/config/default-config.js +68 -68
  6. package/src/config/env-loader.js +68 -68
  7. package/src/index.js +146 -146
  8. package/src/index.mjs +123 -123
  9. package/src/server.js +227 -227
  10. package/src/services/apigateway/index.js +75 -73
  11. package/src/services/apigateway/server.js +570 -507
  12. package/src/services/apigateway/simulator.js +1261 -1261
  13. package/src/services/athena/index.js +75 -75
  14. package/src/services/athena/server.js +101 -101
  15. package/src/services/athena/simulador.js +998 -998
  16. package/src/services/athena/simulator.js +346 -346
  17. package/src/services/cloudformation/index.js +106 -106
  18. package/src/services/cloudformation/server.js +417 -417
  19. package/src/services/cloudformation/simulador.js +1045 -1045
  20. package/src/services/cloudtrail/index.js +84 -84
  21. package/src/services/cloudtrail/server.js +235 -235
  22. package/src/services/cloudtrail/simulador.js +719 -719
  23. package/src/services/cloudwatch/index.js +84 -84
  24. package/src/services/cloudwatch/server.js +366 -366
  25. package/src/services/cloudwatch/simulador.js +1173 -1173
  26. package/src/services/cognito/index.js +79 -79
  27. package/src/services/cognito/server.js +301 -301
  28. package/src/services/cognito/simulator.js +1655 -1655
  29. package/src/services/config/index.js +96 -96
  30. package/src/services/config/server.js +215 -215
  31. package/src/services/config/simulador.js +1260 -1260
  32. package/src/services/dynamodb/index.js +74 -74
  33. package/src/services/dynamodb/server.js +125 -125
  34. package/src/services/dynamodb/simulator.js +630 -630
  35. package/src/services/ecs/index.js +65 -65
  36. package/src/services/ecs/server.js +235 -235
  37. package/src/services/ecs/simulator.js +844 -844
  38. package/src/services/eventbridge/index.js +89 -89
  39. package/src/services/eventbridge/server.js +209 -209
  40. package/src/services/eventbridge/simulator.js +684 -684
  41. package/src/services/index.js +45 -45
  42. package/src/services/kms/index.js +75 -75
  43. package/src/services/kms/server.js +67 -67
  44. package/src/services/kms/simulator.js +324 -324
  45. package/src/services/lambda/handler-loader.js +183 -183
  46. package/src/services/lambda/index.js +78 -78
  47. package/src/services/lambda/route-registry.js +274 -274
  48. package/src/services/lambda/server.js +145 -145
  49. package/src/services/lambda/simulator.js +199 -199
  50. package/src/services/parameter-store/index.js +80 -80
  51. package/src/services/parameter-store/server.js +50 -50
  52. package/src/services/parameter-store/simulator.js +201 -201
  53. package/src/services/s3/index.js +73 -73
  54. package/src/services/s3/server.js +329 -329
  55. package/src/services/s3/simulator.js +565 -565
  56. package/src/services/secret-manager/index.js +80 -80
  57. package/src/services/secret-manager/server.js +50 -50
  58. package/src/services/secret-manager/simulator.js +171 -171
  59. package/src/services/sns/index.js +89 -89
  60. package/src/services/sns/server.js +580 -580
  61. package/src/services/sns/simulator.js +1482 -1482
  62. package/src/services/sqs/index.js +98 -93
  63. package/src/services/sqs/server.js +349 -349
  64. package/src/services/sqs/simulator.js +441 -441
  65. package/src/services/sts/index.js +37 -37
  66. package/src/services/sts/server.js +144 -144
  67. package/src/services/sts/simulator.js +69 -69
  68. package/src/services/xray/index.js +83 -83
  69. package/src/services/xray/server.js +308 -308
  70. package/src/services/xray/simulador.js +994 -994
  71. package/src/template/aws-config-template.js +87 -87
  72. package/src/template/aws-config-template.mjs +90 -90
  73. package/src/template/config-template.json +203 -203
  74. package/src/utils/aws-config.js +91 -91
  75. package/src/utils/cloudtrail-audit.js +129 -129
  76. package/src/utils/local-store.js +83 -83
  77. package/src/utils/logger.js +59 -59
@@ -1,630 +1,630 @@
1
- /**
2
- * DynamoDB Simulator Core
3
- */
4
-
5
- const LocalStore = require("../../utils/local-store");
6
- const logger = require("../../utils/logger");
7
- const crypto = require("crypto");
8
- const path = require("path");
9
- const { CloudTrailAudit } = require("../../utils/cloudtrail-audit");
10
-
11
- class DynamoDBSimulator {
12
- constructor(config) {
13
- this.config = config;
14
- const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || "./.aws-local-simulator-data";
15
-
16
- if (!dataDir) {
17
- throw new Error("AWS_LOCAL_SIMULATOR_DATA_DIR not set");
18
- }
19
-
20
- this.dataDir = path.join(dataDir, "dynamodb");
21
- this.store = new LocalStore(this.dataDir);
22
- this.tables = new Map();
23
- this.audit = new CloudTrailAudit("dynamodb.amazonaws.com");
24
- }
25
- async initialize() {
26
- logger.debug("Inicializando DynamoDB Simulator...");
27
- this.loadTables();
28
- logger.debug(`✅ DynamoDB Simulator inicializado com ${this.tables.size} tabelas`);
29
- }
30
-
31
- loadTables() {
32
- // Carrega tabelas da configuração
33
- if (this.config.dynamodb?.tables) {
34
- for (const tableDef of this.config.dynamodb.tables) {
35
- this.createTable(tableDef);
36
- }
37
- }
38
-
39
- // Carrega tabelas existentes do disco
40
- const savedTables = this.store.read("__tables__");
41
- if (savedTables) {
42
- for (const [name, definition] of Object.entries(savedTables)) {
43
- if (!this.tables.has(name)) {
44
- this.tables.set(name, definition);
45
- }
46
- }
47
- }
48
- }
49
-
50
- createTable(params) {
51
- const { TableName, KeySchema, AttributeDefinitions, ProvisionedThroughput } = params;
52
-
53
- if (this.tables.has(TableName)) {
54
- logger.warn(`Tabela ${TableName} já existe`);
55
- return { TableDescription: { TableName, TableStatus: "ACTIVE" } };
56
- }
57
-
58
- const hashKey = KeySchema.find((k) => k.KeyType === "HASH").AttributeName;
59
- const rangeKey = KeySchema.find((k) => k.KeyType === "RANGE")?.AttributeName;
60
-
61
- const attributeTypes = {};
62
- AttributeDefinitions.forEach((attr) => {
63
- attributeTypes[attr.AttributeName] = attr.AttributeType;
64
- });
65
-
66
- const table = {
67
- name: TableName,
68
- hashKey,
69
- rangeKey,
70
- attributeTypes,
71
- createdAt: new Date().toISOString(),
72
- itemCount: 0,
73
- sizeBytes: 0,
74
- };
75
-
76
- this.tables.set(TableName, table);
77
- this.persistTables();
78
-
79
- // Inicializa arquivo de dados apenas se não existir (preserva dados entre reinicializações)
80
- if (!this.store.exists(TableName)) {
81
- this.store.write(TableName, []);
82
- }
83
-
84
- logger.debug(`✅ Tabela criada: ${TableName}`);
85
- this.audit.record({ eventName: "CreateTable", readOnly: false, resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${TableName}`, type: "AWS::DynamoDB::Table" }], requestParameters: { tableName: TableName } });
86
-
87
- return {
88
- TableDescription: {
89
- TableName,
90
- TableStatus: "ACTIVE",
91
- CreationDateTime: new Date().toISOString(),
92
- KeySchema,
93
- AttributeDefinitions,
94
- ProvisionedThroughput: ProvisionedThroughput || {
95
- ReadCapacityUnits: 5,
96
- WriteCapacityUnits: 5,
97
- },
98
- ItemCount: 0,
99
- TableSizeBytes: 0,
100
- },
101
- };
102
- }
103
-
104
- async handleRequest(target, params) {
105
- const action = target.split(".")[1];
106
-
107
- logger.verboso(`DynamoDB Action: ${action}`, params);
108
-
109
- const readActions = new Set(["GetItem", "BatchGetItem", "Query", "Scan", "DescribeTable", "ListTables"]);
110
- const dataActions = new Set(["PutItem", "GetItem", "UpdateItem", "DeleteItem", "BatchWriteItem", "BatchGetItem", "Query", "Scan"]);
111
-
112
- const result = (() => {
113
- switch (action) {
114
- case "CreateTable": return this.createTable(params);
115
- case "DescribeTable": return this.describeTable(params.TableName);
116
- case "ListTables": return this.listTables(params);
117
- case "DeleteTable": return this.deleteTable(params);
118
- case "PutItem": return this.putItem(params);
119
- case "GetItem": return this.getItem(params);
120
- case "UpdateItem": return this.updateItem(params);
121
- case "DeleteItem": return this.deleteItem(params);
122
- case "BatchWriteItem": return this.batchWriteItem(params);
123
- case "BatchGetItem": return this.batchGetItem(params);
124
- case "Query": return this.query(params);
125
- case "Scan": return this.scan(params);
126
- default: throw new Error(`Unsupported action: ${action}`);
127
- }
128
- })();
129
-
130
- const tableName = params.TableName;
131
- if (tableName) {
132
- this.audit.record({
133
- eventName: action,
134
- readOnly: readActions.has(action),
135
- isDataEvent: dataActions.has(action),
136
- resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${tableName}`, type: "AWS::DynamoDB::Table" }],
137
- requestParameters: { tableName },
138
- });
139
- }
140
-
141
- return result;
142
- }
143
-
144
- describeTable(tableName) {
145
- const table = this.tables.get(tableName);
146
- if (!table) {
147
- throw new Error(`Table ${tableName} does not exist`);
148
- }
149
-
150
- const items = this.store.read(tableName);
151
-
152
- return {
153
- Table: {
154
- TableName: table.name,
155
- TableStatus: "ACTIVE",
156
- CreationDateTime: table.createdAt,
157
- KeySchema: [{ AttributeName: table.hashKey, KeyType: "HASH" }, ...(table.rangeKey ? [{ AttributeName: table.rangeKey, KeyType: "RANGE" }] : [])],
158
- AttributeDefinitions: Object.entries(table.attributeTypes).map(([name, type]) => ({
159
- AttributeName: name,
160
- AttributeType: type,
161
- })),
162
- ItemCount: items.length,
163
- TableSizeBytes: JSON.stringify(items).length,
164
- ProvisionedThroughput: {
165
- ReadCapacityUnits: 5,
166
- WriteCapacityUnits: 5,
167
- },
168
- },
169
- };
170
- }
171
-
172
- listTables(params = {}) {
173
- const tableNames = Array.from(this.tables.keys());
174
- const { Limit = 100, ExclusiveStartTableName } = params;
175
-
176
- let startIndex = 0;
177
- if (ExclusiveStartTableName) {
178
- const index = tableNames.indexOf(ExclusiveStartTableName);
179
- if (index !== -1) startIndex = index + 1;
180
- }
181
-
182
- const result = tableNames.slice(startIndex, startIndex + Limit);
183
-
184
- return {
185
- TableNames: result,
186
- LastEvaluatedTableName: result.length === Limit ? result[result.length - 1] : undefined,
187
- };
188
- }
189
-
190
- deleteTable(params) {
191
- const { TableName } = params;
192
-
193
- if (!this.tables.has(TableName)) {
194
- throw new Error(`Table ${TableName} does not exist`);
195
- }
196
-
197
- this.tables.delete(TableName);
198
- this.store.delete(TableName);
199
- this.persistTables();
200
-
201
- return { TableDescription: { TableName, TableStatus: "DELETING" } };
202
- }
203
-
204
- putItem(params) {
205
- const { TableName, Item, ReturnValues = "NONE" } = params;
206
- const table = this.tables.get(TableName);
207
-
208
- if (!table) {
209
- throw new Error(`Table ${TableName} does not exist`);
210
- }
211
-
212
- // Normaliza o item
213
- const normalizedItem = this.normalizeItem(Item, table);
214
- normalizedItem._createdAt = normalizedItem._createdAt || new Date().toISOString();
215
- normalizedItem._updatedAt = new Date().toISOString();
216
-
217
- // Carrega dados existentes
218
- let items = this.store.read(TableName);
219
- const itemKey = this.getItemKey(normalizedItem, table);
220
-
221
- // Encontra e substitui ou adiciona
222
- const existingIndex = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
223
-
224
- let oldItem = null;
225
- if (existingIndex !== -1) {
226
- oldItem = { ...items[existingIndex] };
227
- items[existingIndex] = normalizedItem;
228
- } else {
229
- items.push(normalizedItem);
230
- table.itemCount++;
231
- }
232
-
233
- // Salva no store
234
- this.store.write(TableName, items);
235
- this.persistTables();
236
-
237
- logger.verboso(`PutItem: ${TableName}/${itemKey}`);
238
-
239
- const response = {};
240
- if (ReturnValues === "ALL_OLD" && oldItem) {
241
- response.Attributes = this.marshallItem(oldItem, table);
242
- }
243
-
244
- return response;
245
- }
246
-
247
- getItem(params) {
248
- const { TableName, Key } = params;
249
- const table = this.tables.get(TableName);
250
-
251
- if (!table) {
252
- throw new Error(`Table ${TableName} does not exist`);
253
- }
254
-
255
- const items = this.store.read(TableName);
256
- const itemKey = this.getItemKeyFromKeys(Key, table);
257
-
258
- const item = items.find((item) => this.getItemKey(item, table) === itemKey);
259
-
260
- logger.verboso(`GetItem: ${TableName}/${itemKey} - ${item ? "found" : "not found"}`);
261
-
262
- return item ? { Item: this.marshallItem(item, table) } : {};
263
- }
264
-
265
- updateItem(params) {
266
- const { TableName, Key, UpdateExpression, ExpressionAttributeNames = {}, ExpressionAttributeValues = {}, ReturnValues = "NONE" } = params;
267
- const table = this.tables.get(TableName);
268
-
269
- if (!table) {
270
- throw new Error(`Table ${TableName} does not exist`);
271
- }
272
-
273
- // Busca o item atual
274
- const items = this.store.read(TableName);
275
- const itemKey = this.getItemKeyFromKeys(Key, table);
276
- const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
277
-
278
- if (index === -1) {
279
- throw new Error(`Item not found in ${TableName}`);
280
- }
281
-
282
- const currentItem = items[index];
283
- const updatedItem = { ...currentItem };
284
- updatedItem._updatedAt = new Date().toISOString();
285
-
286
- // Processa a UpdateExpression
287
- if (UpdateExpression) {
288
- this.processUpdateExpression(updatedItem, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues, table);
289
- }
290
-
291
- // Salva o item atualizado
292
- const oldItem = { ...items[index] };
293
- items[index] = updatedItem;
294
- this.store.write(TableName, items);
295
-
296
- logger.verboso(`UpdateItem: ${TableName}/${itemKey}`);
297
-
298
- const response = {};
299
- switch (ReturnValues) {
300
- case "ALL_OLD":
301
- response.Attributes = this.marshallItem(oldItem, table);
302
- break;
303
- case "ALL_NEW":
304
- response.Attributes = this.marshallItem(updatedItem, table);
305
- break;
306
- default:
307
- break;
308
- }
309
-
310
- return response;
311
- }
312
-
313
- deleteItem(params) {
314
- const { TableName, Key, ReturnValues = "NONE" } = params;
315
- const table = this.tables.get(TableName);
316
-
317
- if (!table) {
318
- throw new Error(`Table ${TableName} does not exist`);
319
- }
320
-
321
- const items = this.store.read(TableName);
322
- const itemKey = this.getItemKeyFromKeys(Key, table);
323
- const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
324
-
325
- if (index === -1) {
326
- return {};
327
- }
328
-
329
- const oldItem = { ...items[index] };
330
- items.splice(index, 1);
331
- this.store.write(TableName, items);
332
- table.itemCount--;
333
- this.persistTables();
334
-
335
- logger.verboso(`DeleteItem: ${TableName}/${itemKey}`);
336
-
337
- const response = {};
338
- if (ReturnValues === "ALL_OLD") {
339
- response.Attributes = this.marshallItem(oldItem, table);
340
- }
341
-
342
- return response;
343
- }
344
-
345
- batchWriteItem(params) {
346
- const { RequestItems } = params;
347
- const responses = {};
348
-
349
- for (const [tableName, operations] of Object.entries(RequestItems)) {
350
- const table = this.tables.get(tableName);
351
- if (!table) continue;
352
-
353
- let items = this.store.read(tableName);
354
- const unprocessedItems = [];
355
-
356
- for (const op of operations) {
357
- if (op.PutRequest) {
358
- const item = this.normalizeItem(op.PutRequest.Item, table);
359
- const itemKey = this.getItemKey(item, table);
360
- const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
361
-
362
- if (index !== -1) {
363
- items[index] = item;
364
- } else {
365
- items.push(item);
366
- table.itemCount++;
367
- }
368
- } else if (op.DeleteRequest) {
369
- const key = op.DeleteRequest.Key;
370
- const itemKey = this.getItemKeyFromKeys(key, table);
371
- const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
372
-
373
- if (index !== -1) {
374
- items.splice(index, 1);
375
- table.itemCount--;
376
- } else {
377
- unprocessedItems.push(op);
378
- }
379
- }
380
- }
381
-
382
- this.store.write(tableName, items);
383
- responses[tableName] = { UnprocessedItems: unprocessedItems };
384
- }
385
-
386
- this.persistTables();
387
-
388
- return { UnprocessedItems: responses };
389
- }
390
-
391
- batchGetItem(params) {
392
- const { RequestItems } = params;
393
- const responses = {};
394
-
395
- for (const [tableName, request] of Object.entries(RequestItems)) {
396
- const table = this.tables.get(tableName);
397
- if (!table) continue;
398
-
399
- const items = this.store.read(tableName);
400
- const { Keys } = request;
401
- const foundItems = [];
402
-
403
- for (const key of Keys) {
404
- const itemKey = this.getItemKeyFromKeys(key, table);
405
- const item = items.find((i) => this.getItemKey(i, table) === itemKey);
406
- if (item) {
407
- foundItems.push(this.marshallItem(item, table));
408
- }
409
- }
410
-
411
- responses[tableName] = { Items: foundItems };
412
- }
413
-
414
- return { Responses: responses };
415
- }
416
-
417
- query(params) {
418
- const { TableName, KeyConditionExpression, ExpressionAttributeValues, IndexName } = params;
419
- const table = this.tables.get(TableName);
420
-
421
- if (!table) {
422
- throw new Error(`Table ${TableName} does not exist`);
423
- }
424
-
425
- let items = this.store.read(TableName);
426
-
427
- // Filtra pela chave de partição
428
- const hashKey = table.hashKey;
429
- const hashValueMatch = KeyConditionExpression.match(new RegExp(`${hashKey}\\s*=\\s*([^\\s]+)`));
430
-
431
- if (hashValueMatch) {
432
- const hashValuePlaceholder = hashValueMatch[1];
433
- const rawHashValue = ExpressionAttributeValues[hashValuePlaceholder];
434
- const hashValue = rawHashValue && typeof rawHashValue === 'object' ? Object.values(rawHashValue)[0] : rawHashValue;
435
- items = items.filter((item) => item[hashKey] === hashValue);
436
- }
437
-
438
- // Filtra pela chave de ordenação se existir
439
- if (table.rangeKey) {
440
- const rangeKey = table.rangeKey;
441
- const rangeConditionMatch = KeyConditionExpression.match(new RegExp(`${rangeKey}\\s*(=|>|<|>=|<=)\\s*([^\\s]+)`));
442
-
443
- if (rangeConditionMatch) {
444
- const operator = rangeConditionMatch[1];
445
- const rangeValuePlaceholder = rangeConditionMatch[2];
446
- const rawRangeValue = ExpressionAttributeValues[rangeValuePlaceholder];
447
- const rangeValue = rawRangeValue && typeof rawRangeValue === 'object' ? Object.values(rawRangeValue)[0] : rawRangeValue;
448
-
449
- items = items.filter((item) => {
450
- const itemValue = item[rangeKey];
451
- switch (operator) {
452
- case "=":
453
- return itemValue === rangeValue;
454
- case ">":
455
- return itemValue > rangeValue;
456
- case "<":
457
- return itemValue < rangeValue;
458
- case ">=":
459
- return itemValue >= rangeValue;
460
- case "<=":
461
- return itemValue <= rangeValue;
462
- default:
463
- return true;
464
- }
465
- });
466
- }
467
- }
468
-
469
- const marshalledItems = items.map((item) => this.marshallItem(item, table));
470
-
471
- return {
472
- Items: marshalledItems,
473
- Count: marshalledItems.length,
474
- ScannedCount: items.length,
475
- };
476
- }
477
-
478
- scan(params) {
479
- const { TableName, FilterExpression, ExpressionAttributeValues, Limit } = params;
480
- const table = this.tables.get(TableName);
481
-
482
- if (!table) {
483
- throw new Error(`Table ${TableName} does not exist`);
484
- }
485
-
486
- let items = this.store.read(TableName);
487
-
488
- // Aplica filtro se existir
489
- if (FilterExpression) {
490
- items = this.applyFilter(items, FilterExpression, ExpressionAttributeValues, table);
491
- }
492
-
493
- // Aplica limite
494
- if (Limit && items.length > Limit) {
495
- items = items.slice(0, Limit);
496
- }
497
-
498
- const marshalledItems = items.map((item) => this.marshallItem(item, table));
499
-
500
- return {
501
- Items: marshalledItems,
502
- Count: marshalledItems.length,
503
- ScannedCount: items.length,
504
- };
505
- }
506
-
507
- // Métodos auxiliares
508
- normalizeItem(item, table) {
509
- const normalized = { ...item };
510
-
511
- // Remove os tipos do DynamoDB (S, N, etc)
512
- for (const [key, value] of Object.entries(normalized)) {
513
- if (value && typeof value === "object") {
514
- if (value.S !== undefined) normalized[key] = value.S;
515
- else if (value.N !== undefined) normalized[key] = parseFloat(value.N);
516
- else if (value.BOOL !== undefined) normalized[key] = value.BOOL;
517
- else if (value.L !== undefined) normalized[key] = value.L.map((v) => this.normalizeItem(v, table));
518
- else if (value.M !== undefined) normalized[key] = this.normalizeItem(value.M, table);
519
- }
520
- }
521
-
522
- return normalized;
523
- }
524
-
525
- marshallItem(item, table) {
526
- const marshalled = {};
527
-
528
- for (const [key, value] of Object.entries(item)) {
529
- if (key.startsWith("_")) continue; // Pula campos internos
530
-
531
- const type = table.attributeTypes[key];
532
- if (type === "S") {
533
- marshalled[key] = { S: String(value) };
534
- } else if (type === "N") {
535
- marshalled[key] = { N: String(value) };
536
- } else if (type === "BOOL") {
537
- marshalled[key] = { BOOL: Boolean(value) };
538
- } else if (Array.isArray(value)) {
539
- marshalled[key] = { L: value.map((v) => ({ S: String(v) })) };
540
- } else if (typeof value === "object") {
541
- marshalled[key] = { M: this.marshallItem(value, table) };
542
- } else {
543
- marshalled[key] = { S: String(value) };
544
- }
545
- }
546
-
547
- return marshalled;
548
- }
549
-
550
- getItemKey(item, table) {
551
- const hashValue = item[table.hashKey];
552
- const rangeValue = table.rangeKey ? item[table.rangeKey] : null;
553
- return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
554
- }
555
-
556
- getItemKeyFromKeys(keys, table) {
557
- const rawHash = keys[table.hashKey];
558
- const hashValue = rawHash && typeof rawHash === 'object' ? Object.values(rawHash)[0] : rawHash;
559
- const rawRange = table.rangeKey ? keys[table.rangeKey] : null;
560
- const rangeValue = rawRange && typeof rawRange === 'object' ? Object.values(rawRange)[0] : rawRange;
561
- return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
562
- }
563
-
564
- processUpdateExpression(item, expression, nameMap, valueMap, table) {
565
- // Implementação simplificada - expandir conforme necessário
566
- const setMatch = expression.match(/SET\s+([^]+?)(?=\s+(?:REMOVE|ADD|DELETE)|\s*$)/i);
567
-
568
- if (setMatch) {
569
- const assignments = setMatch[1].split(",").map((a) => a.trim());
570
-
571
- for (const assignment of assignments) {
572
- const [path, valueExpr] = assignment.split("=").map((s) => s.trim());
573
- const attributeName = path.replace(/#/g, "");
574
- const rawValue = valueMap[valueExpr];
575
- const value = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
576
-
577
- item[attributeName] = value;
578
- }
579
- }
580
- }
581
-
582
- applyFilter(items, expression, values, table) {
583
- // Implementação simplificada
584
- return items.filter((item) => {
585
- const match = expression.match(/([^\s]+)\s*=\s*([^\s]+)/);
586
- if (match) {
587
- const [, attribute, placeholder] = match;
588
- const rawValue = values[placeholder];
589
- const expectedValue = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
590
- const actualValue = item[attribute];
591
- return actualValue === expectedValue;
592
- }
593
- return true;
594
- });
595
- }
596
-
597
- persistTables() {
598
- const tablesObj = {};
599
- for (const [name, table] of this.tables.entries()) {
600
- tablesObj[name] = table;
601
- }
602
- this.store.write("__tables__", tablesObj);
603
- }
604
-
605
- async reset() {
606
- for (const [tableName] of this.tables) {
607
- this.store.write(tableName, []);
608
- }
609
- logger.debug("DynamoDB: Todos os dados resetados");
610
- }
611
-
612
- getTablesCount() {
613
- return this.tables.size;
614
- }
615
-
616
- getTotalItems() {
617
- let total = 0;
618
- for (const [tableName] of this.tables) {
619
- const items = this.store.read(tableName);
620
- total += items.length;
621
- }
622
- return total;
623
- }
624
-
625
- listTables() {
626
- return { TableNames: Array.from(this.tables.keys()) };
627
- }
628
- }
629
-
630
- module.exports = DynamoDBSimulator;
1
+ /**
2
+ * DynamoDB Simulator Core
3
+ */
4
+
5
+ const LocalStore = require("../../utils/local-store");
6
+ const logger = require("../../utils/logger");
7
+ const crypto = require("crypto");
8
+ const path = require("path");
9
+ const { CloudTrailAudit } = require("../../utils/cloudtrail-audit");
10
+
11
+ class DynamoDBSimulator {
12
+ constructor(config) {
13
+ this.config = config;
14
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || "./.aws-local-simulator-data";
15
+
16
+ if (!dataDir) {
17
+ throw new Error("AWS_LOCAL_SIMULATOR_DATA_DIR not set");
18
+ }
19
+
20
+ this.dataDir = path.join(dataDir, "dynamodb");
21
+ this.store = new LocalStore(this.dataDir);
22
+ this.tables = new Map();
23
+ this.audit = new CloudTrailAudit("dynamodb.amazonaws.com");
24
+ }
25
+ async initialize() {
26
+ logger.debug("Inicializando DynamoDB Simulator...");
27
+ this.loadTables();
28
+ logger.debug(`✅ DynamoDB Simulator inicializado com ${this.tables.size} tabelas`);
29
+ }
30
+
31
+ loadTables() {
32
+ // Carrega tabelas da configuração
33
+ if (this.config.dynamodb?.tables) {
34
+ for (const tableDef of this.config.dynamodb.tables) {
35
+ this.createTable(tableDef);
36
+ }
37
+ }
38
+
39
+ // Carrega tabelas existentes do disco
40
+ const savedTables = this.store.read("__tables__");
41
+ if (savedTables) {
42
+ for (const [name, definition] of Object.entries(savedTables)) {
43
+ if (!this.tables.has(name)) {
44
+ this.tables.set(name, definition);
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ createTable(params) {
51
+ const { TableName, KeySchema, AttributeDefinitions, ProvisionedThroughput } = params;
52
+
53
+ if (this.tables.has(TableName)) {
54
+ logger.warn(`Tabela ${TableName} já existe`);
55
+ return { TableDescription: { TableName, TableStatus: "ACTIVE" } };
56
+ }
57
+
58
+ const hashKey = KeySchema.find((k) => k.KeyType === "HASH").AttributeName;
59
+ const rangeKey = KeySchema.find((k) => k.KeyType === "RANGE")?.AttributeName;
60
+
61
+ const attributeTypes = {};
62
+ AttributeDefinitions.forEach((attr) => {
63
+ attributeTypes[attr.AttributeName] = attr.AttributeType;
64
+ });
65
+
66
+ const table = {
67
+ name: TableName,
68
+ hashKey,
69
+ rangeKey,
70
+ attributeTypes,
71
+ createdAt: new Date().toISOString(),
72
+ itemCount: 0,
73
+ sizeBytes: 0,
74
+ };
75
+
76
+ this.tables.set(TableName, table);
77
+ this.persistTables();
78
+
79
+ // Inicializa arquivo de dados apenas se não existir (preserva dados entre reinicializações)
80
+ if (!this.store.exists(TableName)) {
81
+ this.store.write(TableName, []);
82
+ }
83
+
84
+ logger.debug(`✅ Tabela criada: ${TableName}`);
85
+ this.audit.record({ eventName: "CreateTable", readOnly: false, resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${TableName}`, type: "AWS::DynamoDB::Table" }], requestParameters: { tableName: TableName } });
86
+
87
+ return {
88
+ TableDescription: {
89
+ TableName,
90
+ TableStatus: "ACTIVE",
91
+ CreationDateTime: new Date().toISOString(),
92
+ KeySchema,
93
+ AttributeDefinitions,
94
+ ProvisionedThroughput: ProvisionedThroughput || {
95
+ ReadCapacityUnits: 5,
96
+ WriteCapacityUnits: 5,
97
+ },
98
+ ItemCount: 0,
99
+ TableSizeBytes: 0,
100
+ },
101
+ };
102
+ }
103
+
104
+ async handleRequest(target, params) {
105
+ const action = target.split(".")[1];
106
+
107
+ logger.verboso(`DynamoDB Action: ${action}`, params);
108
+
109
+ const readActions = new Set(["GetItem", "BatchGetItem", "Query", "Scan", "DescribeTable", "ListTables"]);
110
+ const dataActions = new Set(["PutItem", "GetItem", "UpdateItem", "DeleteItem", "BatchWriteItem", "BatchGetItem", "Query", "Scan"]);
111
+
112
+ const result = (() => {
113
+ switch (action) {
114
+ case "CreateTable": return this.createTable(params);
115
+ case "DescribeTable": return this.describeTable(params.TableName);
116
+ case "ListTables": return this.listTables(params);
117
+ case "DeleteTable": return this.deleteTable(params);
118
+ case "PutItem": return this.putItem(params);
119
+ case "GetItem": return this.getItem(params);
120
+ case "UpdateItem": return this.updateItem(params);
121
+ case "DeleteItem": return this.deleteItem(params);
122
+ case "BatchWriteItem": return this.batchWriteItem(params);
123
+ case "BatchGetItem": return this.batchGetItem(params);
124
+ case "Query": return this.query(params);
125
+ case "Scan": return this.scan(params);
126
+ default: throw new Error(`Unsupported action: ${action}`);
127
+ }
128
+ })();
129
+
130
+ const tableName = params.TableName;
131
+ if (tableName) {
132
+ this.audit.record({
133
+ eventName: action,
134
+ readOnly: readActions.has(action),
135
+ isDataEvent: dataActions.has(action),
136
+ resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${tableName}`, type: "AWS::DynamoDB::Table" }],
137
+ requestParameters: { tableName },
138
+ });
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ describeTable(tableName) {
145
+ const table = this.tables.get(tableName);
146
+ if (!table) {
147
+ throw new Error(`Table ${tableName} does not exist`);
148
+ }
149
+
150
+ const items = this.store.read(tableName);
151
+
152
+ return {
153
+ Table: {
154
+ TableName: table.name,
155
+ TableStatus: "ACTIVE",
156
+ CreationDateTime: table.createdAt,
157
+ KeySchema: [{ AttributeName: table.hashKey, KeyType: "HASH" }, ...(table.rangeKey ? [{ AttributeName: table.rangeKey, KeyType: "RANGE" }] : [])],
158
+ AttributeDefinitions: Object.entries(table.attributeTypes).map(([name, type]) => ({
159
+ AttributeName: name,
160
+ AttributeType: type,
161
+ })),
162
+ ItemCount: items.length,
163
+ TableSizeBytes: JSON.stringify(items).length,
164
+ ProvisionedThroughput: {
165
+ ReadCapacityUnits: 5,
166
+ WriteCapacityUnits: 5,
167
+ },
168
+ },
169
+ };
170
+ }
171
+
172
+ listTables(params = {}) {
173
+ const tableNames = Array.from(this.tables.keys());
174
+ const { Limit = 100, ExclusiveStartTableName } = params;
175
+
176
+ let startIndex = 0;
177
+ if (ExclusiveStartTableName) {
178
+ const index = tableNames.indexOf(ExclusiveStartTableName);
179
+ if (index !== -1) startIndex = index + 1;
180
+ }
181
+
182
+ const result = tableNames.slice(startIndex, startIndex + Limit);
183
+
184
+ return {
185
+ TableNames: result,
186
+ LastEvaluatedTableName: result.length === Limit ? result[result.length - 1] : undefined,
187
+ };
188
+ }
189
+
190
+ deleteTable(params) {
191
+ const { TableName } = params;
192
+
193
+ if (!this.tables.has(TableName)) {
194
+ throw new Error(`Table ${TableName} does not exist`);
195
+ }
196
+
197
+ this.tables.delete(TableName);
198
+ this.store.delete(TableName);
199
+ this.persistTables();
200
+
201
+ return { TableDescription: { TableName, TableStatus: "DELETING" } };
202
+ }
203
+
204
+ putItem(params) {
205
+ const { TableName, Item, ReturnValues = "NONE" } = params;
206
+ const table = this.tables.get(TableName);
207
+
208
+ if (!table) {
209
+ throw new Error(`Table ${TableName} does not exist`);
210
+ }
211
+
212
+ // Normaliza o item
213
+ const normalizedItem = this.normalizeItem(Item, table);
214
+ normalizedItem._createdAt = normalizedItem._createdAt || new Date().toISOString();
215
+ normalizedItem._updatedAt = new Date().toISOString();
216
+
217
+ // Carrega dados existentes
218
+ let items = this.store.read(TableName);
219
+ const itemKey = this.getItemKey(normalizedItem, table);
220
+
221
+ // Encontra e substitui ou adiciona
222
+ const existingIndex = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
223
+
224
+ let oldItem = null;
225
+ if (existingIndex !== -1) {
226
+ oldItem = { ...items[existingIndex] };
227
+ items[existingIndex] = normalizedItem;
228
+ } else {
229
+ items.push(normalizedItem);
230
+ table.itemCount++;
231
+ }
232
+
233
+ // Salva no store
234
+ this.store.write(TableName, items);
235
+ this.persistTables();
236
+
237
+ logger.verboso(`PutItem: ${TableName}/${itemKey}`);
238
+
239
+ const response = {};
240
+ if (ReturnValues === "ALL_OLD" && oldItem) {
241
+ response.Attributes = this.marshallItem(oldItem, table);
242
+ }
243
+
244
+ return response;
245
+ }
246
+
247
+ getItem(params) {
248
+ const { TableName, Key } = params;
249
+ const table = this.tables.get(TableName);
250
+
251
+ if (!table) {
252
+ throw new Error(`Table ${TableName} does not exist`);
253
+ }
254
+
255
+ const items = this.store.read(TableName);
256
+ const itemKey = this.getItemKeyFromKeys(Key, table);
257
+
258
+ const item = items.find((item) => this.getItemKey(item, table) === itemKey);
259
+
260
+ logger.verboso(`GetItem: ${TableName}/${itemKey} - ${item ? "found" : "not found"}`);
261
+
262
+ return item ? { Item: this.marshallItem(item, table) } : {};
263
+ }
264
+
265
+ updateItem(params) {
266
+ const { TableName, Key, UpdateExpression, ExpressionAttributeNames = {}, ExpressionAttributeValues = {}, ReturnValues = "NONE" } = params;
267
+ const table = this.tables.get(TableName);
268
+
269
+ if (!table) {
270
+ throw new Error(`Table ${TableName} does not exist`);
271
+ }
272
+
273
+ // Busca o item atual
274
+ const items = this.store.read(TableName);
275
+ const itemKey = this.getItemKeyFromKeys(Key, table);
276
+ const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
277
+
278
+ if (index === -1) {
279
+ throw new Error(`Item not found in ${TableName}`);
280
+ }
281
+
282
+ const currentItem = items[index];
283
+ const updatedItem = { ...currentItem };
284
+ updatedItem._updatedAt = new Date().toISOString();
285
+
286
+ // Processa a UpdateExpression
287
+ if (UpdateExpression) {
288
+ this.processUpdateExpression(updatedItem, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues, table);
289
+ }
290
+
291
+ // Salva o item atualizado
292
+ const oldItem = { ...items[index] };
293
+ items[index] = updatedItem;
294
+ this.store.write(TableName, items);
295
+
296
+ logger.verboso(`UpdateItem: ${TableName}/${itemKey}`);
297
+
298
+ const response = {};
299
+ switch (ReturnValues) {
300
+ case "ALL_OLD":
301
+ response.Attributes = this.marshallItem(oldItem, table);
302
+ break;
303
+ case "ALL_NEW":
304
+ response.Attributes = this.marshallItem(updatedItem, table);
305
+ break;
306
+ default:
307
+ break;
308
+ }
309
+
310
+ return response;
311
+ }
312
+
313
+ deleteItem(params) {
314
+ const { TableName, Key, ReturnValues = "NONE" } = params;
315
+ const table = this.tables.get(TableName);
316
+
317
+ if (!table) {
318
+ throw new Error(`Table ${TableName} does not exist`);
319
+ }
320
+
321
+ const items = this.store.read(TableName);
322
+ const itemKey = this.getItemKeyFromKeys(Key, table);
323
+ const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
324
+
325
+ if (index === -1) {
326
+ return {};
327
+ }
328
+
329
+ const oldItem = { ...items[index] };
330
+ items.splice(index, 1);
331
+ this.store.write(TableName, items);
332
+ table.itemCount--;
333
+ this.persistTables();
334
+
335
+ logger.verboso(`DeleteItem: ${TableName}/${itemKey}`);
336
+
337
+ const response = {};
338
+ if (ReturnValues === "ALL_OLD") {
339
+ response.Attributes = this.marshallItem(oldItem, table);
340
+ }
341
+
342
+ return response;
343
+ }
344
+
345
+ batchWriteItem(params) {
346
+ const { RequestItems } = params;
347
+ const responses = {};
348
+
349
+ for (const [tableName, operations] of Object.entries(RequestItems)) {
350
+ const table = this.tables.get(tableName);
351
+ if (!table) continue;
352
+
353
+ let items = this.store.read(tableName);
354
+ const unprocessedItems = [];
355
+
356
+ for (const op of operations) {
357
+ if (op.PutRequest) {
358
+ const item = this.normalizeItem(op.PutRequest.Item, table);
359
+ const itemKey = this.getItemKey(item, table);
360
+ const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
361
+
362
+ if (index !== -1) {
363
+ items[index] = item;
364
+ } else {
365
+ items.push(item);
366
+ table.itemCount++;
367
+ }
368
+ } else if (op.DeleteRequest) {
369
+ const key = op.DeleteRequest.Key;
370
+ const itemKey = this.getItemKeyFromKeys(key, table);
371
+ const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
372
+
373
+ if (index !== -1) {
374
+ items.splice(index, 1);
375
+ table.itemCount--;
376
+ } else {
377
+ unprocessedItems.push(op);
378
+ }
379
+ }
380
+ }
381
+
382
+ this.store.write(tableName, items);
383
+ responses[tableName] = { UnprocessedItems: unprocessedItems };
384
+ }
385
+
386
+ this.persistTables();
387
+
388
+ return { UnprocessedItems: responses };
389
+ }
390
+
391
+ batchGetItem(params) {
392
+ const { RequestItems } = params;
393
+ const responses = {};
394
+
395
+ for (const [tableName, request] of Object.entries(RequestItems)) {
396
+ const table = this.tables.get(tableName);
397
+ if (!table) continue;
398
+
399
+ const items = this.store.read(tableName);
400
+ const { Keys } = request;
401
+ const foundItems = [];
402
+
403
+ for (const key of Keys) {
404
+ const itemKey = this.getItemKeyFromKeys(key, table);
405
+ const item = items.find((i) => this.getItemKey(i, table) === itemKey);
406
+ if (item) {
407
+ foundItems.push(this.marshallItem(item, table));
408
+ }
409
+ }
410
+
411
+ responses[tableName] = { Items: foundItems };
412
+ }
413
+
414
+ return { Responses: responses };
415
+ }
416
+
417
+ query(params) {
418
+ const { TableName, KeyConditionExpression, ExpressionAttributeValues, IndexName } = params;
419
+ const table = this.tables.get(TableName);
420
+
421
+ if (!table) {
422
+ throw new Error(`Table ${TableName} does not exist`);
423
+ }
424
+
425
+ let items = this.store.read(TableName);
426
+
427
+ // Filtra pela chave de partição
428
+ const hashKey = table.hashKey;
429
+ const hashValueMatch = KeyConditionExpression.match(new RegExp(`${hashKey}\\s*=\\s*([^\\s]+)`));
430
+
431
+ if (hashValueMatch) {
432
+ const hashValuePlaceholder = hashValueMatch[1];
433
+ const rawHashValue = ExpressionAttributeValues[hashValuePlaceholder];
434
+ const hashValue = rawHashValue && typeof rawHashValue === 'object' ? Object.values(rawHashValue)[0] : rawHashValue;
435
+ items = items.filter((item) => item[hashKey] === hashValue);
436
+ }
437
+
438
+ // Filtra pela chave de ordenação se existir
439
+ if (table.rangeKey) {
440
+ const rangeKey = table.rangeKey;
441
+ const rangeConditionMatch = KeyConditionExpression.match(new RegExp(`${rangeKey}\\s*(=|>|<|>=|<=)\\s*([^\\s]+)`));
442
+
443
+ if (rangeConditionMatch) {
444
+ const operator = rangeConditionMatch[1];
445
+ const rangeValuePlaceholder = rangeConditionMatch[2];
446
+ const rawRangeValue = ExpressionAttributeValues[rangeValuePlaceholder];
447
+ const rangeValue = rawRangeValue && typeof rawRangeValue === 'object' ? Object.values(rawRangeValue)[0] : rawRangeValue;
448
+
449
+ items = items.filter((item) => {
450
+ const itemValue = item[rangeKey];
451
+ switch (operator) {
452
+ case "=":
453
+ return itemValue === rangeValue;
454
+ case ">":
455
+ return itemValue > rangeValue;
456
+ case "<":
457
+ return itemValue < rangeValue;
458
+ case ">=":
459
+ return itemValue >= rangeValue;
460
+ case "<=":
461
+ return itemValue <= rangeValue;
462
+ default:
463
+ return true;
464
+ }
465
+ });
466
+ }
467
+ }
468
+
469
+ const marshalledItems = items.map((item) => this.marshallItem(item, table));
470
+
471
+ return {
472
+ Items: marshalledItems,
473
+ Count: marshalledItems.length,
474
+ ScannedCount: items.length,
475
+ };
476
+ }
477
+
478
+ scan(params) {
479
+ const { TableName, FilterExpression, ExpressionAttributeValues, Limit } = params;
480
+ const table = this.tables.get(TableName);
481
+
482
+ if (!table) {
483
+ throw new Error(`Table ${TableName} does not exist`);
484
+ }
485
+
486
+ let items = this.store.read(TableName);
487
+
488
+ // Aplica filtro se existir
489
+ if (FilterExpression) {
490
+ items = this.applyFilter(items, FilterExpression, ExpressionAttributeValues, table);
491
+ }
492
+
493
+ // Aplica limite
494
+ if (Limit && items.length > Limit) {
495
+ items = items.slice(0, Limit);
496
+ }
497
+
498
+ const marshalledItems = items.map((item) => this.marshallItem(item, table));
499
+
500
+ return {
501
+ Items: marshalledItems,
502
+ Count: marshalledItems.length,
503
+ ScannedCount: items.length,
504
+ };
505
+ }
506
+
507
+ // Métodos auxiliares
508
+ normalizeItem(item, table) {
509
+ const normalized = { ...item };
510
+
511
+ // Remove os tipos do DynamoDB (S, N, etc)
512
+ for (const [key, value] of Object.entries(normalized)) {
513
+ if (value && typeof value === "object") {
514
+ if (value.S !== undefined) normalized[key] = value.S;
515
+ else if (value.N !== undefined) normalized[key] = parseFloat(value.N);
516
+ else if (value.BOOL !== undefined) normalized[key] = value.BOOL;
517
+ else if (value.L !== undefined) normalized[key] = value.L.map((v) => this.normalizeItem(v, table));
518
+ else if (value.M !== undefined) normalized[key] = this.normalizeItem(value.M, table);
519
+ }
520
+ }
521
+
522
+ return normalized;
523
+ }
524
+
525
+ marshallItem(item, table) {
526
+ const marshalled = {};
527
+
528
+ for (const [key, value] of Object.entries(item)) {
529
+ if (key.startsWith("_")) continue; // Pula campos internos
530
+
531
+ const type = table.attributeTypes[key];
532
+ if (type === "S") {
533
+ marshalled[key] = { S: String(value) };
534
+ } else if (type === "N") {
535
+ marshalled[key] = { N: String(value) };
536
+ } else if (type === "BOOL") {
537
+ marshalled[key] = { BOOL: Boolean(value) };
538
+ } else if (Array.isArray(value)) {
539
+ marshalled[key] = { L: value.map((v) => ({ S: String(v) })) };
540
+ } else if (typeof value === "object") {
541
+ marshalled[key] = { M: this.marshallItem(value, table) };
542
+ } else {
543
+ marshalled[key] = { S: String(value) };
544
+ }
545
+ }
546
+
547
+ return marshalled;
548
+ }
549
+
550
+ getItemKey(item, table) {
551
+ const hashValue = item[table.hashKey];
552
+ const rangeValue = table.rangeKey ? item[table.rangeKey] : null;
553
+ return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
554
+ }
555
+
556
+ getItemKeyFromKeys(keys, table) {
557
+ const rawHash = keys[table.hashKey];
558
+ const hashValue = rawHash && typeof rawHash === 'object' ? Object.values(rawHash)[0] : rawHash;
559
+ const rawRange = table.rangeKey ? keys[table.rangeKey] : null;
560
+ const rangeValue = rawRange && typeof rawRange === 'object' ? Object.values(rawRange)[0] : rawRange;
561
+ return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
562
+ }
563
+
564
+ processUpdateExpression(item, expression, nameMap, valueMap, table) {
565
+ // Implementação simplificada - expandir conforme necessário
566
+ const setMatch = expression.match(/SET\s+([^]+?)(?=\s+(?:REMOVE|ADD|DELETE)|\s*$)/i);
567
+
568
+ if (setMatch) {
569
+ const assignments = setMatch[1].split(",").map((a) => a.trim());
570
+
571
+ for (const assignment of assignments) {
572
+ const [path, valueExpr] = assignment.split("=").map((s) => s.trim());
573
+ const attributeName = path.replace(/#/g, "");
574
+ const rawValue = valueMap[valueExpr];
575
+ const value = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
576
+
577
+ item[attributeName] = value;
578
+ }
579
+ }
580
+ }
581
+
582
+ applyFilter(items, expression, values, table) {
583
+ // Implementação simplificada
584
+ return items.filter((item) => {
585
+ const match = expression.match(/([^\s]+)\s*=\s*([^\s]+)/);
586
+ if (match) {
587
+ const [, attribute, placeholder] = match;
588
+ const rawValue = values[placeholder];
589
+ const expectedValue = rawValue && typeof rawValue === 'object' ? Object.values(rawValue)[0] : rawValue;
590
+ const actualValue = item[attribute];
591
+ return actualValue === expectedValue;
592
+ }
593
+ return true;
594
+ });
595
+ }
596
+
597
+ persistTables() {
598
+ const tablesObj = {};
599
+ for (const [name, table] of this.tables.entries()) {
600
+ tablesObj[name] = table;
601
+ }
602
+ this.store.write("__tables__", tablesObj);
603
+ }
604
+
605
+ async reset() {
606
+ for (const [tableName] of this.tables) {
607
+ this.store.write(tableName, []);
608
+ }
609
+ logger.debug("DynamoDB: Todos os dados resetados");
610
+ }
611
+
612
+ getTablesCount() {
613
+ return this.tables.size;
614
+ }
615
+
616
+ getTotalItems() {
617
+ let total = 0;
618
+ for (const [tableName] of this.tables) {
619
+ const items = this.store.read(tableName);
620
+ total += items.length;
621
+ }
622
+ return total;
623
+ }
624
+
625
+ listTables() {
626
+ return { TableNames: Array.from(this.tables.keys()) };
627
+ }
628
+ }
629
+
630
+ module.exports = DynamoDBSimulator;