@gugananuvem/aws-local-simulator 1.0.33 → 1.0.34
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/README.md +834 -834
- package/aws-config +153 -153
- package/bin/aws-local-simulator.js +63 -63
- package/package.json +3 -2
- package/src/config/config-loader.js +114 -114
- package/src/config/default-config.js +79 -79
- package/src/config/env-loader.js +68 -68
- package/src/index.js +146 -146
- package/src/index.mjs +123 -123
- package/src/server.js +463 -463
- package/src/services/apigateway/index.js +75 -75
- package/src/services/apigateway/server.js +607 -607
- package/src/services/apigateway/simulator.js +1405 -1405
- package/src/services/athena/index.js +75 -75
- package/src/services/athena/server.js +101 -101
- package/src/services/athena/simulador.js +998 -998
- package/src/services/athena/simulator.js +346 -346
- package/src/services/cloudformation/index.js +106 -106
- package/src/services/cloudformation/server.js +417 -417
- package/src/services/cloudformation/simulador.js +1020 -1020
- package/src/services/cloudtrail/index.js +84 -84
- package/src/services/cloudtrail/server.js +235 -235
- package/src/services/cloudtrail/simulador.js +719 -719
- package/src/services/cloudwatch/index.js +84 -84
- package/src/services/cloudwatch/server.js +366 -366
- package/src/services/cloudwatch/simulador.js +1173 -1173
- package/src/services/cognito/index.js +79 -79
- package/src/services/cognito/server.js +297 -297
- package/src/services/cognito/simulator.js +1992 -1761
- package/src/services/config/index.js +96 -96
- package/src/services/config/server.js +215 -215
- package/src/services/config/simulador.js +1260 -1260
- package/src/services/dynamodb/index.js +74 -74
- package/src/services/dynamodb/server.js +139 -139
- package/src/services/dynamodb/simulator.js +1005 -994
- package/src/services/dynamodb/sqlite-store.js +722 -0
- package/src/services/ecs/index.js +65 -65
- package/src/services/ecs/server.js +235 -235
- package/src/services/ecs/simulator.js +844 -844
- package/src/services/eventbridge/index.js +89 -89
- package/src/services/eventbridge/server.js +209 -209
- package/src/services/eventbridge/simulator.js +684 -684
- package/src/services/index.js +45 -45
- package/src/services/kms/index.js +75 -75
- package/src/services/kms/server.js +81 -81
- package/src/services/kms/simulator.js +344 -344
- package/src/services/lambda/handler-loader.js +183 -183
- package/src/services/lambda/index.js +81 -81
- package/src/services/lambda/route-registry.js +274 -274
- package/src/services/lambda/server.js +191 -191
- package/src/services/lambda/simulator.js +364 -364
- package/src/services/parameter-store/index.js +80 -80
- package/src/services/parameter-store/server.js +50 -50
- package/src/services/parameter-store/simulator.js +201 -201
- package/src/services/s3/index.js +73 -73
- package/src/services/s3/server.js +350 -350
- package/src/services/s3/simulator.js +568 -568
- package/src/services/secret-manager/index.js +80 -80
- package/src/services/secret-manager/server.js +51 -51
- package/src/services/secret-manager/simulator.js +182 -182
- package/src/services/sns/index.js +89 -89
- package/src/services/sns/server.js +607 -607
- package/src/services/sns/simulator.js +1482 -1482
- package/src/services/sqs/index.js +98 -98
- package/src/services/sqs/server.js +360 -360
- package/src/services/sqs/simulator.js +509 -509
- package/src/services/sts/index.js +37 -37
- package/src/services/sts/server.js +144 -144
- package/src/services/sts/simulator.js +69 -69
- package/src/services/xray/index.js +83 -83
- package/src/services/xray/server.js +308 -308
- package/src/services/xray/simulador.js +994 -994
- package/src/template/aws-config-template.js +87 -87
- package/src/template/aws-config-template.mjs +90 -90
- package/src/template/config-template.json +203 -203
- package/src/utils/aws-config.js +91 -91
- package/src/utils/cloudtrail-audit.js +129 -129
- package/src/utils/local-store.js +83 -83
- package/src/utils/logger.js +59 -59
|
@@ -1,994 +1,1005 @@
|
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
this.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
this._writeLocks
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
AttributeDefinitions
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
case "
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
items[index]
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
this.
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
return
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
if (value
|
|
811
|
-
if (
|
|
812
|
-
if (
|
|
813
|
-
if (
|
|
814
|
-
if (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1
|
+
/**
|
|
2
|
+
* DynamoDB Simulator Core
|
|
3
|
+
*/
|
|
4
|
+
const SQLiteStore = require('./sqlite-store');
|
|
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
|
+
this.useSQLite = process.env.DYNAMODB_USE_SQLITE == 'true'; // Default false
|
|
16
|
+
|
|
17
|
+
if (!dataDir) {
|
|
18
|
+
throw new Error("AWS_LOCAL_SIMULATOR_DATA_DIR not set");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
this.dataDir = path.join(dataDir, "dynamodb");
|
|
22
|
+
|
|
23
|
+
// Escolhe o store baseado na configuração
|
|
24
|
+
if (this.useSQLite) {
|
|
25
|
+
this.store = new SQLiteStore(this.dataDir);
|
|
26
|
+
logger.info("📦 Usando SQLite Store para persistência");
|
|
27
|
+
} else {
|
|
28
|
+
this.store = new LocalStore(this.dataDir);
|
|
29
|
+
logger.info("📄 Usando JSON Store (legacy) para persistência");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
this.tables = new Map();
|
|
34
|
+
this.audit = new CloudTrailAudit("dynamodb.amazonaws.com");
|
|
35
|
+
// Mutex por tabela para evitar race condition em escritas concorrentes
|
|
36
|
+
this._writeLocks = new Map();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Executa fn com exclusão mútua por tableName.
|
|
41
|
+
* Garante que escritas na mesma tabela não se sobreponham.
|
|
42
|
+
*/
|
|
43
|
+
_withTableLock(tableName, fn) {
|
|
44
|
+
const prev = this._writeLocks.get(tableName) || Promise.resolve();
|
|
45
|
+
const next = prev.then(() => fn());
|
|
46
|
+
// Guarda apenas a tail da cadeia (sem acumular referências)
|
|
47
|
+
this._writeLocks.set(tableName, next.catch(() => { }));
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
async initialize() {
|
|
51
|
+
logger.debug("Inicializando DynamoDB Simulator...");
|
|
52
|
+
this.loadTables();
|
|
53
|
+
this._watchTablesFile();
|
|
54
|
+
logger.debug(`✅ DynamoDB Simulator inicializado com ${this.tables.size} tabelas`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_watchTablesFile() {
|
|
58
|
+
const fs = require("fs");
|
|
59
|
+
const tablesFilePath = this.store.getFilePath("__tables__");
|
|
60
|
+
if (!fs.existsSync(tablesFilePath)) return;
|
|
61
|
+
|
|
62
|
+
let reloadTimeout = null;
|
|
63
|
+
fs.watch(tablesFilePath, (eventType) => {
|
|
64
|
+
if (eventType !== "change") return;
|
|
65
|
+
clearTimeout(reloadTimeout);
|
|
66
|
+
reloadTimeout = setTimeout(() => {
|
|
67
|
+
try {
|
|
68
|
+
this.tables.clear();
|
|
69
|
+
const savedTables = this.store.read("__tables__");
|
|
70
|
+
if (savedTables) {
|
|
71
|
+
for (const [name, definition] of Object.entries(savedTables)) {
|
|
72
|
+
this.tables.set(name, definition);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
logger.info(`🔄 DynamoDB schema recarregado (${this.tables.size} tabelas)`);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
logger.warn(`⚠️ Erro ao recarregar schema: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
}, 200);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
logger.debug(`👁️ Watching: ${tablesFilePath}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
loadTables() {
|
|
86
|
+
// Carrega tabelas existentes do disco PRIMEIRO para evitar sobrescrever definições persistidas
|
|
87
|
+
const savedTables = this.store.read("__tables__");
|
|
88
|
+
if (savedTables) {
|
|
89
|
+
for (const [name, definition] of Object.entries(savedTables)) {
|
|
90
|
+
this.tables.set(name, definition);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Cria tabelas da configuração ou atualiza schema (GSIs/attributeTypes) se já existirem
|
|
95
|
+
if (this.config.dynamodb?.tables) {
|
|
96
|
+
for (const tableDef of this.config.dynamodb.tables) {
|
|
97
|
+
const { TableName, AttributeDefinitions, GlobalSecondaryIndexes } = tableDef;
|
|
98
|
+
if (this.tables.has(TableName)) {
|
|
99
|
+
// Tabela já existe no disco — atualiza schema sem apagar dados
|
|
100
|
+
const existing = this.tables.get(TableName);
|
|
101
|
+
|
|
102
|
+
if (AttributeDefinitions) {
|
|
103
|
+
const attributeTypes = {};
|
|
104
|
+
AttributeDefinitions.forEach((attr) => {
|
|
105
|
+
attributeTypes[attr.AttributeName] = attr.AttributeType;
|
|
106
|
+
});
|
|
107
|
+
existing.attributeTypes = attributeTypes;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (GlobalSecondaryIndexes) {
|
|
111
|
+
const globalSecondaryIndexes = {};
|
|
112
|
+
for (const gsi of GlobalSecondaryIndexes) {
|
|
113
|
+
const gsiHashKey = gsi.KeySchema.find((k) => k.KeyType === "HASH").AttributeName;
|
|
114
|
+
const gsiRangeKey = gsi.KeySchema.find((k) => k.KeyType === "RANGE")?.AttributeName;
|
|
115
|
+
globalSecondaryIndexes[gsi.IndexName] = { hashKey: gsiHashKey, rangeKey: gsiRangeKey };
|
|
116
|
+
}
|
|
117
|
+
existing.globalSecondaryIndexes = globalSecondaryIndexes;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.tables.set(TableName, existing);
|
|
121
|
+
} else {
|
|
122
|
+
this.createTable(tableDef);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
this.persistTables();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
createTable(params) {
|
|
130
|
+
const { TableName, KeySchema, AttributeDefinitions, ProvisionedThroughput, GlobalSecondaryIndexes } = params;
|
|
131
|
+
|
|
132
|
+
if (this.tables.has(TableName)) {
|
|
133
|
+
logger.warn(`Tabela ${TableName} já existe`);
|
|
134
|
+
return { TableDescription: { TableName, TableStatus: "ACTIVE" } };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const hashKey = KeySchema.find((k) => k.KeyType === "HASH").AttributeName;
|
|
138
|
+
const rangeKey = KeySchema.find((k) => k.KeyType === "RANGE")?.AttributeName;
|
|
139
|
+
|
|
140
|
+
const attributeTypes = {};
|
|
141
|
+
AttributeDefinitions.forEach((attr) => {
|
|
142
|
+
attributeTypes[attr.AttributeName] = attr.AttributeType;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const globalSecondaryIndexes = {};
|
|
146
|
+
if (GlobalSecondaryIndexes) {
|
|
147
|
+
for (const gsi of GlobalSecondaryIndexes) {
|
|
148
|
+
const gsiHashKey = gsi.KeySchema.find((k) => k.KeyType === "HASH").AttributeName;
|
|
149
|
+
const gsiRangeKey = gsi.KeySchema.find((k) => k.KeyType === "RANGE")?.AttributeName;
|
|
150
|
+
globalSecondaryIndexes[gsi.IndexName] = { hashKey: gsiHashKey, rangeKey: gsiRangeKey };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const table = {
|
|
155
|
+
name: TableName,
|
|
156
|
+
hashKey,
|
|
157
|
+
rangeKey,
|
|
158
|
+
attributeTypes,
|
|
159
|
+
globalSecondaryIndexes,
|
|
160
|
+
createdAt: new Date().toISOString(),
|
|
161
|
+
itemCount: 0,
|
|
162
|
+
sizeBytes: 0,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
this.tables.set(TableName, table);
|
|
166
|
+
this.persistTables();
|
|
167
|
+
|
|
168
|
+
// Inicializa arquivo de dados apenas se não existir (preserva dados entre reinicializações)
|
|
169
|
+
if (!this.store.exists(TableName)) {
|
|
170
|
+
this.store.write(TableName, []);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
logger.debug(`✅ Tabela criada: ${TableName}`);
|
|
174
|
+
this.audit.record({ eventName: "CreateTable", readOnly: false, resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${TableName}`, type: "AWS::DynamoDB::Table" }], requestParameters: { tableName: TableName } });
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
TableDescription: {
|
|
178
|
+
TableName,
|
|
179
|
+
TableStatus: "ACTIVE",
|
|
180
|
+
CreationDateTime: new Date().toISOString(),
|
|
181
|
+
KeySchema,
|
|
182
|
+
AttributeDefinitions,
|
|
183
|
+
ProvisionedThroughput: ProvisionedThroughput || {
|
|
184
|
+
ReadCapacityUnits: 5,
|
|
185
|
+
WriteCapacityUnits: 5,
|
|
186
|
+
},
|
|
187
|
+
ItemCount: 0,
|
|
188
|
+
TableSizeBytes: 0,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async handleRequest(target, params) {
|
|
194
|
+
const action = target.split(".")[1];
|
|
195
|
+
|
|
196
|
+
logger.verboso(`DynamoDB Action: ${action}`, params);
|
|
197
|
+
|
|
198
|
+
const readActions = new Set(["GetItem", "BatchGetItem", "Query", "Scan", "DescribeTable", "ListTables"]);
|
|
199
|
+
const dataActions = new Set(["PutItem", "GetItem", "UpdateItem", "DeleteItem", "BatchWriteItem", "BatchGetItem", "Query", "Scan"]);
|
|
200
|
+
|
|
201
|
+
const result = (() => {
|
|
202
|
+
switch (action) {
|
|
203
|
+
case "CreateTable": return this.createTable(params);
|
|
204
|
+
case "DescribeTable": return this.describeTable(params.TableName);
|
|
205
|
+
case "ListTables": return this.listTables(params);
|
|
206
|
+
case "DeleteTable": return this.deleteTable(params);
|
|
207
|
+
case "PutItem": return this._withTableLock(params.TableName, () => this.putItem(params));
|
|
208
|
+
case "GetItem": return this.getItem(params);
|
|
209
|
+
case "UpdateItem": return this._withTableLock(params.TableName, () => this.updateItem(params));
|
|
210
|
+
case "DeleteItem": return this._withTableLock(params.TableName, () => this.deleteItem(params));
|
|
211
|
+
case "BatchWriteItem": return this.batchWriteItem(params);
|
|
212
|
+
case "BatchGetItem": return this.batchGetItem(params);
|
|
213
|
+
case "Query": return this.query(params);
|
|
214
|
+
case "Scan": return this.scan(params);
|
|
215
|
+
default: throw new Error(`Unsupported action: ${action}`);
|
|
216
|
+
}
|
|
217
|
+
})();
|
|
218
|
+
|
|
219
|
+
const tableName = params.TableName;
|
|
220
|
+
if (tableName) {
|
|
221
|
+
this.audit.record({
|
|
222
|
+
eventName: action,
|
|
223
|
+
readOnly: readActions.has(action),
|
|
224
|
+
isDataEvent: dataActions.has(action),
|
|
225
|
+
resources: [{ ARN: `arn:aws:dynamodb:local:000000000000:table/${tableName}`, type: "AWS::DynamoDB::Table" }],
|
|
226
|
+
requestParameters: { tableName },
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
describeTable(tableName) {
|
|
234
|
+
const table = this.tables.get(tableName);
|
|
235
|
+
if (!table) {
|
|
236
|
+
throw new Error(`Table ${tableName} does not exist`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const items = this.store.read(tableName);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
Table: {
|
|
243
|
+
TableName: table.name,
|
|
244
|
+
TableStatus: "ACTIVE",
|
|
245
|
+
CreationDateTime: table.createdAt,
|
|
246
|
+
KeySchema: [{ AttributeName: table.hashKey, KeyType: "HASH" }, ...(table.rangeKey ? [{ AttributeName: table.rangeKey, KeyType: "RANGE" }] : [])],
|
|
247
|
+
AttributeDefinitions: Object.entries(table.attributeTypes).map(([name, type]) => ({
|
|
248
|
+
AttributeName: name,
|
|
249
|
+
AttributeType: type,
|
|
250
|
+
})),
|
|
251
|
+
ItemCount: items.length,
|
|
252
|
+
TableSizeBytes: JSON.stringify(items).length,
|
|
253
|
+
GlobalSecondaryIndexes: Object.entries(table.globalSecondaryIndexes || {}).map(([indexName, gsi]) => ({
|
|
254
|
+
IndexName: indexName,
|
|
255
|
+
IndexStatus: "ACTIVE",
|
|
256
|
+
KeySchema: [
|
|
257
|
+
{ AttributeName: gsi.hashKey, KeyType: "HASH" },
|
|
258
|
+
...(gsi.rangeKey ? [{ AttributeName: gsi.rangeKey, KeyType: "RANGE" }] : []),
|
|
259
|
+
],
|
|
260
|
+
Projection: { ProjectionType: "ALL" },
|
|
261
|
+
ProvisionedThroughput: {
|
|
262
|
+
ReadCapacityUnits: 5,
|
|
263
|
+
WriteCapacityUnits: 5,
|
|
264
|
+
},
|
|
265
|
+
})),
|
|
266
|
+
ProvisionedThroughput: {
|
|
267
|
+
ReadCapacityUnits: 5,
|
|
268
|
+
WriteCapacityUnits: 5,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
listTables(params = {}) {
|
|
275
|
+
const tableNames = Array.from(this.tables.keys());
|
|
276
|
+
const { Limit = 100, ExclusiveStartTableName } = params;
|
|
277
|
+
|
|
278
|
+
let startIndex = 0;
|
|
279
|
+
if (ExclusiveStartTableName) {
|
|
280
|
+
const index = tableNames.indexOf(ExclusiveStartTableName);
|
|
281
|
+
if (index !== -1) startIndex = index + 1;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const result = tableNames.slice(startIndex, startIndex + Limit);
|
|
285
|
+
|
|
286
|
+
return {
|
|
287
|
+
TableNames: result,
|
|
288
|
+
LastEvaluatedTableName: result.length === Limit ? result[result.length - 1] : undefined,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
deleteTable(params) {
|
|
293
|
+
const { TableName } = params;
|
|
294
|
+
|
|
295
|
+
if (!this.tables.has(TableName)) {
|
|
296
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
this.tables.delete(TableName);
|
|
300
|
+
this.store.delete(TableName);
|
|
301
|
+
this.persistTables();
|
|
302
|
+
|
|
303
|
+
return { TableDescription: { TableName, TableStatus: "DELETING" } };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
putItem(params) {
|
|
307
|
+
const { TableName, Item, ReturnValues = "NONE" } = params;
|
|
308
|
+
const table = this.tables.get(TableName);
|
|
309
|
+
|
|
310
|
+
if (!table) {
|
|
311
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Normaliza o item
|
|
315
|
+
const normalizedItem = this.normalizeItem(Item, table);
|
|
316
|
+
normalizedItem._createdAt = normalizedItem._createdAt || new Date().toISOString();
|
|
317
|
+
normalizedItem._updatedAt = new Date().toISOString();
|
|
318
|
+
|
|
319
|
+
// Carrega dados existentes
|
|
320
|
+
let items = this.store.read(TableName);
|
|
321
|
+
const itemKey = this.getItemKey(normalizedItem, table);
|
|
322
|
+
|
|
323
|
+
// Encontra e substitui ou adiciona
|
|
324
|
+
const existingIndex = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
|
|
325
|
+
|
|
326
|
+
let oldItem = null;
|
|
327
|
+
if (existingIndex !== -1) {
|
|
328
|
+
oldItem = { ...items[existingIndex] };
|
|
329
|
+
items[existingIndex] = normalizedItem;
|
|
330
|
+
} else {
|
|
331
|
+
items.push(normalizedItem);
|
|
332
|
+
table.itemCount++;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Salva no store
|
|
336
|
+
this.store.write(TableName, items);
|
|
337
|
+
this.persistTables();
|
|
338
|
+
|
|
339
|
+
logger.verboso(`PutItem: ${TableName}/${itemKey}`);
|
|
340
|
+
|
|
341
|
+
const response = {};
|
|
342
|
+
if (ReturnValues === "ALL_OLD" && oldItem) {
|
|
343
|
+
response.Attributes = this.marshallItem(oldItem, table);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return response;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getItem(params) {
|
|
350
|
+
const { TableName, Key } = params;
|
|
351
|
+
const table = this.tables.get(TableName);
|
|
352
|
+
|
|
353
|
+
if (!table) {
|
|
354
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const items = this.store.read(TableName);
|
|
358
|
+
const itemKey = this.getItemKeyFromKeys(Key, table);
|
|
359
|
+
|
|
360
|
+
const item = items.find((item) => this.getItemKey(item, table) === itemKey);
|
|
361
|
+
|
|
362
|
+
logger.verboso(`GetItem: ${TableName}/${itemKey} - ${item ? "found" : "not found"}`);
|
|
363
|
+
|
|
364
|
+
return item ? { Item: this.marshallItem(item, table) } : {};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
updateItem(params) {
|
|
368
|
+
const { TableName, Key, UpdateExpression, ExpressionAttributeNames = {}, ExpressionAttributeValues = {}, ReturnValues = "NONE" } = params;
|
|
369
|
+
const table = this.tables.get(TableName);
|
|
370
|
+
|
|
371
|
+
if (!table) {
|
|
372
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Busca o item atual (upsert: cria se não existir, como o DynamoDB real)
|
|
376
|
+
const items = this.store.read(TableName);
|
|
377
|
+
const itemKey = this.getItemKeyFromKeys(Key, table);
|
|
378
|
+
const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
|
|
379
|
+
|
|
380
|
+
// Se não existe, cria um novo item com as chaves fornecidas
|
|
381
|
+
if (index === -1) {
|
|
382
|
+
const newItem = this.normalizeItem(Key, table);
|
|
383
|
+
newItem._createdAt = new Date().toISOString();
|
|
384
|
+
newItem._updatedAt = new Date().toISOString();
|
|
385
|
+
if (UpdateExpression) {
|
|
386
|
+
this.processUpdateExpression(newItem, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues, table);
|
|
387
|
+
}
|
|
388
|
+
items.push(newItem);
|
|
389
|
+
this.store.write(TableName, items);
|
|
390
|
+
logger.verboso(`UpdateItem (upsert): ${TableName}/${itemKey}`);
|
|
391
|
+
const response = {};
|
|
392
|
+
if (ReturnValues === "ALL_NEW" || ReturnValues === "UPDATED_NEW") {
|
|
393
|
+
response.Attributes = this.marshallItem(newItem, table);
|
|
394
|
+
}
|
|
395
|
+
return response;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const currentItem = items[index];
|
|
399
|
+
const updatedItem = { ...currentItem };
|
|
400
|
+
updatedItem._updatedAt = new Date().toISOString();
|
|
401
|
+
|
|
402
|
+
// Processa a UpdateExpression
|
|
403
|
+
if (UpdateExpression) {
|
|
404
|
+
this.processUpdateExpression(updatedItem, UpdateExpression, ExpressionAttributeNames, ExpressionAttributeValues, table);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Salva o item atualizado
|
|
408
|
+
const oldItem = { ...items[index] };
|
|
409
|
+
items[index] = updatedItem;
|
|
410
|
+
this.store.write(TableName, items);
|
|
411
|
+
|
|
412
|
+
logger.verboso(`UpdateItem: ${TableName}/${itemKey}`);
|
|
413
|
+
|
|
414
|
+
const response = {};
|
|
415
|
+
switch (ReturnValues) {
|
|
416
|
+
case "ALL_OLD":
|
|
417
|
+
response.Attributes = this.marshallItem(oldItem, table);
|
|
418
|
+
break;
|
|
419
|
+
case "ALL_NEW":
|
|
420
|
+
case "UPDATED_NEW":
|
|
421
|
+
response.Attributes = this.marshallItem(updatedItem, table);
|
|
422
|
+
break;
|
|
423
|
+
case "UPDATED_OLD":
|
|
424
|
+
response.Attributes = this.marshallItem(oldItem, table);
|
|
425
|
+
break;
|
|
426
|
+
default:
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return response;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
deleteItem(params) {
|
|
434
|
+
const { TableName, Key, ReturnValues = "NONE" } = params;
|
|
435
|
+
const table = this.tables.get(TableName);
|
|
436
|
+
|
|
437
|
+
if (!table) {
|
|
438
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const items = this.store.read(TableName);
|
|
442
|
+
const itemKey = this.getItemKeyFromKeys(Key, table);
|
|
443
|
+
const index = items.findIndex((item) => this.getItemKey(item, table) === itemKey);
|
|
444
|
+
|
|
445
|
+
if (index === -1) {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const oldItem = { ...items[index] };
|
|
450
|
+
items.splice(index, 1);
|
|
451
|
+
this.store.write(TableName, items);
|
|
452
|
+
table.itemCount--;
|
|
453
|
+
this.persistTables();
|
|
454
|
+
|
|
455
|
+
logger.verboso(`DeleteItem: ${TableName}/${itemKey}`);
|
|
456
|
+
|
|
457
|
+
const response = {};
|
|
458
|
+
if (ReturnValues === "ALL_OLD") {
|
|
459
|
+
response.Attributes = this.marshallItem(oldItem, table);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return response;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
batchWriteItem(params) {
|
|
466
|
+
const { RequestItems } = params;
|
|
467
|
+
const responses = {};
|
|
468
|
+
|
|
469
|
+
if (!RequestItems) {
|
|
470
|
+
logger.debug(`[DEBUG batchWriteItem] params recebido: ${JSON.stringify(params)}`);
|
|
471
|
+
throw new Error(`RequestItems is required for BatchWriteItem. Params received: ${JSON.stringify(params)}`);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Serializa por tabela usando o mutex para evitar race condition
|
|
475
|
+
const tablePromises = Object.entries(RequestItems).map(([tableName, operations]) =>
|
|
476
|
+
this._withTableLock(tableName, () => {
|
|
477
|
+
const table = this.tables.get(tableName);
|
|
478
|
+
if (!table) {
|
|
479
|
+
logger.info(`[BATCH-DEBUG] tabela não encontrada: ${tableName}`);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const itemsBefore = this.store.read(tableName);
|
|
484
|
+
logger.info(`[BATCH-DEBUG] ${tableName} | antes=${itemsBefore.length} | ops=${operations.length}`);
|
|
485
|
+
|
|
486
|
+
let items = [...itemsBefore];
|
|
487
|
+
const unprocessedItems = [];
|
|
488
|
+
let inserts = 0;
|
|
489
|
+
let updates = 0;
|
|
490
|
+
|
|
491
|
+
for (const op of operations) {
|
|
492
|
+
if (op.PutRequest) {
|
|
493
|
+
const item = this.normalizeItem(op.PutRequest.Item, table);
|
|
494
|
+
const itemKey = this.getItemKey(item, table);
|
|
495
|
+
const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
|
|
496
|
+
|
|
497
|
+
if (index !== -1) {
|
|
498
|
+
items[index] = item;
|
|
499
|
+
updates++;
|
|
500
|
+
} else {
|
|
501
|
+
items.push(item);
|
|
502
|
+
table.itemCount++;
|
|
503
|
+
inserts++;
|
|
504
|
+
}
|
|
505
|
+
} else if (op.DeleteRequest) {
|
|
506
|
+
const key = op.DeleteRequest.Key;
|
|
507
|
+
const itemKey = this.getItemKeyFromKeys(key, table);
|
|
508
|
+
const index = items.findIndex((i) => this.getItemKey(i, table) === itemKey);
|
|
509
|
+
|
|
510
|
+
if (index !== -1) {
|
|
511
|
+
items.splice(index, 1);
|
|
512
|
+
table.itemCount--;
|
|
513
|
+
} else {
|
|
514
|
+
unprocessedItems.push(op);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
this.store.write(tableName, items);
|
|
520
|
+
const itemsAfter = this.store.read(tableName);
|
|
521
|
+
logger.info(`[BATCH-DEBUG] ${tableName} | depois=${itemsAfter.length} | esperado=${items.length} | match=${itemsAfter.length === items.length} | inserts=${inserts} | updates=${updates}`);
|
|
522
|
+
|
|
523
|
+
if (unprocessedItems.length > 0) {
|
|
524
|
+
responses[tableName] = unprocessedItems;
|
|
525
|
+
}
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
return Promise.all(tablePromises).then(() => {
|
|
530
|
+
this.persistTables();
|
|
531
|
+
const finalCount = this.store.read(Object.keys(RequestItems)[0]).length;
|
|
532
|
+
//logger.info(`[BATCH-DEBUG] FINAL | tabela=${Object.keys(RequestItems)[0]} | total=${finalCount}`);
|
|
533
|
+
return { UnprocessedItems: responses };
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
batchGetItem(params) {
|
|
538
|
+
const { RequestItems } = params;
|
|
539
|
+
const responses = {};
|
|
540
|
+
|
|
541
|
+
for (const [tableName, request] of Object.entries(RequestItems)) {
|
|
542
|
+
const table = this.tables.get(tableName);
|
|
543
|
+
if (!table) continue;
|
|
544
|
+
|
|
545
|
+
const items = this.store.read(tableName);
|
|
546
|
+
const { Keys } = request;
|
|
547
|
+
const foundItems = [];
|
|
548
|
+
|
|
549
|
+
for (const key of Keys) {
|
|
550
|
+
const itemKey = this.getItemKeyFromKeys(key, table);
|
|
551
|
+
const item = items.find((i) => this.getItemKey(i, table) === itemKey);
|
|
552
|
+
if (item) {
|
|
553
|
+
foundItems.push(this.marshallItem(item, table));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
responses[tableName] = { Items: foundItems };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { Responses: responses };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
query(params) {
|
|
564
|
+
const {
|
|
565
|
+
TableName,
|
|
566
|
+
KeyConditionExpression,
|
|
567
|
+
FilterExpression,
|
|
568
|
+
ExpressionAttributeValues,
|
|
569
|
+
ExpressionAttributeNames = {},
|
|
570
|
+
IndexName,
|
|
571
|
+
Limit,
|
|
572
|
+
ExclusiveStartKey,
|
|
573
|
+
ProjectionExpression,
|
|
574
|
+
ScanIndexForward = true
|
|
575
|
+
} = params;
|
|
576
|
+
const table = this.tables.get(TableName);
|
|
577
|
+
|
|
578
|
+
if (!table) {
|
|
579
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let items = this.store.read(TableName);
|
|
583
|
+
|
|
584
|
+
// Se for consulta por índice, filtra itens que não possuem as chaves do índice (Sparse Index)
|
|
585
|
+
if (IndexName && table.globalSecondaryIndexes?.[IndexName]) {
|
|
586
|
+
const gsi = table.globalSecondaryIndexes[IndexName];
|
|
587
|
+
items = items.filter(item => item[gsi.hashKey] !== undefined);
|
|
588
|
+
if (gsi.rangeKey) {
|
|
589
|
+
items = items.filter(item => item[gsi.rangeKey] !== undefined);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Helper para resolver nomes de atributos (que podem ser placeholders como #n0)
|
|
594
|
+
const resolveAttributeName = (name) => {
|
|
595
|
+
if (name.startsWith("#")) return ExpressionAttributeNames[name] || name;
|
|
596
|
+
return name;
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// Helper para extrair valor de placeholder (ex: :v0)
|
|
600
|
+
const resolveValue = (placeholder) => {
|
|
601
|
+
const rawValue = ExpressionAttributeValues[placeholder];
|
|
602
|
+
if (rawValue === undefined) return undefined;
|
|
603
|
+
if (rawValue !== null && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
|
|
604
|
+
const keys = Object.keys(rawValue);
|
|
605
|
+
if (keys.length === 1 && ["S", "N", "BOOL", "NULL", "M", "L", "SS", "NS", "BS"].includes(keys[0])) {
|
|
606
|
+
return this.normalizeValue(rawValue, table);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return rawValue;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
// Filtra pela KeyConditionExpression
|
|
613
|
+
if (KeyConditionExpression) {
|
|
614
|
+
const parts = KeyConditionExpression.split(/\s+AND\s+/i);
|
|
615
|
+
for (const part of parts) {
|
|
616
|
+
const trimmedPart = part.trim();
|
|
617
|
+
|
|
618
|
+
// Tenta match de função: begins_with(attr, :val)
|
|
619
|
+
const funcMatch = trimmedPart.match(/^begins_with\s*\(\s*([^\s,]+)\s*,\s*([^\s,)]+)\s*\)$/i);
|
|
620
|
+
if (funcMatch) {
|
|
621
|
+
const attributeName = resolveAttributeName(funcMatch[1]);
|
|
622
|
+
const expectedValue = resolveValue(funcMatch[2]);
|
|
623
|
+
items = items.filter(item => String(item[attributeName] || "").startsWith(String(expectedValue)));
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Tenta match de operador infix: attr OP :val
|
|
628
|
+
const match = trimmedPart.match(/([^\s]+)\s*(=|>|<|>=|<=|BEGINS_WITH|BETWEEN)\s*([^\s]+)(?:\s+AND\s+([^\s]+))?/i);
|
|
629
|
+
if (match) {
|
|
630
|
+
const attrPlaceholder = match[1];
|
|
631
|
+
const operator = match[2].toUpperCase();
|
|
632
|
+
const valPlaceholder = match[3];
|
|
633
|
+
const attributeName = resolveAttributeName(attrPlaceholder);
|
|
634
|
+
const expectedValue = resolveValue(valPlaceholder);
|
|
635
|
+
|
|
636
|
+
if (operator === "=") items = items.filter(item => item[attributeName] === expectedValue);
|
|
637
|
+
else if (operator === ">") items = items.filter(item => item[attributeName] > expectedValue);
|
|
638
|
+
else if (operator === "<") items = items.filter(item => item[attributeName] < expectedValue);
|
|
639
|
+
else if (operator === ">=") items = items.filter(item => item[attributeName] >= expectedValue);
|
|
640
|
+
else if (operator === "<=") items = items.filter(item => item[attributeName] <= expectedValue);
|
|
641
|
+
else if (operator === "BEGINS_WITH") {
|
|
642
|
+
const val = expectedValue;
|
|
643
|
+
items = items.filter(item => String(item[attributeName] || "").startsWith(String(val)));
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Ordenação (DynamoDB sempre ordena pela Sort Key)
|
|
650
|
+
let sortKey = table.rangeKey;
|
|
651
|
+
if (IndexName && table.globalSecondaryIndexes?.[IndexName]) {
|
|
652
|
+
sortKey = table.globalSecondaryIndexes[IndexName].rangeKey;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (sortKey) {
|
|
656
|
+
items.sort((a, b) => {
|
|
657
|
+
const valA = a[sortKey];
|
|
658
|
+
const valB = b[sortKey];
|
|
659
|
+
|
|
660
|
+
if (valA === valB) return 0;
|
|
661
|
+
if (valA === undefined || valA === null) return 1;
|
|
662
|
+
if (valB === undefined || valB === null) return -1;
|
|
663
|
+
|
|
664
|
+
let comparison = 0;
|
|
665
|
+
if (typeof valA === 'number' && typeof valB === 'number') {
|
|
666
|
+
comparison = valA - valB;
|
|
667
|
+
} else {
|
|
668
|
+
comparison = String(valA).localeCompare(String(valB));
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return ScanIndexForward ? comparison : -comparison;
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const scannedCount = items.length;
|
|
676
|
+
|
|
677
|
+
// Aplica FilterExpression se existir
|
|
678
|
+
if (FilterExpression) {
|
|
679
|
+
items = this.applyFilter(items, FilterExpression, ExpressionAttributeValues, ExpressionAttributeNames, table);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const totalMatchingCount = items.length;
|
|
683
|
+
|
|
684
|
+
// Apply Pagination (ExclusiveStartKey)
|
|
685
|
+
if (ExclusiveStartKey) {
|
|
686
|
+
const startKeyStr = this.getItemKeyFromKeys(ExclusiveStartKey, table);
|
|
687
|
+
const startIndex = items.findIndex(item => this.getItemKey(item, table) === startKeyStr);
|
|
688
|
+
if (startIndex !== -1) {
|
|
689
|
+
items = items.slice(startIndex + 1);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Apply Limit
|
|
694
|
+
let lastEvaluatedKey = null;
|
|
695
|
+
if (Limit && items.length > Limit) {
|
|
696
|
+
const lastItem = items[Limit - 1];
|
|
697
|
+
lastEvaluatedKey = this.marshallItem(lastItem, table);
|
|
698
|
+
items = items.slice(0, Limit);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
let marshalledItems = items.map((item) => this.marshallItem(item, table));
|
|
702
|
+
|
|
703
|
+
// Apply Projection
|
|
704
|
+
if (ProjectionExpression) {
|
|
705
|
+
marshalledItems = this.applyProjection(marshalledItems, ProjectionExpression, ExpressionAttributeNames);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return {
|
|
709
|
+
Items: marshalledItems,
|
|
710
|
+
Count: marshalledItems.length,
|
|
711
|
+
ScannedCount: scannedCount,
|
|
712
|
+
LastEvaluatedKey: lastEvaluatedKey || undefined
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
scan(params) {
|
|
717
|
+
const {
|
|
718
|
+
TableName,
|
|
719
|
+
FilterExpression,
|
|
720
|
+
ExpressionAttributeValues,
|
|
721
|
+
ExpressionAttributeNames = {},
|
|
722
|
+
Limit,
|
|
723
|
+
ExclusiveStartKey,
|
|
724
|
+
ProjectionExpression
|
|
725
|
+
} = params;
|
|
726
|
+
const table = this.tables.get(TableName);
|
|
727
|
+
|
|
728
|
+
if (!table) {
|
|
729
|
+
throw new Error(`Table ${TableName} does not exist`);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const allItems = this.store.read(TableName);
|
|
733
|
+
let items = allItems;
|
|
734
|
+
const scannedCount = items.length;
|
|
735
|
+
|
|
736
|
+
// Aplica filtro se existir
|
|
737
|
+
if (FilterExpression) {
|
|
738
|
+
items = this.applyFilter(items, FilterExpression, ExpressionAttributeValues, ExpressionAttributeNames, table);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Apply Pagination (ExclusiveStartKey)
|
|
742
|
+
if (ExclusiveStartKey) {
|
|
743
|
+
const startKeyStr = this.getItemKeyFromKeys(ExclusiveStartKey, table);
|
|
744
|
+
const startIndex = items.findIndex(item => this.getItemKey(item, table) === startKeyStr);
|
|
745
|
+
if (startIndex !== -1) {
|
|
746
|
+
items = items.slice(startIndex + 1);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Apply Limit
|
|
751
|
+
let lastEvaluatedKey = null;
|
|
752
|
+
if (Limit && items.length > Limit) {
|
|
753
|
+
const lastItem = items[Limit - 1];
|
|
754
|
+
lastEvaluatedKey = this.marshallItem(lastItem, table);
|
|
755
|
+
items = items.slice(0, Limit);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
let marshalledItems = items.map((item) => this.marshallItem(item, table));
|
|
759
|
+
|
|
760
|
+
// Apply Projection
|
|
761
|
+
if (ProjectionExpression) {
|
|
762
|
+
marshalledItems = this.applyProjection(marshalledItems, ProjectionExpression, ExpressionAttributeNames);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return {
|
|
766
|
+
Items: marshalledItems,
|
|
767
|
+
Count: marshalledItems.length,
|
|
768
|
+
ScannedCount: scannedCount,
|
|
769
|
+
LastEvaluatedKey: lastEvaluatedKey || undefined
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
applyProjection(items, expression, names = {}) {
|
|
774
|
+
const projectedAttrs = expression.split(',').map(s => s.trim()).filter(Boolean);
|
|
775
|
+
const resolvedAttrs = projectedAttrs.map(attr => {
|
|
776
|
+
if (attr.startsWith("#")) return names[attr] || attr;
|
|
777
|
+
return attr;
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
return items.map(item => {
|
|
781
|
+
const newItem = {};
|
|
782
|
+
resolvedAttrs.forEach(attr => {
|
|
783
|
+
if (item[attr] !== undefined) {
|
|
784
|
+
newItem[attr] = item[attr];
|
|
785
|
+
}
|
|
786
|
+
});
|
|
787
|
+
return newItem;
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
// Métodos auxiliares
|
|
793
|
+
normalizeItem(item, table) {
|
|
794
|
+
const normalized = { ...item };
|
|
795
|
+
|
|
796
|
+
for (const [key, value] of Object.entries(normalized)) {
|
|
797
|
+
normalized[key] = this.normalizeValue(value, table);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return normalized;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
normalizeValue(value, table) {
|
|
804
|
+
if (value === null || value === undefined) return value;
|
|
805
|
+
if (typeof value !== 'object') return value;
|
|
806
|
+
|
|
807
|
+
if (value.S !== undefined) return value.S;
|
|
808
|
+
if (value.N !== undefined) return parseFloat(value.N);
|
|
809
|
+
if (value.BOOL !== undefined) return value.BOOL;
|
|
810
|
+
if (value.NULL !== undefined) return null;
|
|
811
|
+
if (value.L !== undefined) return value.L.map((v) => this.normalizeValue(v, table));
|
|
812
|
+
if (value.M !== undefined) return this.normalizeItem(value.M, table);
|
|
813
|
+
if (value.SS !== undefined) return value.SS;
|
|
814
|
+
if (value.NS !== undefined) return value.NS.map(Number);
|
|
815
|
+
|
|
816
|
+
// plain object (already normalized, e.g. stored without DynamoDB types)
|
|
817
|
+
return value;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
marshallValue(value, table) {
|
|
821
|
+
if (value === null || value === undefined) return { NULL: true };
|
|
822
|
+
if (typeof value === 'boolean') return { BOOL: value };
|
|
823
|
+
if (typeof value === 'number') return { N: String(value) };
|
|
824
|
+
if (typeof value === 'string') return { S: value };
|
|
825
|
+
if (Array.isArray(value)) return { L: value.map((v) => this.marshallValue(v, table)) };
|
|
826
|
+
if (typeof value === 'object') return { M: this.marshallItem(value, table) };
|
|
827
|
+
return { S: String(value) };
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
marshallItem(item, table) {
|
|
831
|
+
const marshalled = {};
|
|
832
|
+
|
|
833
|
+
for (const [key, value] of Object.entries(item)) {
|
|
834
|
+
if (key.startsWith("_")) continue; // Pula campos internos
|
|
835
|
+
|
|
836
|
+
const type = table ? table.attributeTypes[key] : null;
|
|
837
|
+
if (type === "S") {
|
|
838
|
+
marshalled[key] = { S: String(value) };
|
|
839
|
+
} else if (type === "N") {
|
|
840
|
+
marshalled[key] = { N: String(value) };
|
|
841
|
+
} else {
|
|
842
|
+
marshalled[key] = this.marshallValue(value, table);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return marshalled;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
getItemKey(item, table) {
|
|
850
|
+
const hashValue = item[table.hashKey];
|
|
851
|
+
const rangeValue = table.rangeKey ? item[table.rangeKey] : null;
|
|
852
|
+
return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
getItemKeyFromKeys(keys, table) {
|
|
856
|
+
const rawHash = keys[table.hashKey];
|
|
857
|
+
const hashValue = rawHash && typeof rawHash === 'object' ? Object.values(rawHash)[0] : rawHash;
|
|
858
|
+
const rawRange = table.rangeKey ? keys[table.rangeKey] : null;
|
|
859
|
+
const rangeValue = rawRange && typeof rawRange === 'object' ? Object.values(rawRange)[0] : rawRange;
|
|
860
|
+
return rangeValue ? `${hashValue}|${rangeValue}` : String(hashValue);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
processUpdateExpression(item, expression, nameMap, valueMap, table) {
|
|
864
|
+
// SET clause
|
|
865
|
+
const setMatch = expression.match(/SET\s+([^]+?)(?=\s+(?:REMOVE|ADD|DELETE)\s|\s*$)/i);
|
|
866
|
+
if (setMatch) {
|
|
867
|
+
const assignments = setMatch[1].split(",").map((a) => a.trim());
|
|
868
|
+
for (const assignment of assignments) {
|
|
869
|
+
const [path, valueExpr] = assignment.split("=").map((s) => s.trim());
|
|
870
|
+
const attributeName = nameMap[path] || path.replace(/#/g, "");
|
|
871
|
+
const rawValue = valueMap[valueExpr];
|
|
872
|
+
// Usa normalizeValue para garantir o mesmo formato que o putItem
|
|
873
|
+
item[attributeName] = this.normalizeValue(rawValue, table);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// ADD clause — incrementa números ou adiciona a sets (upsert-friendly)
|
|
878
|
+
const addMatch = expression.match(/ADD\s+([^]+?)(?=\s+(?:SET|REMOVE|DELETE)\s|\s*$)/i);
|
|
879
|
+
if (addMatch) {
|
|
880
|
+
const assignments = addMatch[1].split(",").map((a) => a.trim());
|
|
881
|
+
for (const assignment of assignments) {
|
|
882
|
+
const parts = assignment.split(/\s+/);
|
|
883
|
+
const attributeName = nameMap[parts[0]] || parts[0].replace(/#/g, "");
|
|
884
|
+
const rawValue = valueMap[parts[1]];
|
|
885
|
+
// Usa normalizeValue para garantir o mesmo formato que o putItem
|
|
886
|
+
const delta = this.normalizeValue(rawValue, table);
|
|
887
|
+
const current = item[attributeName];
|
|
888
|
+
if (current === undefined || current === null) {
|
|
889
|
+
item[attributeName] = typeof delta === 'number' ? delta : parseFloat(delta) || 0;
|
|
890
|
+
} else {
|
|
891
|
+
item[attributeName] = (parseFloat(current) || 0) + (parseFloat(delta) || 0);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// REMOVE clause
|
|
897
|
+
const removeMatch = expression.match(/REMOVE\s+([^]+?)(?=\s+(?:SET|ADD|DELETE)\s|\s*$)/i);
|
|
898
|
+
if (removeMatch) {
|
|
899
|
+
const attributes = removeMatch[1].split(",").map((a) => a.trim());
|
|
900
|
+
for (const attr of attributes) {
|
|
901
|
+
const attributeName = nameMap[attr] || attr.replace(/#/g, "");
|
|
902
|
+
delete item[attributeName];
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
applyFilter(items, expression, values, names, table) {
|
|
908
|
+
if (!expression) return items;
|
|
909
|
+
|
|
910
|
+
const resolveAttributeName = (name) => {
|
|
911
|
+
if (name.startsWith("#")) return names[name] || name;
|
|
912
|
+
return name;
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const resolveValue = (placeholder) => {
|
|
916
|
+
const rawValue = values[placeholder];
|
|
917
|
+
if (rawValue === undefined) return undefined;
|
|
918
|
+
if (rawValue !== null && typeof rawValue === 'object' && !Array.isArray(rawValue)) {
|
|
919
|
+
const keys = Object.keys(rawValue);
|
|
920
|
+
if (keys.length === 1 && ["S", "N", "BOOL", "NULL", "M", "L", "SS", "NS", "BS"].includes(keys[0])) {
|
|
921
|
+
return this.normalizeValue(rawValue, table);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return rawValue;
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
const conditions = expression.split(/\s+AND\s+/i);
|
|
928
|
+
|
|
929
|
+
return items.filter((item) => {
|
|
930
|
+
return conditions.every(cond => {
|
|
931
|
+
// Regex para match de funções como contains(#n, :v) ou begins_with(#n, :v)
|
|
932
|
+
const funcMatch = cond.match(/(contains|begins_with)\s*\(\s*([^\s,]+)\s*,\s*([^\s,)]+)\s*\)/i);
|
|
933
|
+
if (funcMatch) {
|
|
934
|
+
const func = funcMatch[1].toLowerCase();
|
|
935
|
+
const attrName = resolveAttributeName(funcMatch[2]);
|
|
936
|
+
const expectedVal = resolveValue(funcMatch[3]);
|
|
937
|
+
const actualVal = item[attrName];
|
|
938
|
+
|
|
939
|
+
if (func === 'contains') {
|
|
940
|
+
if (Array.isArray(actualVal)) return actualVal.includes(expectedVal);
|
|
941
|
+
return String(actualVal || "").includes(String(expectedVal));
|
|
942
|
+
}
|
|
943
|
+
if (func === 'begins_with') {
|
|
944
|
+
return String(actualVal || "").startsWith(String(expectedVal));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Regex para operadores básicos
|
|
949
|
+
const opMatch = cond.match(/([^\s]+)\s*(=|<>|<|<=|>|>=)\s*([^\s]+)/);
|
|
950
|
+
if (opMatch) {
|
|
951
|
+
const attrName = resolveAttributeName(opMatch[1]);
|
|
952
|
+
const operator = opMatch[2];
|
|
953
|
+
const expectedVal = resolveValue(opMatch[3]);
|
|
954
|
+
const actualVal = item[attrName];
|
|
955
|
+
|
|
956
|
+
switch (operator) {
|
|
957
|
+
case "=": return actualVal === expectedVal;
|
|
958
|
+
case "<>": return actualVal !== expectedVal;
|
|
959
|
+
case "<": return actualVal < expectedVal;
|
|
960
|
+
case "<=": return actualVal <= expectedVal;
|
|
961
|
+
case ">": return actualVal > expectedVal;
|
|
962
|
+
case ">=": return actualVal >= expectedVal;
|
|
963
|
+
default: return true;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return true;
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
persistTables() {
|
|
973
|
+
const tablesObj = {};
|
|
974
|
+
for (const [name, table] of this.tables.entries()) {
|
|
975
|
+
tablesObj[name] = table;
|
|
976
|
+
}
|
|
977
|
+
this.store.write("__tables__", tablesObj);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
async reset() {
|
|
981
|
+
for (const [tableName] of this.tables) {
|
|
982
|
+
this.store.write(tableName, []);
|
|
983
|
+
}
|
|
984
|
+
logger.debug("DynamoDB: Todos os dados resetados");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
getTablesCount() {
|
|
988
|
+
return this.tables.size;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
getTotalItems() {
|
|
992
|
+
let total = 0;
|
|
993
|
+
for (const [tableName] of this.tables) {
|
|
994
|
+
const items = this.store.read(tableName);
|
|
995
|
+
total += items.length;
|
|
996
|
+
}
|
|
997
|
+
return total;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
listTables() {
|
|
1001
|
+
return { TableNames: Array.from(this.tables.keys()) };
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
module.exports = DynamoDBSimulator;
|