@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39

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 (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. package/aws-config +0 -154
@@ -0,0 +1,514 @@
1
+ /**
2
+ * Kinesis Data Streams Simulator
3
+ * Simulador de Kinesis Data Streams com suporte a shards, records, e consumidores
4
+ */
5
+
6
+ const { Stream, Shard } = require('./models/stream');
7
+ const Record = require('./models/record');
8
+ const LocalStore = require('../../utils/local-store');
9
+ const logger = require('../../utils/logger');
10
+ const path = require('path');
11
+ const { CloudTrailAudit } = require('../../utils/cloudtrail-audit');
12
+ const { v4: uuidv4 } = require('uuid');
13
+
14
+ class KinesisDataStreamsSimulator {
15
+ constructor(config) {
16
+ this.config = config;
17
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
18
+ this.dataDir = path.join(dataDir, 'kinesis-data-streams');
19
+
20
+ this.store = new LocalStore(this.dataDir);
21
+ this.streams = new Map();
22
+ this.consumers = new Map();
23
+ this.audit = new CloudTrailAudit('kinesis.amazonaws.com');
24
+
25
+ // Mutexes por stream para operações thread-safe
26
+ this._streamLocks = new Map();
27
+ }
28
+
29
+ /**
30
+ * Executa operação com lock exclusivo por stream
31
+ */
32
+ _withStreamLock(streamName, fn) {
33
+ const prev = this._streamLocks.get(streamName) || Promise.resolve();
34
+ const next = prev.then(() => fn());
35
+ this._streamLocks.set(streamName, next.catch(() => {}));
36
+ return next;
37
+ }
38
+
39
+ async initialize() {
40
+ logger.debug('Inicializando Kinesis Data Streams Simulator...');
41
+ this.loadStreams();
42
+
43
+ // Cria streams da configuração se não existirem
44
+ if (this.config['kinesis-data-streams']?.streams) {
45
+ for (const streamConfig of this.config['kinesis-data-streams'].streams) {
46
+ if (!this.streams.has(streamConfig.name)) {
47
+ this.createStream(streamConfig.name, streamConfig);
48
+ }
49
+ }
50
+ }
51
+
52
+ logger.debug(`✅ Kinesis Data Streams Simulator inicializado com ${this.streams.size} streams`);
53
+ }
54
+
55
+ /**
56
+ * Carrega streams persistidos
57
+ */
58
+ loadStreams() {
59
+ const metadata = this.store.read('__streams__');
60
+ if (!metadata) return;
61
+
62
+ for (const [streamName, streamData] of Object.entries(metadata)) {
63
+ const stream = new Stream(streamName, streamData);
64
+ stream.shards = streamData.shards.map(shardData => {
65
+ const shard = new Shard(0, streamName);
66
+ shard.shardId = shardData.shardId;
67
+ shard.shardStatus = shardData.shardStatus;
68
+ shard.records = (streamData.records || []).filter(r => r.shardId === shard.shardId);
69
+ shard.recordCount = shard.records.length;
70
+ shard.lastSequenceNumber = shardData.lastSequenceNumber || 0;
71
+ return shard;
72
+ });
73
+ this.streams.set(streamName, stream);
74
+ }
75
+
76
+ logger.debug(`Carregados ${this.streams.size} streams`);
77
+ }
78
+
79
+ /**
80
+ * Persiste metadados de streams
81
+ */
82
+ persistStreams() {
83
+ const metadata = {};
84
+
85
+ for (const [streamName, stream] of this.streams) {
86
+ metadata[streamName] = {
87
+ streamName: stream.streamName,
88
+ streamStatus: stream.streamStatus,
89
+ streamModeDetails: stream.streamModeDetails,
90
+ shardCount: stream.shardCount,
91
+ creationTimestamp: stream.creationTimestamp,
92
+ retentionPeriod: stream.retentionPeriod,
93
+ shards: stream.shards.map(s => ({
94
+ shardId: s.shardId,
95
+ shardStatus: s.shardStatus,
96
+ lastSequenceNumber: s.lastSequenceNumber
97
+ })),
98
+ records: Array.from(stream.shards).flatMap(s =>
99
+ s.records.map(r => ({
100
+ recordId: r.recordId,
101
+ shardId: s.shardId,
102
+ data: r.data,
103
+ partitionKey: r.partitionKey,
104
+ sequenceNumber: r.sequenceNumber,
105
+ timestamp: r.timestamp
106
+ }))
107
+ )
108
+ };
109
+ }
110
+
111
+ this.store.write('__streams__', metadata);
112
+ }
113
+
114
+ /**
115
+ * Cria um novo stream
116
+ */
117
+ createStream(streamName, config = {}) {
118
+ if (this.streams.has(streamName)) {
119
+ throw new Error(`Stream '${streamName}' already exists`);
120
+ }
121
+
122
+ const stream = new Stream(streamName, config);
123
+ this.streams.set(streamName, stream);
124
+ this.persistStreams();
125
+
126
+ this.audit.record({
127
+ eventName: 'CreateStream',
128
+ readOnly: false,
129
+ resources: [{
130
+ ARN: stream.streamArn,
131
+ type: 'AWS::Kinesis::Stream'
132
+ }],
133
+ requestParameters: { streamName, shardCount: stream.shardCount }
134
+ });
135
+
136
+ logger.info(`✅ Stream criado: ${streamName}`);
137
+
138
+ return {
139
+ StreamDescription: stream.describe().StreamDescription
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Descreve um stream
145
+ */
146
+ describeStream(streamName) {
147
+ const stream = this.streams.get(streamName);
148
+ if (!stream) {
149
+ throw new Error(`Stream '${streamName}' not found`);
150
+ }
151
+
152
+ return stream.describe();
153
+ }
154
+
155
+ /**
156
+ * Lista todos os streams
157
+ */
158
+ listStreams(params = {}) {
159
+ const { Limit = 100, ExclusiveStartStreamName } = params;
160
+ const streamNames = Array.from(this.streams.keys());
161
+
162
+ let startIndex = 0;
163
+ if (ExclusiveStartStreamName) {
164
+ const index = streamNames.indexOf(ExclusiveStartStreamName);
165
+ if (index !== -1) startIndex = index + 1;
166
+ }
167
+
168
+ const result = streamNames.slice(startIndex, startIndex + Limit);
169
+
170
+ return {
171
+ StreamNames: result,
172
+ HasMoreStreams: result.length === Limit,
173
+ StreamCreationTimestamps: result.map(name => {
174
+ const stream = this.streams.get(name);
175
+ return stream?.creationTimestamp || new Date().toISOString();
176
+ }),
177
+ StreamStatuses: result.map(() => 'ACTIVE')
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Deleta um stream
183
+ */
184
+ deleteStream(streamName) {
185
+ const stream = this.streams.get(streamName);
186
+ if (!stream) {
187
+ throw new Error(`Stream '${streamName}' not found`);
188
+ }
189
+
190
+ this.streams.delete(streamName);
191
+ this.persistStreams();
192
+
193
+ // Remove consumidores do stream
194
+ for (const [consumerArn] of this.consumers) {
195
+ if (consumerArn.includes(streamName)) {
196
+ this.consumers.delete(consumerArn);
197
+ }
198
+ }
199
+
200
+ this.audit.record({
201
+ eventName: 'DeleteStream',
202
+ readOnly: false,
203
+ resources: [{
204
+ ARN: stream.streamArn,
205
+ type: 'AWS::Kinesis::Stream'
206
+ }],
207
+ requestParameters: { streamName }
208
+ });
209
+
210
+ logger.info(`✅ Stream deletado: ${streamName}`);
211
+
212
+ return {};
213
+ }
214
+
215
+ /**
216
+ * Coloca um record no stream
217
+ */
218
+ async putRecord(streamName, data, partitionKey, config = {}) {
219
+ const stream = this.streams.get(streamName);
220
+ if (!stream) {
221
+ throw new Error(`Stream '${streamName}' not found`);
222
+ }
223
+
224
+ return this._withStreamLock(streamName, () => {
225
+ const shard = stream.selectShardByPartitionKey(partitionKey);
226
+ if (!shard) {
227
+ throw new Error(`No shards available in stream '${streamName}'`);
228
+ }
229
+
230
+ const record = new Record(data, partitionKey, {
231
+ shardId: shard.shardId,
232
+ encryptionType: config.encryptionType || 'NONE'
233
+ });
234
+
235
+ shard.addRecord(record);
236
+ stream.recordPutRecord(data);
237
+ this.persistStreams();
238
+
239
+ this.audit.record({
240
+ eventName: 'PutRecord',
241
+ readOnly: false,
242
+ isDataEvent: true,
243
+ resources: [{
244
+ ARN: stream.streamArn,
245
+ type: 'AWS::Kinesis::Stream'
246
+ }],
247
+ requestParameters: { streamName, partitionKey }
248
+ });
249
+
250
+ return {
251
+ ShardId: shard.shardId,
252
+ SequenceNumber: record.sequenceNumber,
253
+ EncryptionType: record.encryptionType
254
+ };
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Coloca múltiplos records no stream
260
+ */
261
+ async putRecords(streamName, records) {
262
+ const stream = this.streams.get(streamName);
263
+ if (!stream) {
264
+ throw new Error(`Stream '${streamName}' not found`);
265
+ }
266
+
267
+ return this._withStreamLock(streamName, () => {
268
+ const results = [];
269
+ let failedRecordCount = 0;
270
+
271
+ for (const recordData of records) {
272
+ try {
273
+ const shard = stream.selectShardByPartitionKey(recordData.PartitionKey);
274
+ const record = new Record(recordData.Data, recordData.PartitionKey, {
275
+ shardId: shard.shardId
276
+ });
277
+
278
+ shard.addRecord(record);
279
+ stream.recordPutRecord(recordData.Data);
280
+
281
+ results.push({
282
+ ShardId: shard.shardId,
283
+ SequenceNumber: record.sequenceNumber
284
+ });
285
+ } catch (error) {
286
+ failedRecordCount++;
287
+ results.push({
288
+ ErrorCode: 'InternalFailure',
289
+ ErrorMessage: error.message
290
+ });
291
+ }
292
+ }
293
+
294
+ this.persistStreams();
295
+
296
+ this.audit.record({
297
+ eventName: 'PutRecords',
298
+ readOnly: false,
299
+ isDataEvent: true,
300
+ resources: [{
301
+ ARN: stream.streamArn,
302
+ type: 'AWS::Kinesis::Stream'
303
+ }],
304
+ requestParameters: { streamName, recordCount: records.length }
305
+ });
306
+
307
+ return {
308
+ FailedRecordCount: failedRecordCount,
309
+ Records: results
310
+ };
311
+ });
312
+ }
313
+
314
+ /**
315
+ * Obtém records de um shard
316
+ */
317
+ getRecords(shardIterator, config = {}) {
318
+ const { Limit = 100 } = config;
319
+
320
+ // Parse iterator (formato simplificado: streamName:shardId:startSeqNum)
321
+ const [streamName, shardId, startSeqNum] = shardIterator.split(':');
322
+
323
+ const stream = this.streams.get(streamName);
324
+ if (!stream) {
325
+ throw new Error(`Stream '${streamName}' not found`);
326
+ }
327
+
328
+ const shard = stream.getShard(shardId);
329
+ if (!shard) {
330
+ throw new Error(`Shard '${shardId}' not found in stream '${streamName}'`);
331
+ }
332
+
333
+ const records = shard.getRecords(startSeqNum, Limit);
334
+ const lastSeqNum = shard.getLastSequenceNumber();
335
+
336
+ // Gera novo iterator
337
+ const nextSeqNum = records.length > 0
338
+ ? parseInt(records[records.length - 1].sequenceNumber)
339
+ : parseInt(startSeqNum || 0);
340
+
341
+ const nextShardIterator = `${streamName}:${shardId}:${nextSeqNum}`;
342
+
343
+ this.audit.record({
344
+ eventName: 'GetRecords',
345
+ readOnly: true,
346
+ isDataEvent: true,
347
+ resources: [{
348
+ ARN: stream.streamArn,
349
+ type: 'AWS::Kinesis::Stream'
350
+ }],
351
+ requestParameters: { streamName, shardId }
352
+ });
353
+
354
+ return {
355
+ Records: records.map(r => r.toJSON()),
356
+ NextShardIterator: nextShardIterator,
357
+ MillisBehindLatest: 0
358
+ };
359
+ }
360
+
361
+ /**
362
+ * Obtém shard iterator
363
+ */
364
+ getSharIterator(streamName, shardId, shardIteratorType, config = {}) {
365
+ const stream = this.streams.get(streamName);
366
+ if (!stream) {
367
+ throw new Error(`Stream '${streamName}' not found`);
368
+ }
369
+
370
+ const shard = stream.getShard(shardId);
371
+ if (!shard) {
372
+ throw new Error(`Shard '${shardId}' not found in stream '${streamName}'`);
373
+ }
374
+
375
+ let startSeqNum = '0';
376
+
377
+ switch (shardIteratorType) {
378
+ case 'TRIM_HORIZON':
379
+ startSeqNum = '0';
380
+ break;
381
+ case 'LATEST':
382
+ startSeqNum = shard.getLastSequenceNumber();
383
+ break;
384
+ case 'AT_SEQUENCE_NUMBER':
385
+ startSeqNum = config.StartingSequenceNumber || '0';
386
+ break;
387
+ case 'AFTER_SEQUENCE_NUMBER':
388
+ startSeqNum = (parseInt(config.StartingSequenceNumber || '0') + 1).toString();
389
+ break;
390
+ case 'AT_TIMESTAMP':
391
+ // Implementação simplificada
392
+ startSeqNum = '0';
393
+ break;
394
+ default:
395
+ throw new Error(`Invalid ShardIteratorType: ${shardIteratorType}`);
396
+ }
397
+
398
+ const shardIterator = `${streamName}:${shardId}:${startSeqNum}`;
399
+
400
+ return {
401
+ ShardIterator: shardIterator
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Registra um consumidor
407
+ */
408
+ registerStreamConsumer(streamArn, consumerName) {
409
+ const streamName = streamArn.split(':').pop();
410
+ const stream = this.streams.get(streamName);
411
+
412
+ if (!stream) {
413
+ throw new Error(`Stream not found: ${streamName}`);
414
+ }
415
+
416
+ const consumerArn = `${streamArn}/consumer/${consumerName}:${uuidv4().substring(0, 8)}`;
417
+ const consumer = stream.registerConsumer(consumerArn, consumerName);
418
+
419
+ this.consumers.set(consumerArn, {
420
+ consumerArn,
421
+ consumerName,
422
+ streamArn,
423
+ streamName,
424
+ creationTimestamp: new Date().toISOString(),
425
+ consumerStatus: 'ACTIVE'
426
+ });
427
+
428
+ logger.info(`✅ Consumidor registrado: ${consumerName} para stream ${streamName}`);
429
+
430
+ return {
431
+ Consumer: {
432
+ ConsumerName: consumerName,
433
+ ConsumerArn: consumerArn,
434
+ ConsumerStatus: 'ACTIVE',
435
+ ConsumerCreationTimestamp: new Date().toISOString()
436
+ }
437
+ };
438
+ }
439
+
440
+ /**
441
+ * Descreve um consumidor
442
+ */
443
+ describeStreamConsumer(consumerArn) {
444
+ const consumer = this.consumers.get(consumerArn);
445
+ if (!consumer) {
446
+ throw new Error(`Consumer not found: ${consumerArn}`);
447
+ }
448
+
449
+ return {
450
+ ConsumerDescription: {
451
+ ConsumerName: consumer.consumerName,
452
+ ConsumerArn: consumer.consumerArn,
453
+ ConsumerStatus: consumer.consumerStatus,
454
+ ConsumerCreationTimestamp: consumer.creationTimestamp
455
+ }
456
+ };
457
+ }
458
+
459
+ /**
460
+ * Lista consumidores
461
+ */
462
+ listStreamConsumers(streamArn) {
463
+ const streamName = streamArn.split(':').pop();
464
+ const consumers = Array.from(this.consumers.values())
465
+ .filter(c => c.streamName === streamName);
466
+
467
+ return {
468
+ Consumers: consumers.map(c => ({
469
+ ConsumerName: c.consumerName,
470
+ ConsumerArn: c.consumerArn,
471
+ ConsumerStatus: c.consumerStatus,
472
+ ConsumerCreationTimestamp: c.creationTimestamp
473
+ }))
474
+ };
475
+ }
476
+
477
+ /**
478
+ * Obtém estatísticas do simulator
479
+ */
480
+ getStats() {
481
+ const stats = {
482
+ streamsCount: this.streams.size,
483
+ consumersCount: this.consumers.size,
484
+ totalRecords: 0,
485
+ totalShards: 0,
486
+ streams: []
487
+ };
488
+
489
+ for (const [streamName, stream] of this.streams) {
490
+ const streamStats = stream.toJSON();
491
+ stats.totalRecords += streamStats.recordCount;
492
+ stats.totalShards += stream.shards.length;
493
+ stats.streams.push({
494
+ ...streamStats,
495
+ shardCount: stream.shards.length
496
+ });
497
+ }
498
+
499
+ return stats;
500
+ }
501
+
502
+ /**
503
+ * Reset - limpa todos os dados
504
+ */
505
+ async reset() {
506
+ this.streams.clear();
507
+ this.consumers.clear();
508
+ this.store.write('__streams__', {});
509
+
510
+ logger.info('✅ Kinesis Data Streams Simulator resetado');
511
+ }
512
+ }
513
+
514
+ module.exports = KinesisDataStreamsSimulator;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Kinesis Data Streams Store - Persistência de streams e records em JSON
3
+ */
4
+
5
+ const path = require('path');
6
+ const fs = require('fs');
7
+ const logger = require('../../utils/logger');
8
+
9
+ class KinesisDataStreamsStore {
10
+ constructor(dataDir) {
11
+ this.dataDir = dataDir;
12
+ this.streamsDir = path.join(dataDir, 'kinesis-data-streams');
13
+ this.metadataFile = path.join(this.streamsDir, '__streams__.json');
14
+
15
+ if (!fs.existsSync(this.streamsDir)) {
16
+ fs.mkdirSync(this.streamsDir, { recursive: true });
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Carrega metadados de todos os streams
22
+ */
23
+ loadMetadata() {
24
+ if (fs.existsSync(this.metadataFile)) {
25
+ try {
26
+ const data = fs.readFileSync(this.metadataFile, 'utf8');
27
+ return JSON.parse(data);
28
+ } catch (error) {
29
+ logger.warn(`Erro ao ler ${this.metadataFile}: ${error.message}`);
30
+ return {};
31
+ }
32
+ }
33
+ return {};
34
+ }
35
+
36
+ /**
37
+ * Salva metadados de todos os streams
38
+ */
39
+ saveMetadata(metadata) {
40
+ try {
41
+ fs.writeFileSync(this.metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
42
+ logger.debug(`Metadados de streams salvos em ${this.metadataFile}`);
43
+ } catch (error) {
44
+ logger.error(`Erro ao salvar ${this.metadataFile}: ${error.message}`);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Obtém caminho do arquivo de stream
50
+ */
51
+ getStreamPath(streamName) {
52
+ return path.join(this.streamsDir, `${streamName}.json`);
53
+ }
54
+
55
+ /**
56
+ * Carrega records de um stream
57
+ */
58
+ loadStreamRecords(streamName) {
59
+ const streamPath = this.getStreamPath(streamName);
60
+
61
+ if (!fs.existsSync(streamPath)) {
62
+ return [];
63
+ }
64
+
65
+ try {
66
+ const data = fs.readFileSync(streamPath, 'utf8');
67
+ return JSON.parse(data);
68
+ } catch (error) {
69
+ logger.warn(`Erro ao ler stream ${streamName}: ${error.message}`);
70
+ return [];
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Salva records de um stream
76
+ */
77
+ saveStreamRecords(streamName, records) {
78
+ try {
79
+ const streamPath = this.getStreamPath(streamName);
80
+ fs.writeFileSync(streamPath, JSON.stringify(records, null, 2), 'utf8');
81
+ logger.debug(`${records.length} records salvos no stream ${streamName}`);
82
+ } catch (error) {
83
+ logger.error(`Erro ao salvar stream ${streamName}: ${error.message}`);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Adiciona record ao stream
89
+ */
90
+ addRecord(streamName, record) {
91
+ const records = this.loadStreamRecords(streamName);
92
+ records.push(record);
93
+ this.saveStreamRecords(streamName, records);
94
+ }
95
+
96
+ /**
97
+ * Adiciona múltiplos records
98
+ */
99
+ addRecords(streamName, newRecords) {
100
+ const records = this.loadStreamRecords(streamName);
101
+ records.push(...newRecords);
102
+ this.saveStreamRecords(streamName, records);
103
+ }
104
+
105
+ /**
106
+ * Remove record do stream
107
+ */
108
+ removeRecord(streamName, recordId) {
109
+ const records = this.loadStreamRecords(streamName);
110
+ const filtered = records.filter(r => r.recordId !== recordId);
111
+
112
+ if (filtered.length < records.length) {
113
+ this.saveStreamRecords(streamName, filtered);
114
+ return true;
115
+ }
116
+
117
+ return false;
118
+ }
119
+
120
+ /**
121
+ * Deleta stream completamente
122
+ */
123
+ deleteStream(streamName) {
124
+ try {
125
+ const streamPath = this.getStreamPath(streamName);
126
+ if (fs.existsSync(streamPath)) {
127
+ fs.unlinkSync(streamPath);
128
+ logger.debug(`Stream ${streamName} deletado`);
129
+ }
130
+ } catch (error) {
131
+ logger.error(`Erro ao deletar stream ${streamName}: ${error.message}`);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Calcula tamanho total do stream em bytes
137
+ */
138
+ getStreamSize(streamName) {
139
+ const records = this.loadStreamRecords(streamName);
140
+ return records.reduce((sum, record) => sum + Buffer.byteLength(record.data), 0);
141
+ }
142
+
143
+ /**
144
+ * Limpa todos os streams
145
+ */
146
+ clear() {
147
+ try {
148
+ if (fs.existsSync(this.streamsDir)) {
149
+ const files = fs.readdirSync(this.streamsDir);
150
+ files.forEach(file => {
151
+ if (file.endsWith('.json')) {
152
+ fs.unlinkSync(path.join(this.streamsDir, file));
153
+ }
154
+ });
155
+ logger.debug('Todos os streams foram limpos');
156
+ }
157
+ } catch (error) {
158
+ logger.error(`Erro ao limpar streams: ${error.message}`);
159
+ }
160
+ }
161
+ }
162
+
163
+ module.exports = KinesisDataStreamsStore;