@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,684 +1,684 @@
1
- /**
2
- * @fileoverview EventBridge Simulator
3
- * Simula o Amazon EventBridge com event buses, rules e targets
4
- */
5
-
6
- 'use strict';
7
-
8
- const crypto = require('crypto');
9
-
10
- /**
11
- * EventBridge Simulator
12
- */
13
- class EventBridgeSimulator {
14
- /**
15
- * @param {Object} config - Service configuration
16
- * @param {Object} store - LocalStore instance
17
- * @param {Object} logger - Logger instance
18
- */
19
- constructor(config, store, logger) {
20
- this.config = config;
21
- this.store = store;
22
- this.logger = logger;
23
-
24
- /** @type {Map<string, Object>} Event buses */
25
- this.buses = new Map();
26
- /** @type {Map<string, Object>} Rules by ruleArn */
27
- this.rules = new Map();
28
- /** @type {Map<string, Object[]>} Targets by ruleArn */
29
- this.targets = new Map();
30
- /** @type {Array} Event archive (recent events) */
31
- this.eventArchive = [];
32
-
33
- this.region = 'us-east-1';
34
- this.accountId = '123456789012';
35
-
36
- // Services for target delivery
37
- this.lambdaService = null;
38
- this.sqsService = null;
39
- this.snsService = null;
40
-
41
- // Create default event bus
42
- this._createDefaultBus();
43
- }
44
-
45
- /**
46
- * Create the default event bus
47
- */
48
- _createDefaultBus() {
49
- const defaultBus = {
50
- Name: 'default',
51
- Arn: `arn:aws:events:${this.region}:${this.accountId}:event-bus/default`,
52
- State: 'ACTIVE',
53
- CreationTime: new Date().toISOString()
54
- };
55
- this.buses.set('default', defaultBus);
56
- }
57
-
58
- /** @param {Object} s */ setLambdaService(s) { this.lambdaService = s; }
59
- /** @param {Object} s */ setSqsService(s) { this.sqsService = s; }
60
- /** @param {Object} s */ setSnsService(s) { this.snsService = s; }
61
-
62
- /**
63
- * Load persisted data
64
- */
65
- async load() {
66
- try {
67
- const buses = await this.store.read('eventbridge/buses');
68
- if (Array.isArray(buses)) {
69
- buses.forEach(b => this.buses.set(b.Name, b));
70
- }
71
- const rules = await this.store.read('eventbridge/rules');
72
- if (Array.isArray(rules)) {
73
- rules.forEach(r => this.rules.set(r.Arn, r));
74
- }
75
- const targets = await this.store.read('eventbridge/targets');
76
- if (Array.isArray(targets)) {
77
- targets.forEach(({ ruleArn, list }) => this.targets.set(ruleArn, list));
78
- }
79
- this.logger.debug('EventBridge', `Loaded ${this.buses.size} buses, ${this.rules.size} rules`);
80
- } catch {
81
- this.logger.debug('EventBridge', 'No persisted data, starting fresh');
82
- }
83
- }
84
-
85
- /**
86
- * Save data
87
- */
88
- async _save() {
89
- await this.store.write('eventbridge/buses', null, Array.from(this.buses.values()));
90
- await this.store.write('eventbridge/rules', null, Array.from(this.rules.values()));
91
- const targetsList = Array.from(this.targets.entries())
92
- .map(([ruleArn, list]) => ({ ruleArn, list }));
93
- await this.store.write('eventbridge/targets', null, targetsList);
94
- }
95
-
96
- // ==================== Event Bus Operations ====================
97
-
98
- /**
99
- * CreateEventBus
100
- * @param {Object} params
101
- * @returns {Object}
102
- */
103
- async createEventBus(params) {
104
- const { Name, EventSourceName, Tags } = params;
105
-
106
- if (!Name) throw this._error('ValidationException', 'Name is required');
107
- if (Name === 'default') throw this._error('ResourceAlreadyExistsException', 'Default event bus already exists');
108
- if (this.buses.has(Name)) throw this._error('ResourceAlreadyExistsException', `Event bus ${Name} already exists`);
109
-
110
- const bus = {
111
- Name,
112
- Arn: `arn:aws:events:${this.region}:${this.accountId}:event-bus/${Name}`,
113
- State: 'ACTIVE',
114
- EventSourceName: EventSourceName || null,
115
- Tags: Tags || [],
116
- CreationTime: new Date().toISOString()
117
- };
118
-
119
- this.buses.set(Name, bus);
120
- await this._save();
121
-
122
- this.logger.info('EventBridge', `Created event bus: ${Name}`);
123
- return { EventBusArn: bus.Arn };
124
- }
125
-
126
- /**
127
- * DeleteEventBus
128
- * @param {Object} params
129
- */
130
- async deleteEventBus(params) {
131
- const { Name } = params;
132
-
133
- if (Name === 'default') throw this._error('ValidationException', 'Cannot delete default event bus');
134
- if (!this.buses.has(Name)) throw this._error('ResourceNotFoundException', `Event bus ${Name} not found`);
135
-
136
- // Delete all rules for this bus
137
- for (const [arn, rule] of this.rules.entries()) {
138
- if (rule.EventBusName === Name) {
139
- this.rules.delete(arn);
140
- this.targets.delete(arn);
141
- }
142
- }
143
-
144
- this.buses.delete(Name);
145
- await this._save();
146
-
147
- this.logger.info('EventBridge', `Deleted event bus: ${Name}`);
148
- }
149
-
150
- /**
151
- * ListEventBuses
152
- * @param {Object} params
153
- * @returns {Object}
154
- */
155
- listEventBuses(params = {}) {
156
- const { Limit = 100, NamePrefix, NextToken } = params;
157
- let buses = Array.from(this.buses.values());
158
-
159
- if (NamePrefix) {
160
- buses = buses.filter(b => b.Name.startsWith(NamePrefix));
161
- }
162
-
163
- return {
164
- EventBuses: buses.slice(0, Limit).map(b => ({
165
- Name: b.Name,
166
- Arn: b.Arn,
167
- State: b.State
168
- }))
169
- };
170
- }
171
-
172
- /**
173
- * DescribeEventBus
174
- * @param {Object} params
175
- * @returns {Object}
176
- */
177
- describeEventBus(params = {}) {
178
- const { Name = 'default' } = params;
179
- const bus = this.buses.get(Name);
180
- if (!bus) throw this._error('ResourceNotFoundException', `Event bus ${Name} not found`);
181
- return bus;
182
- }
183
-
184
- // ==================== Rules ====================
185
-
186
- /**
187
- * PutRule
188
- * @param {Object} params
189
- * @returns {Object}
190
- */
191
- async putRule(params) {
192
- const {
193
- Name, EventBusName = 'default', EventPattern, ScheduleExpression,
194
- State = 'ENABLED', Description, RoleArn, Tags
195
- } = params;
196
-
197
- if (!Name) throw this._error('ValidationException', 'Name is required');
198
- if (!EventPattern && !ScheduleExpression) {
199
- throw this._error('ValidationException', 'Either EventPattern or ScheduleExpression is required');
200
- }
201
-
202
- const bus = this.buses.get(EventBusName);
203
- if (!bus) throw this._error('ResourceNotFoundException', `Event bus ${EventBusName} not found`);
204
-
205
- // Validate EventPattern
206
- let parsedPattern = null;
207
- if (EventPattern) {
208
- try {
209
- parsedPattern = JSON.parse(EventPattern);
210
- } catch {
211
- throw this._error('InvalidEventPatternException', 'Event pattern is not valid JSON');
212
- }
213
- }
214
-
215
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
216
-
217
- const rule = {
218
- Name,
219
- Arn: ruleArn,
220
- EventBusName,
221
- EventPattern: EventPattern || null,
222
- ParsedPattern: parsedPattern,
223
- ScheduleExpression: ScheduleExpression || null,
224
- State,
225
- Description: Description || '',
226
- RoleArn: RoleArn || null,
227
- Tags: Tags || [],
228
- CreatedAt: new Date().toISOString()
229
- };
230
-
231
- this.rules.set(ruleArn, rule);
232
- await this._save();
233
-
234
- this.logger.info('EventBridge', `Put rule: ${Name} on bus ${EventBusName}`);
235
- return { RuleArn: ruleArn };
236
- }
237
-
238
- /**
239
- * DeleteRule
240
- * @param {Object} params
241
- */
242
- async deleteRule(params) {
243
- const { Name, EventBusName = 'default', Force } = params;
244
-
245
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
246
-
247
- if (!this.rules.has(ruleArn)) {
248
- throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
249
- }
250
-
251
- // Check for targets unless Force
252
- const ruleTargets = this.targets.get(ruleArn) || [];
253
- if (ruleTargets.length > 0 && !Force) {
254
- throw this._error('ValidationException', 'Rule has targets. Use Force=true to delete anyway');
255
- }
256
-
257
- this.rules.delete(ruleArn);
258
- this.targets.delete(ruleArn);
259
- await this._save();
260
-
261
- this.logger.info('EventBridge', `Deleted rule: ${Name}`);
262
- }
263
-
264
- /**
265
- * ListRules
266
- * @param {Object} params
267
- * @returns {Object}
268
- */
269
- listRules(params = {}) {
270
- const { EventBusName = 'default', NamePrefix, Limit = 100 } = params;
271
- let rules = Array.from(this.rules.values())
272
- .filter(r => r.EventBusName === EventBusName);
273
-
274
- if (NamePrefix) {
275
- rules = rules.filter(r => r.Name.startsWith(NamePrefix));
276
- }
277
-
278
- return {
279
- Rules: rules.slice(0, Limit).map(r => ({
280
- Name: r.Name,
281
- Arn: r.Arn,
282
- EventBusName: r.EventBusName,
283
- EventPattern: r.EventPattern,
284
- ScheduleExpression: r.ScheduleExpression,
285
- State: r.State,
286
- Description: r.Description
287
- }))
288
- };
289
- }
290
-
291
- /**
292
- * DescribeRule
293
- * @param {Object} params
294
- * @returns {Object}
295
- */
296
- describeRule(params) {
297
- const { Name, EventBusName = 'default' } = params;
298
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
299
- const rule = this.rules.get(ruleArn);
300
- if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
301
- return rule;
302
- }
303
-
304
- /**
305
- * EnableRule
306
- * @param {Object} params
307
- */
308
- async enableRule(params) {
309
- const { Name, EventBusName = 'default' } = params;
310
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
311
- const rule = this.rules.get(ruleArn);
312
- if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
313
- rule.State = 'ENABLED';
314
- await this._save();
315
- }
316
-
317
- /**
318
- * DisableRule
319
- * @param {Object} params
320
- */
321
- async disableRule(params) {
322
- const { Name, EventBusName = 'default' } = params;
323
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
324
- const rule = this.rules.get(ruleArn);
325
- if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
326
- rule.State = 'DISABLED';
327
- await this._save();
328
- }
329
-
330
- // ==================== Targets ====================
331
-
332
- /**
333
- * PutTargets
334
- * @param {Object} params
335
- * @returns {Object}
336
- */
337
- async putTargets(params) {
338
- const { Rule, EventBusName = 'default', Targets } = params;
339
-
340
- if (!Rule) throw this._error('ValidationException', 'Rule is required');
341
- if (!Targets || !Targets.length) throw this._error('ValidationException', 'Targets are required');
342
-
343
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
344
- if (!this.rules.has(ruleArn)) throw this._error('ResourceNotFoundException', `Rule ${Rule} not found`);
345
-
346
- const existing = this.targets.get(ruleArn) || [];
347
- const failedEntries = [];
348
-
349
- for (const target of Targets) {
350
- if (!target.Id || !target.Arn) {
351
- failedEntries.push({ TargetId: target.Id, ErrorCode: 'ValidationException', ErrorMessage: 'Id and Arn are required' });
352
- continue;
353
- }
354
-
355
- // Remove existing target with same Id
356
- const idx = existing.findIndex(t => t.Id === target.Id);
357
- if (idx >= 0) existing.splice(idx, 1);
358
-
359
- existing.push({
360
- Id: target.Id,
361
- Arn: target.Arn,
362
- Input: target.Input || null,
363
- InputPath: target.InputPath || null,
364
- InputTransformer: target.InputTransformer || null,
365
- RoleArn: target.RoleArn || null,
366
- RetryPolicy: target.RetryPolicy || { MaximumRetryAttempts: 185, MaximumEventAgeInSeconds: 86400 },
367
- DeadLetterConfig: target.DeadLetterConfig || null
368
- });
369
- }
370
-
371
- this.targets.set(ruleArn, existing);
372
- await this._save();
373
-
374
- this.logger.info('EventBridge', `Put ${Targets.length} targets for rule ${Rule}`);
375
- return {
376
- FailedEntryCount: failedEntries.length,
377
- FailedEntries: failedEntries
378
- };
379
- }
380
-
381
- /**
382
- * RemoveTargets
383
- * @param {Object} params
384
- * @returns {Object}
385
- */
386
- async removeTargets(params) {
387
- const { Rule, EventBusName = 'default', Ids } = params;
388
-
389
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
390
- const existing = this.targets.get(ruleArn) || [];
391
-
392
- const remaining = existing.filter(t => !Ids.includes(t.Id));
393
- this.targets.set(ruleArn, remaining);
394
- await this._save();
395
-
396
- return { FailedEntryCount: 0, FailedEntries: [] };
397
- }
398
-
399
- /**
400
- * ListTargetsByRule
401
- * @param {Object} params
402
- * @returns {Object}
403
- */
404
- listTargetsByRule(params) {
405
- const { Rule, EventBusName = 'default' } = params;
406
- const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
407
-
408
- if (!this.rules.has(ruleArn)) throw this._error('ResourceNotFoundException', `Rule ${Rule} not found`);
409
-
410
- const targets = this.targets.get(ruleArn) || [];
411
- return { Targets: targets };
412
- }
413
-
414
- // ==================== PutEvents ====================
415
-
416
- /**
417
- * PutEvents
418
- * @param {Object} params
419
- * @returns {Object}
420
- */
421
- async putEvents(params) {
422
- const { Entries } = params;
423
-
424
- if (!Entries || !Entries.length) {
425
- throw this._error('ValidationException', 'Entries are required');
426
- }
427
-
428
- const results = [];
429
- const failedEntries = [];
430
-
431
- for (const entry of Entries) {
432
- const eventId = crypto.randomUUID();
433
-
434
- // Validate entry
435
- if (!entry.Source) {
436
- failedEntries.push({ ErrorCode: 'ValidationException', ErrorMessage: 'Source is required' });
437
- results.push({ EventId: null, ErrorCode: 'ValidationException' });
438
- continue;
439
- }
440
-
441
- const busName = entry.EventBusName || 'default';
442
- if (!this.buses.has(busName)) {
443
- failedEntries.push({ ErrorCode: 'ResourceNotFoundException', ErrorMessage: `Bus ${busName} not found` });
444
- results.push({ EventId: null, ErrorCode: 'ResourceNotFoundException' });
445
- continue;
446
- }
447
-
448
- // Build event
449
- let detail = entry.Detail;
450
- if (typeof detail === 'string') {
451
- try { detail = JSON.parse(detail); } catch { detail = {}; }
452
- }
453
-
454
- const event = {
455
- id: eventId,
456
- version: '0',
457
- account: this.accountId,
458
- time: entry.Time || new Date().toISOString(),
459
- region: this.region,
460
- source: entry.Source,
461
- 'detail-type': entry.DetailType || '',
462
- resources: entry.Resources || [],
463
- detail: detail || {}
464
- };
465
-
466
- // Archive event
467
- this.eventArchive.push(event);
468
- if (this.eventArchive.length > 1000) {
469
- this.eventArchive.shift();
470
- }
471
-
472
- // Match and deliver to rules
473
- await this._matchAndDeliver(busName, event);
474
-
475
- results.push({ EventId: eventId });
476
- }
477
-
478
- this.logger.debug('EventBridge', `PutEvents: ${Entries.length} events, ${failedEntries.length} failed`);
479
-
480
- return {
481
- FailedEntryCount: failedEntries.length,
482
- Entries: results
483
- };
484
- }
485
-
486
- /**
487
- * Match event to rules and deliver to targets
488
- * @param {string} busName
489
- * @param {Object} event
490
- */
491
- async _matchAndDeliver(busName, event) {
492
- const busRules = Array.from(this.rules.values())
493
- .filter(r => r.EventBusName === busName && r.State === 'ENABLED');
494
-
495
- for (const rule of busRules) {
496
- if (rule.ParsedPattern && this._matchesPattern(event, rule.ParsedPattern)) {
497
- const targets = this.targets.get(rule.Arn) || [];
498
- for (const target of targets) {
499
- try {
500
- await this._deliverToTarget(target, event);
501
- } catch (err) {
502
- this.logger.warn('EventBridge', `Target delivery failed: ${err.message}`);
503
- }
504
- }
505
- }
506
- }
507
- }
508
-
509
- /**
510
- * Check if event matches pattern
511
- * @param {Object} event
512
- * @param {Object} pattern
513
- * @returns {boolean}
514
- */
515
- _matchesPattern(event, pattern) {
516
- for (const [key, matchers] of Object.entries(pattern)) {
517
- const eventValue = key === 'detail' ? event.detail : event[key];
518
-
519
- if (key === 'detail' && typeof matchers === 'object' && !Array.isArray(matchers)) {
520
- // Recursive detail matching
521
- if (!this._matchesPattern(event.detail || {}, matchers)) return false;
522
- continue;
523
- }
524
-
525
- if (!Array.isArray(matchers)) continue;
526
-
527
- const matches = matchers.some(matcher => {
528
- if (matcher === null) return eventValue === null;
529
- if (typeof matcher === 'string') return eventValue === matcher;
530
- if (typeof matcher === 'object') {
531
- if (matcher.prefix) return String(eventValue).startsWith(matcher.prefix);
532
- if (matcher['anything-but']) {
533
- return !matcher['anything-but'].includes(eventValue);
534
- }
535
- if (matcher.exists !== undefined) {
536
- return matcher.exists ? eventValue !== undefined : eventValue === undefined;
537
- }
538
- if (matcher.numeric) return this._checkNumeric(parseFloat(eventValue), matcher.numeric);
539
- }
540
- return false;
541
- });
542
-
543
- if (!matches) return false;
544
- }
545
- return true;
546
- }
547
-
548
- /**
549
- * Check numeric conditions
550
- * @param {number} value
551
- * @param {Array} conditions
552
- * @returns {boolean}
553
- */
554
- _checkNumeric(value, conditions) {
555
- for (let i = 0; i < conditions.length; i += 2) {
556
- const op = conditions[i];
557
- const threshold = conditions[i + 1];
558
- if (op === '=' && value !== threshold) return false;
559
- if (op === '>' && value <= threshold) return false;
560
- if (op === '>=' && value < threshold) return false;
561
- if (op === '<' && value >= threshold) return false;
562
- if (op === '<=' && value > threshold) return false;
563
- }
564
- return true;
565
- }
566
-
567
- /**
568
- * Deliver event to target
569
- * @param {Object} target
570
- * @param {Object} event
571
- */
572
- async _deliverToTarget(target, event) {
573
- // Transform input
574
- let inputEvent = event;
575
- if (target.Input) {
576
- try { inputEvent = JSON.parse(target.Input); } catch { inputEvent = target.Input; }
577
- } else if (target.InputPath) {
578
- inputEvent = this._extractPath(event, target.InputPath);
579
- } else if (target.InputTransformer) {
580
- inputEvent = this._transformInput(event, target.InputTransformer);
581
- }
582
-
583
- const arn = target.Arn;
584
-
585
- // Lambda target
586
- if (arn.includes(':lambda:') || arn.includes('function:')) {
587
- if (!this.lambdaService) return;
588
- const match = arn.match(/function:([^:]+)/);
589
- if (!match) return;
590
- await this.lambdaService.simulator.invokeFunction(match[1], inputEvent);
591
- this.logger.debug('EventBridge', `Delivered to Lambda: ${match[1]}`);
592
- return;
593
- }
594
-
595
- // SQS target
596
- if (arn.includes(':sqs:') || arn.includes(':queue:')) {
597
- if (!this.sqsService) return;
598
- await this.sqsService.simulator.sendMessage({
599
- QueueUrl: arn,
600
- MessageBody: JSON.stringify(inputEvent)
601
- });
602
- this.logger.debug('EventBridge', `Delivered to SQS: ${arn}`);
603
- return;
604
- }
605
-
606
- // SNS target
607
- if (arn.includes(':sns:')) {
608
- if (!this.snsService) return;
609
- await this.snsService.simulator.publish({
610
- TopicArn: arn,
611
- Message: JSON.stringify(inputEvent)
612
- });
613
- this.logger.debug('EventBridge', `Delivered to SNS: ${arn}`);
614
- return;
615
- }
616
-
617
- this.logger.warn('EventBridge', `Unsupported target ARN: ${arn}`);
618
- }
619
-
620
- /**
621
- * Extract value at JSONPath
622
- * @param {Object} obj
623
- * @param {string} path
624
- * @returns {*}
625
- */
626
- _extractPath(obj, path) {
627
- if (path === '$') return obj;
628
- const parts = path.replace(/^\$\./, '').split('.');
629
- let current = obj;
630
- for (const part of parts) {
631
- if (current === null || current === undefined) return null;
632
- current = current[part];
633
- }
634
- return current;
635
- }
636
-
637
- /**
638
- * Transform input using InputTransformer
639
- * @param {Object} event
640
- * @param {Object} transformer
641
- * @returns {*}
642
- */
643
- _transformInput(event, transformer) {
644
- const { InputPathsMap, InputTemplate } = transformer;
645
- let result = InputTemplate;
646
-
647
- if (InputPathsMap && InputTemplate) {
648
- for (const [key, path] of Object.entries(InputPathsMap)) {
649
- const value = this._extractPath(event, path);
650
- result = result.replace(new RegExp(`<${key}>`, 'g'), JSON.stringify(value));
651
- }
652
- }
653
-
654
- try { return JSON.parse(result); } catch { return result; }
655
- }
656
-
657
- /**
658
- * Reset all data
659
- */
660
- async reset() {
661
- this.buses.clear();
662
- this.rules.clear();
663
- this.targets.clear();
664
- this.eventArchive = [];
665
- this._createDefaultBus();
666
- await this._save();
667
- this.logger.info('EventBridge', 'Data reset');
668
- }
669
-
670
- /**
671
- * Create AWS-formatted error
672
- * @param {string} code
673
- * @param {string} message
674
- * @returns {Error}
675
- */
676
- _error(code, message) {
677
- const err = new Error(message);
678
- err.code = code;
679
- err.__type = code;
680
- return err;
681
- }
682
- }
683
-
684
- module.exports = { EventBridgeSimulator };
1
+ /**
2
+ * @fileoverview EventBridge Simulator
3
+ * Simula o Amazon EventBridge com event buses, rules e targets
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const crypto = require('crypto');
9
+
10
+ /**
11
+ * EventBridge Simulator
12
+ */
13
+ class EventBridgeSimulator {
14
+ /**
15
+ * @param {Object} config - Service configuration
16
+ * @param {Object} store - LocalStore instance
17
+ * @param {Object} logger - Logger instance
18
+ */
19
+ constructor(config, store, logger) {
20
+ this.config = config;
21
+ this.store = store;
22
+ this.logger = logger;
23
+
24
+ /** @type {Map<string, Object>} Event buses */
25
+ this.buses = new Map();
26
+ /** @type {Map<string, Object>} Rules by ruleArn */
27
+ this.rules = new Map();
28
+ /** @type {Map<string, Object[]>} Targets by ruleArn */
29
+ this.targets = new Map();
30
+ /** @type {Array} Event archive (recent events) */
31
+ this.eventArchive = [];
32
+
33
+ this.region = 'us-east-1';
34
+ this.accountId = '123456789012';
35
+
36
+ // Services for target delivery
37
+ this.lambdaService = null;
38
+ this.sqsService = null;
39
+ this.snsService = null;
40
+
41
+ // Create default event bus
42
+ this._createDefaultBus();
43
+ }
44
+
45
+ /**
46
+ * Create the default event bus
47
+ */
48
+ _createDefaultBus() {
49
+ const defaultBus = {
50
+ Name: 'default',
51
+ Arn: `arn:aws:events:${this.region}:${this.accountId}:event-bus/default`,
52
+ State: 'ACTIVE',
53
+ CreationTime: new Date().toISOString()
54
+ };
55
+ this.buses.set('default', defaultBus);
56
+ }
57
+
58
+ /** @param {Object} s */ setLambdaService(s) { this.lambdaService = s; }
59
+ /** @param {Object} s */ setSqsService(s) { this.sqsService = s; }
60
+ /** @param {Object} s */ setSnsService(s) { this.snsService = s; }
61
+
62
+ /**
63
+ * Load persisted data
64
+ */
65
+ async load() {
66
+ try {
67
+ const buses = await this.store.read('eventbridge/buses');
68
+ if (Array.isArray(buses)) {
69
+ buses.forEach(b => this.buses.set(b.Name, b));
70
+ }
71
+ const rules = await this.store.read('eventbridge/rules');
72
+ if (Array.isArray(rules)) {
73
+ rules.forEach(r => this.rules.set(r.Arn, r));
74
+ }
75
+ const targets = await this.store.read('eventbridge/targets');
76
+ if (Array.isArray(targets)) {
77
+ targets.forEach(({ ruleArn, list }) => this.targets.set(ruleArn, list));
78
+ }
79
+ this.logger.debug('EventBridge', `Loaded ${this.buses.size} buses, ${this.rules.size} rules`);
80
+ } catch {
81
+ this.logger.debug('EventBridge', 'No persisted data, starting fresh');
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Save data
87
+ */
88
+ async _save() {
89
+ await this.store.write('eventbridge/buses', null, Array.from(this.buses.values()));
90
+ await this.store.write('eventbridge/rules', null, Array.from(this.rules.values()));
91
+ const targetsList = Array.from(this.targets.entries())
92
+ .map(([ruleArn, list]) => ({ ruleArn, list }));
93
+ await this.store.write('eventbridge/targets', null, targetsList);
94
+ }
95
+
96
+ // ==================== Event Bus Operations ====================
97
+
98
+ /**
99
+ * CreateEventBus
100
+ * @param {Object} params
101
+ * @returns {Object}
102
+ */
103
+ async createEventBus(params) {
104
+ const { Name, EventSourceName, Tags } = params;
105
+
106
+ if (!Name) throw this._error('ValidationException', 'Name is required');
107
+ if (Name === 'default') throw this._error('ResourceAlreadyExistsException', 'Default event bus already exists');
108
+ if (this.buses.has(Name)) throw this._error('ResourceAlreadyExistsException', `Event bus ${Name} already exists`);
109
+
110
+ const bus = {
111
+ Name,
112
+ Arn: `arn:aws:events:${this.region}:${this.accountId}:event-bus/${Name}`,
113
+ State: 'ACTIVE',
114
+ EventSourceName: EventSourceName || null,
115
+ Tags: Tags || [],
116
+ CreationTime: new Date().toISOString()
117
+ };
118
+
119
+ this.buses.set(Name, bus);
120
+ await this._save();
121
+
122
+ this.logger.info('EventBridge', `Created event bus: ${Name}`);
123
+ return { EventBusArn: bus.Arn };
124
+ }
125
+
126
+ /**
127
+ * DeleteEventBus
128
+ * @param {Object} params
129
+ */
130
+ async deleteEventBus(params) {
131
+ const { Name } = params;
132
+
133
+ if (Name === 'default') throw this._error('ValidationException', 'Cannot delete default event bus');
134
+ if (!this.buses.has(Name)) throw this._error('ResourceNotFoundException', `Event bus ${Name} not found`);
135
+
136
+ // Delete all rules for this bus
137
+ for (const [arn, rule] of this.rules.entries()) {
138
+ if (rule.EventBusName === Name) {
139
+ this.rules.delete(arn);
140
+ this.targets.delete(arn);
141
+ }
142
+ }
143
+
144
+ this.buses.delete(Name);
145
+ await this._save();
146
+
147
+ this.logger.info('EventBridge', `Deleted event bus: ${Name}`);
148
+ }
149
+
150
+ /**
151
+ * ListEventBuses
152
+ * @param {Object} params
153
+ * @returns {Object}
154
+ */
155
+ listEventBuses(params = {}) {
156
+ const { Limit = 100, NamePrefix, NextToken } = params;
157
+ let buses = Array.from(this.buses.values());
158
+
159
+ if (NamePrefix) {
160
+ buses = buses.filter(b => b.Name.startsWith(NamePrefix));
161
+ }
162
+
163
+ return {
164
+ EventBuses: buses.slice(0, Limit).map(b => ({
165
+ Name: b.Name,
166
+ Arn: b.Arn,
167
+ State: b.State
168
+ }))
169
+ };
170
+ }
171
+
172
+ /**
173
+ * DescribeEventBus
174
+ * @param {Object} params
175
+ * @returns {Object}
176
+ */
177
+ describeEventBus(params = {}) {
178
+ const { Name = 'default' } = params;
179
+ const bus = this.buses.get(Name);
180
+ if (!bus) throw this._error('ResourceNotFoundException', `Event bus ${Name} not found`);
181
+ return bus;
182
+ }
183
+
184
+ // ==================== Rules ====================
185
+
186
+ /**
187
+ * PutRule
188
+ * @param {Object} params
189
+ * @returns {Object}
190
+ */
191
+ async putRule(params) {
192
+ const {
193
+ Name, EventBusName = 'default', EventPattern, ScheduleExpression,
194
+ State = 'ENABLED', Description, RoleArn, Tags
195
+ } = params;
196
+
197
+ if (!Name) throw this._error('ValidationException', 'Name is required');
198
+ if (!EventPattern && !ScheduleExpression) {
199
+ throw this._error('ValidationException', 'Either EventPattern or ScheduleExpression is required');
200
+ }
201
+
202
+ const bus = this.buses.get(EventBusName);
203
+ if (!bus) throw this._error('ResourceNotFoundException', `Event bus ${EventBusName} not found`);
204
+
205
+ // Validate EventPattern
206
+ let parsedPattern = null;
207
+ if (EventPattern) {
208
+ try {
209
+ parsedPattern = JSON.parse(EventPattern);
210
+ } catch {
211
+ throw this._error('InvalidEventPatternException', 'Event pattern is not valid JSON');
212
+ }
213
+ }
214
+
215
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
216
+
217
+ const rule = {
218
+ Name,
219
+ Arn: ruleArn,
220
+ EventBusName,
221
+ EventPattern: EventPattern || null,
222
+ ParsedPattern: parsedPattern,
223
+ ScheduleExpression: ScheduleExpression || null,
224
+ State,
225
+ Description: Description || '',
226
+ RoleArn: RoleArn || null,
227
+ Tags: Tags || [],
228
+ CreatedAt: new Date().toISOString()
229
+ };
230
+
231
+ this.rules.set(ruleArn, rule);
232
+ await this._save();
233
+
234
+ this.logger.info('EventBridge', `Put rule: ${Name} on bus ${EventBusName}`);
235
+ return { RuleArn: ruleArn };
236
+ }
237
+
238
+ /**
239
+ * DeleteRule
240
+ * @param {Object} params
241
+ */
242
+ async deleteRule(params) {
243
+ const { Name, EventBusName = 'default', Force } = params;
244
+
245
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
246
+
247
+ if (!this.rules.has(ruleArn)) {
248
+ throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
249
+ }
250
+
251
+ // Check for targets unless Force
252
+ const ruleTargets = this.targets.get(ruleArn) || [];
253
+ if (ruleTargets.length > 0 && !Force) {
254
+ throw this._error('ValidationException', 'Rule has targets. Use Force=true to delete anyway');
255
+ }
256
+
257
+ this.rules.delete(ruleArn);
258
+ this.targets.delete(ruleArn);
259
+ await this._save();
260
+
261
+ this.logger.info('EventBridge', `Deleted rule: ${Name}`);
262
+ }
263
+
264
+ /**
265
+ * ListRules
266
+ * @param {Object} params
267
+ * @returns {Object}
268
+ */
269
+ listRules(params = {}) {
270
+ const { EventBusName = 'default', NamePrefix, Limit = 100 } = params;
271
+ let rules = Array.from(this.rules.values())
272
+ .filter(r => r.EventBusName === EventBusName);
273
+
274
+ if (NamePrefix) {
275
+ rules = rules.filter(r => r.Name.startsWith(NamePrefix));
276
+ }
277
+
278
+ return {
279
+ Rules: rules.slice(0, Limit).map(r => ({
280
+ Name: r.Name,
281
+ Arn: r.Arn,
282
+ EventBusName: r.EventBusName,
283
+ EventPattern: r.EventPattern,
284
+ ScheduleExpression: r.ScheduleExpression,
285
+ State: r.State,
286
+ Description: r.Description
287
+ }))
288
+ };
289
+ }
290
+
291
+ /**
292
+ * DescribeRule
293
+ * @param {Object} params
294
+ * @returns {Object}
295
+ */
296
+ describeRule(params) {
297
+ const { Name, EventBusName = 'default' } = params;
298
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
299
+ const rule = this.rules.get(ruleArn);
300
+ if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
301
+ return rule;
302
+ }
303
+
304
+ /**
305
+ * EnableRule
306
+ * @param {Object} params
307
+ */
308
+ async enableRule(params) {
309
+ const { Name, EventBusName = 'default' } = params;
310
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
311
+ const rule = this.rules.get(ruleArn);
312
+ if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
313
+ rule.State = 'ENABLED';
314
+ await this._save();
315
+ }
316
+
317
+ /**
318
+ * DisableRule
319
+ * @param {Object} params
320
+ */
321
+ async disableRule(params) {
322
+ const { Name, EventBusName = 'default' } = params;
323
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Name}`;
324
+ const rule = this.rules.get(ruleArn);
325
+ if (!rule) throw this._error('ResourceNotFoundException', `Rule ${Name} not found`);
326
+ rule.State = 'DISABLED';
327
+ await this._save();
328
+ }
329
+
330
+ // ==================== Targets ====================
331
+
332
+ /**
333
+ * PutTargets
334
+ * @param {Object} params
335
+ * @returns {Object}
336
+ */
337
+ async putTargets(params) {
338
+ const { Rule, EventBusName = 'default', Targets } = params;
339
+
340
+ if (!Rule) throw this._error('ValidationException', 'Rule is required');
341
+ if (!Targets || !Targets.length) throw this._error('ValidationException', 'Targets are required');
342
+
343
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
344
+ if (!this.rules.has(ruleArn)) throw this._error('ResourceNotFoundException', `Rule ${Rule} not found`);
345
+
346
+ const existing = this.targets.get(ruleArn) || [];
347
+ const failedEntries = [];
348
+
349
+ for (const target of Targets) {
350
+ if (!target.Id || !target.Arn) {
351
+ failedEntries.push({ TargetId: target.Id, ErrorCode: 'ValidationException', ErrorMessage: 'Id and Arn are required' });
352
+ continue;
353
+ }
354
+
355
+ // Remove existing target with same Id
356
+ const idx = existing.findIndex(t => t.Id === target.Id);
357
+ if (idx >= 0) existing.splice(idx, 1);
358
+
359
+ existing.push({
360
+ Id: target.Id,
361
+ Arn: target.Arn,
362
+ Input: target.Input || null,
363
+ InputPath: target.InputPath || null,
364
+ InputTransformer: target.InputTransformer || null,
365
+ RoleArn: target.RoleArn || null,
366
+ RetryPolicy: target.RetryPolicy || { MaximumRetryAttempts: 185, MaximumEventAgeInSeconds: 86400 },
367
+ DeadLetterConfig: target.DeadLetterConfig || null
368
+ });
369
+ }
370
+
371
+ this.targets.set(ruleArn, existing);
372
+ await this._save();
373
+
374
+ this.logger.info('EventBridge', `Put ${Targets.length} targets for rule ${Rule}`);
375
+ return {
376
+ FailedEntryCount: failedEntries.length,
377
+ FailedEntries: failedEntries
378
+ };
379
+ }
380
+
381
+ /**
382
+ * RemoveTargets
383
+ * @param {Object} params
384
+ * @returns {Object}
385
+ */
386
+ async removeTargets(params) {
387
+ const { Rule, EventBusName = 'default', Ids } = params;
388
+
389
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
390
+ const existing = this.targets.get(ruleArn) || [];
391
+
392
+ const remaining = existing.filter(t => !Ids.includes(t.Id));
393
+ this.targets.set(ruleArn, remaining);
394
+ await this._save();
395
+
396
+ return { FailedEntryCount: 0, FailedEntries: [] };
397
+ }
398
+
399
+ /**
400
+ * ListTargetsByRule
401
+ * @param {Object} params
402
+ * @returns {Object}
403
+ */
404
+ listTargetsByRule(params) {
405
+ const { Rule, EventBusName = 'default' } = params;
406
+ const ruleArn = `arn:aws:events:${this.region}:${this.accountId}:rule/${EventBusName}/${Rule}`;
407
+
408
+ if (!this.rules.has(ruleArn)) throw this._error('ResourceNotFoundException', `Rule ${Rule} not found`);
409
+
410
+ const targets = this.targets.get(ruleArn) || [];
411
+ return { Targets: targets };
412
+ }
413
+
414
+ // ==================== PutEvents ====================
415
+
416
+ /**
417
+ * PutEvents
418
+ * @param {Object} params
419
+ * @returns {Object}
420
+ */
421
+ async putEvents(params) {
422
+ const { Entries } = params;
423
+
424
+ if (!Entries || !Entries.length) {
425
+ throw this._error('ValidationException', 'Entries are required');
426
+ }
427
+
428
+ const results = [];
429
+ const failedEntries = [];
430
+
431
+ for (const entry of Entries) {
432
+ const eventId = crypto.randomUUID();
433
+
434
+ // Validate entry
435
+ if (!entry.Source) {
436
+ failedEntries.push({ ErrorCode: 'ValidationException', ErrorMessage: 'Source is required' });
437
+ results.push({ EventId: null, ErrorCode: 'ValidationException' });
438
+ continue;
439
+ }
440
+
441
+ const busName = entry.EventBusName || 'default';
442
+ if (!this.buses.has(busName)) {
443
+ failedEntries.push({ ErrorCode: 'ResourceNotFoundException', ErrorMessage: `Bus ${busName} not found` });
444
+ results.push({ EventId: null, ErrorCode: 'ResourceNotFoundException' });
445
+ continue;
446
+ }
447
+
448
+ // Build event
449
+ let detail = entry.Detail;
450
+ if (typeof detail === 'string') {
451
+ try { detail = JSON.parse(detail); } catch { detail = {}; }
452
+ }
453
+
454
+ const event = {
455
+ id: eventId,
456
+ version: '0',
457
+ account: this.accountId,
458
+ time: entry.Time || new Date().toISOString(),
459
+ region: this.region,
460
+ source: entry.Source,
461
+ 'detail-type': entry.DetailType || '',
462
+ resources: entry.Resources || [],
463
+ detail: detail || {}
464
+ };
465
+
466
+ // Archive event
467
+ this.eventArchive.push(event);
468
+ if (this.eventArchive.length > 1000) {
469
+ this.eventArchive.shift();
470
+ }
471
+
472
+ // Match and deliver to rules
473
+ await this._matchAndDeliver(busName, event);
474
+
475
+ results.push({ EventId: eventId });
476
+ }
477
+
478
+ this.logger.debug('EventBridge', `PutEvents: ${Entries.length} events, ${failedEntries.length} failed`);
479
+
480
+ return {
481
+ FailedEntryCount: failedEntries.length,
482
+ Entries: results
483
+ };
484
+ }
485
+
486
+ /**
487
+ * Match event to rules and deliver to targets
488
+ * @param {string} busName
489
+ * @param {Object} event
490
+ */
491
+ async _matchAndDeliver(busName, event) {
492
+ const busRules = Array.from(this.rules.values())
493
+ .filter(r => r.EventBusName === busName && r.State === 'ENABLED');
494
+
495
+ for (const rule of busRules) {
496
+ if (rule.ParsedPattern && this._matchesPattern(event, rule.ParsedPattern)) {
497
+ const targets = this.targets.get(rule.Arn) || [];
498
+ for (const target of targets) {
499
+ try {
500
+ await this._deliverToTarget(target, event);
501
+ } catch (err) {
502
+ this.logger.warn('EventBridge', `Target delivery failed: ${err.message}`);
503
+ }
504
+ }
505
+ }
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Check if event matches pattern
511
+ * @param {Object} event
512
+ * @param {Object} pattern
513
+ * @returns {boolean}
514
+ */
515
+ _matchesPattern(event, pattern) {
516
+ for (const [key, matchers] of Object.entries(pattern)) {
517
+ const eventValue = key === 'detail' ? event.detail : event[key];
518
+
519
+ if (key === 'detail' && typeof matchers === 'object' && !Array.isArray(matchers)) {
520
+ // Recursive detail matching
521
+ if (!this._matchesPattern(event.detail || {}, matchers)) return false;
522
+ continue;
523
+ }
524
+
525
+ if (!Array.isArray(matchers)) continue;
526
+
527
+ const matches = matchers.some(matcher => {
528
+ if (matcher === null) return eventValue === null;
529
+ if (typeof matcher === 'string') return eventValue === matcher;
530
+ if (typeof matcher === 'object') {
531
+ if (matcher.prefix) return String(eventValue).startsWith(matcher.prefix);
532
+ if (matcher['anything-but']) {
533
+ return !matcher['anything-but'].includes(eventValue);
534
+ }
535
+ if (matcher.exists !== undefined) {
536
+ return matcher.exists ? eventValue !== undefined : eventValue === undefined;
537
+ }
538
+ if (matcher.numeric) return this._checkNumeric(parseFloat(eventValue), matcher.numeric);
539
+ }
540
+ return false;
541
+ });
542
+
543
+ if (!matches) return false;
544
+ }
545
+ return true;
546
+ }
547
+
548
+ /**
549
+ * Check numeric conditions
550
+ * @param {number} value
551
+ * @param {Array} conditions
552
+ * @returns {boolean}
553
+ */
554
+ _checkNumeric(value, conditions) {
555
+ for (let i = 0; i < conditions.length; i += 2) {
556
+ const op = conditions[i];
557
+ const threshold = conditions[i + 1];
558
+ if (op === '=' && value !== threshold) return false;
559
+ if (op === '>' && value <= threshold) return false;
560
+ if (op === '>=' && value < threshold) return false;
561
+ if (op === '<' && value >= threshold) return false;
562
+ if (op === '<=' && value > threshold) return false;
563
+ }
564
+ return true;
565
+ }
566
+
567
+ /**
568
+ * Deliver event to target
569
+ * @param {Object} target
570
+ * @param {Object} event
571
+ */
572
+ async _deliverToTarget(target, event) {
573
+ // Transform input
574
+ let inputEvent = event;
575
+ if (target.Input) {
576
+ try { inputEvent = JSON.parse(target.Input); } catch { inputEvent = target.Input; }
577
+ } else if (target.InputPath) {
578
+ inputEvent = this._extractPath(event, target.InputPath);
579
+ } else if (target.InputTransformer) {
580
+ inputEvent = this._transformInput(event, target.InputTransformer);
581
+ }
582
+
583
+ const arn = target.Arn;
584
+
585
+ // Lambda target
586
+ if (arn.includes(':lambda:') || arn.includes('function:')) {
587
+ if (!this.lambdaService) return;
588
+ const match = arn.match(/function:([^:]+)/);
589
+ if (!match) return;
590
+ await this.lambdaService.simulator.invokeFunction(match[1], inputEvent);
591
+ this.logger.debug('EventBridge', `Delivered to Lambda: ${match[1]}`);
592
+ return;
593
+ }
594
+
595
+ // SQS target
596
+ if (arn.includes(':sqs:') || arn.includes(':queue:')) {
597
+ if (!this.sqsService) return;
598
+ await this.sqsService.simulator.sendMessage({
599
+ QueueUrl: arn,
600
+ MessageBody: JSON.stringify(inputEvent)
601
+ });
602
+ this.logger.debug('EventBridge', `Delivered to SQS: ${arn}`);
603
+ return;
604
+ }
605
+
606
+ // SNS target
607
+ if (arn.includes(':sns:')) {
608
+ if (!this.snsService) return;
609
+ await this.snsService.simulator.publish({
610
+ TopicArn: arn,
611
+ Message: JSON.stringify(inputEvent)
612
+ });
613
+ this.logger.debug('EventBridge', `Delivered to SNS: ${arn}`);
614
+ return;
615
+ }
616
+
617
+ this.logger.warn('EventBridge', `Unsupported target ARN: ${arn}`);
618
+ }
619
+
620
+ /**
621
+ * Extract value at JSONPath
622
+ * @param {Object} obj
623
+ * @param {string} path
624
+ * @returns {*}
625
+ */
626
+ _extractPath(obj, path) {
627
+ if (path === '$') return obj;
628
+ const parts = path.replace(/^\$\./, '').split('.');
629
+ let current = obj;
630
+ for (const part of parts) {
631
+ if (current === null || current === undefined) return null;
632
+ current = current[part];
633
+ }
634
+ return current;
635
+ }
636
+
637
+ /**
638
+ * Transform input using InputTransformer
639
+ * @param {Object} event
640
+ * @param {Object} transformer
641
+ * @returns {*}
642
+ */
643
+ _transformInput(event, transformer) {
644
+ const { InputPathsMap, InputTemplate } = transformer;
645
+ let result = InputTemplate;
646
+
647
+ if (InputPathsMap && InputTemplate) {
648
+ for (const [key, path] of Object.entries(InputPathsMap)) {
649
+ const value = this._extractPath(event, path);
650
+ result = result.replace(new RegExp(`<${key}>`, 'g'), JSON.stringify(value));
651
+ }
652
+ }
653
+
654
+ try { return JSON.parse(result); } catch { return result; }
655
+ }
656
+
657
+ /**
658
+ * Reset all data
659
+ */
660
+ async reset() {
661
+ this.buses.clear();
662
+ this.rules.clear();
663
+ this.targets.clear();
664
+ this.eventArchive = [];
665
+ this._createDefaultBus();
666
+ await this._save();
667
+ this.logger.info('EventBridge', 'Data reset');
668
+ }
669
+
670
+ /**
671
+ * Create AWS-formatted error
672
+ * @param {string} code
673
+ * @param {string} message
674
+ * @returns {Error}
675
+ */
676
+ _error(code, message) {
677
+ const err = new Error(message);
678
+ err.code = code;
679
+ err.__type = code;
680
+ return err;
681
+ }
682
+ }
683
+
684
+ module.exports = { EventBridgeSimulator };