@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,605 @@
1
+ /**
2
+ * OpenSearch Simulator Core - Simulador Elasticsearch/OpenSearch
3
+ */
4
+
5
+ const Index = require('./models/index');
6
+ const Document = require('./models/document');
7
+ const OpenSearchStore = require('./store');
8
+ const logger = require('../../utils/logger');
9
+ const path = require('path');
10
+
11
+ class OpenSearchSimulator {
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.dataDir = path.join(dataDir, 'opensearch');
16
+
17
+ this.store = new OpenSearchStore(dataDir);
18
+ this.indices = new Map();
19
+ this.indexCache = new Map();
20
+ }
21
+
22
+ async initialize() {
23
+ logger.debug('Inicializando OpenSearch Simulator...');
24
+
25
+ // Carrega metadados persistidos
26
+ this.loadIndices();
27
+
28
+ // Cria índices da configuração
29
+ if (this.config.opensearch?.indices) {
30
+ for (const indexConfig of this.config.opensearch.indices) {
31
+ if (!this.indices.has(indexConfig.name)) {
32
+ this.createIndex(indexConfig.name, indexConfig);
33
+ }
34
+ }
35
+ }
36
+
37
+ logger.debug(`✅ OpenSearch Simulator inicializado com ${this.indices.size} índices`);
38
+ }
39
+
40
+ /**
41
+ * Carrega metadados de índices do disco
42
+ */
43
+ loadIndices() {
44
+ const metadata = this.store.loadMetadata();
45
+
46
+ for (const [indexName, indexDef] of Object.entries(metadata)) {
47
+ const index = new Index(indexName, indexDef.settings);
48
+ index.createdAt = indexDef.createdAt;
49
+ index.docsCount = indexDef.docsCount || 0;
50
+ index.storeSize = indexDef.storeSize || 0;
51
+ this.indices.set(indexName, index);
52
+
53
+ logger.debug(`Índice carregado: ${indexName}`);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Persiste metadados de índices
59
+ */
60
+ persistIndices() {
61
+ const metadata = {};
62
+
63
+ for (const [indexName, index] of this.indices) {
64
+ metadata[indexName] = {
65
+ name: index.name,
66
+ settings: index.settings,
67
+ mappings: index.mappings,
68
+ aliases: index.aliases,
69
+ createdAt: index.createdAt,
70
+ docsCount: index.docsCount,
71
+ storeSize: index.storeSize
72
+ };
73
+ }
74
+
75
+ this.store.saveMetadata(metadata);
76
+ }
77
+
78
+ // ========== Operações de Índice ==========
79
+
80
+ /**
81
+ * Cria um novo índice
82
+ */
83
+ createIndex(name, config = {}) {
84
+ if (this.indices.has(name)) {
85
+ throw new Error(`Index '${name}' already exists`);
86
+ }
87
+
88
+ const index = new Index(name, config);
89
+ this.indices.set(name, index);
90
+ this.persistIndices();
91
+
92
+ logger.info(`✅ Índice criado: ${name}`);
93
+
94
+ return {
95
+ acknowledged: true,
96
+ shards_acknowledged: true,
97
+ index: name
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Deleta um índice
103
+ */
104
+ deleteIndex(name) {
105
+ if (!this.indices.has(name)) {
106
+ throw new Error(`Index '${name}' does not exist`);
107
+ }
108
+
109
+ this.indices.delete(name);
110
+ this.indexCache.delete(name);
111
+ this.store.deleteIndex(name);
112
+ this.persistIndices();
113
+
114
+ logger.info(`🗑️ Índice deletado: ${name}`);
115
+
116
+ return {
117
+ acknowledged: true
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Obtém informações de um índice
123
+ */
124
+ getIndex(name) {
125
+ const index = this.indices.get(name);
126
+ if (!index) {
127
+ throw new Error(`Index '${name}' does not exist`);
128
+ }
129
+
130
+ // Atualiza tamanho
131
+ index.updateSize(this.store.getIndexSize(name));
132
+
133
+ return {
134
+ [name]: index.toJSON()
135
+ };
136
+ }
137
+
138
+ /**
139
+ * Lista todos os índices
140
+ */
141
+ listIndices() {
142
+ const indices = [];
143
+
144
+ for (const [name, index] of this.indices) {
145
+ index.updateSize(this.store.getIndexSize(name));
146
+ indices.push({
147
+ name: name,
148
+ status: index.status,
149
+ docs: index.docsCount,
150
+ size_mb: (index.storeSize / 1024 / 1024).toFixed(2),
151
+ created_at: index.createdAt
152
+ });
153
+ }
154
+
155
+ return indices;
156
+ }
157
+
158
+ /**
159
+ * Abre um índice (simples - todos começam abertos)
160
+ */
161
+ openIndex(name) {
162
+ const index = this.indices.get(name);
163
+ if (!index) throw new Error(`Index '${name}' does not exist`);
164
+
165
+ index.status = 'open';
166
+ this.persistIndices();
167
+
168
+ return { acknowledged: true };
169
+ }
170
+
171
+ /**
172
+ * Fecha um índice
173
+ */
174
+ closeIndex(name) {
175
+ const index = this.indices.get(name);
176
+ if (!index) throw new Error(`Index '${name}' does not exist`);
177
+
178
+ index.status = 'closed';
179
+ this.persistIndices();
180
+
181
+ return { acknowledged: true };
182
+ }
183
+
184
+ // ========== Operações de Documento ==========
185
+
186
+ /**
187
+ * Indexa um documento (PUT/POST)
188
+ */
189
+ indexDocument(indexName, document, documentId = null) {
190
+ if (!this.indices.has(indexName)) {
191
+ throw new Error(`Index '${indexName}' does not exist`);
192
+ }
193
+
194
+ // Cria objeto Document
195
+ const doc = new Document(document, {
196
+ _id: documentId || document._id,
197
+ _index: indexName,
198
+ _type: '_doc'
199
+ });
200
+
201
+ // Carrega documentos existentes
202
+ let documents = this.store.loadDocuments(indexName);
203
+ const existingIndex = documents.findIndex(d => d._id === doc._id);
204
+
205
+ const isNew = existingIndex === -1;
206
+ if (isNew) {
207
+ documents.push(doc.toJSON());
208
+ this.indices.get(indexName).addDocument();
209
+ } else {
210
+ documents[existingIndex] = doc.toJSON();
211
+ }
212
+
213
+ // Salva
214
+ this.store.saveDocuments(indexName, documents);
215
+ this.indexCache.delete(indexName);
216
+
217
+ logger.verboso(`Documento ${isNew ? 'criado' : 'atualizado'}: ${indexName}/${doc._id}`);
218
+
219
+ return {
220
+ _index: indexName,
221
+ _type: '_doc',
222
+ _id: doc._id,
223
+ _version: doc._version,
224
+ result: isNew ? 'created' : 'updated',
225
+ _shards: { total: 2, successful: 1, failed: 0 },
226
+ _seq_no: doc._seq_no,
227
+ _primary_term: doc._primary_term
228
+ };
229
+ }
230
+
231
+ /**
232
+ * Obtém um documento por ID
233
+ */
234
+ getDocument(indexName, documentId) {
235
+ if (!this.indices.has(indexName)) {
236
+ throw new Error(`Index '${indexName}' does not exist`);
237
+ }
238
+
239
+ const documents = this.store.loadDocuments(indexName);
240
+ const doc = documents.find(d => d._id === documentId);
241
+
242
+ if (!doc) {
243
+ return {
244
+ _index: indexName,
245
+ _type: '_doc',
246
+ _id: documentId,
247
+ found: false
248
+ };
249
+ }
250
+
251
+ logger.verboso(`Documento encontrado: ${indexName}/${documentId}`);
252
+
253
+ return {
254
+ _index: indexName,
255
+ _type: '_doc',
256
+ _id: documentId,
257
+ _version: doc._version,
258
+ _seq_no: doc._seq_no,
259
+ _primary_term: doc._primary_term,
260
+ found: true,
261
+ _source: doc._source
262
+ };
263
+ }
264
+
265
+ /**
266
+ * Deleta um documento
267
+ */
268
+ deleteDocument(indexName, documentId) {
269
+ if (!this.indices.has(indexName)) {
270
+ throw new Error(`Index '${indexName}' does not exist`);
271
+ }
272
+
273
+ const deleted = this.store.removeDocument(indexName, documentId);
274
+
275
+ if (deleted) {
276
+ this.indices.get(indexName).removeDocument();
277
+ this.indexCache.delete(indexName);
278
+ logger.verboso(`Documento deletado: ${indexName}/${documentId}`);
279
+ }
280
+
281
+ return {
282
+ _index: indexName,
283
+ _type: '_doc',
284
+ _id: documentId,
285
+ _version: 2,
286
+ result: deleted ? 'deleted' : 'not_found',
287
+ _shards: { total: 2, successful: 1, failed: 0 },
288
+ _seq_no: 1,
289
+ _primary_term: 1
290
+ };
291
+ }
292
+
293
+ /**
294
+ * Busca documentos com query
295
+ */
296
+ search(indexName, queryParams = {}) {
297
+ if (!this.indices.has(indexName)) {
298
+ throw new Error(`Index '${indexName}' does not exist`);
299
+ }
300
+
301
+ let documents = this.store.loadDocuments(indexName);
302
+ const {
303
+ query = {},
304
+ from = 0,
305
+ size = 10,
306
+ sort = [],
307
+ highlight = false
308
+ } = queryParams;
309
+
310
+ let results = documents;
311
+
312
+ // Aplicar query filter
313
+ if (query.match) {
314
+ const [field, value] = Object.entries(query.match)[0];
315
+ results = results.filter(doc => {
316
+ const fieldValue = doc._source[field];
317
+ if (!fieldValue) return false;
318
+ return String(fieldValue).toLowerCase().includes(String(value).toLowerCase());
319
+ });
320
+ }
321
+
322
+ if (query.bool) {
323
+ // Suporte básico para bool queries
324
+ if (query.bool.must) {
325
+ for (const mustClause of query.bool.must) {
326
+ if (mustClause.match) {
327
+ const [field, value] = Object.entries(mustClause.match)[0];
328
+ results = results.filter(doc => {
329
+ const fieldValue = doc._source[field];
330
+ if (!fieldValue) return false;
331
+ return String(fieldValue).toLowerCase().includes(String(value).toLowerCase());
332
+ });
333
+ }
334
+ }
335
+ }
336
+
337
+ if (query.bool.should) {
338
+ // OR logic
339
+ const shouldMatches = [];
340
+ for (const shouldClause of query.bool.should) {
341
+ if (shouldClause.match) {
342
+ const [field, value] = Object.entries(shouldClause.match)[0];
343
+ shouldMatches.push(
344
+ results.filter(doc => {
345
+ const fieldValue = doc._source[field];
346
+ if (!fieldValue) return false;
347
+ return String(fieldValue).toLowerCase().includes(String(value).toLowerCase());
348
+ })
349
+ );
350
+ }
351
+ }
352
+ results = [...new Map(shouldMatches.flat().map(item => [item._id, item])).values()];
353
+ }
354
+ }
355
+
356
+ if (query.query_string) {
357
+ // Full-text search
358
+ const text = query.query_string.query.toLowerCase();
359
+ results = results.filter(doc => {
360
+ for (const value of Object.values(doc._source)) {
361
+ if (String(value).toLowerCase().includes(text)) return true;
362
+ }
363
+ return false;
364
+ });
365
+ }
366
+
367
+ // Aplicar sorting
368
+ if (sort.length > 0) {
369
+ for (const sortSpec of sort) {
370
+ if (typeof sortSpec === 'string') {
371
+ results.sort((a, b) => {
372
+ const aVal = a._source[sortSpec];
373
+ const bVal = b._source[sortSpec];
374
+ return aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
375
+ });
376
+ } else if (typeof sortSpec === 'object') {
377
+ const [field, direction] = Object.entries(sortSpec)[0];
378
+ results.sort((a, b) => {
379
+ const aVal = a._source[field];
380
+ const bVal = b._source[field];
381
+ const cmp = aVal < bVal ? -1 : aVal > bVal ? 1 : 0;
382
+ return direction.order === 'desc' ? -cmp : cmp;
383
+ });
384
+ }
385
+ }
386
+ }
387
+
388
+ // Paginação
389
+ const total = results.length;
390
+ const hits = results.slice(from, from + size);
391
+
392
+ logger.verboso(`Busca em ${indexName}: ${total} resultados, retornando ${hits.length}`);
393
+
394
+ return {
395
+ took: 5,
396
+ timed_out: false,
397
+ _shards: { total: 5, successful: 5, skipped: 0, failed: 0 },
398
+ hits: {
399
+ total: { value: total, relation: 'eq' },
400
+ max_score: hits.length > 0 ? 1.0 : null,
401
+ hits: hits.map(doc => ({
402
+ _index: indexName,
403
+ _type: '_doc',
404
+ _id: doc._id,
405
+ _score: 1.0,
406
+ _source: doc._source
407
+ }))
408
+ }
409
+ };
410
+ }
411
+
412
+ /**
413
+ * Busca agregada (simples)
414
+ */
415
+ aggregate(indexName, aggs = {}) {
416
+ if (!this.indices.has(indexName)) {
417
+ throw new Error(`Index '${indexName}' does not exist`);
418
+ }
419
+
420
+ const documents = this.store.loadDocuments(indexName);
421
+ const result = { aggregations: {} };
422
+
423
+ for (const [aggName, aggSpec] of Object.entries(aggs)) {
424
+ if (aggSpec.terms) {
425
+ // Agregação por termos
426
+ const field = aggSpec.terms.field;
427
+ const buckets = {};
428
+
429
+ documents.forEach(doc => {
430
+ const value = doc._source[field];
431
+ if (value) {
432
+ buckets[value] = (buckets[value] || 0) + 1;
433
+ }
434
+ });
435
+
436
+ result.aggregations[aggName] = {
437
+ buckets: Object.entries(buckets)
438
+ .map(([key, count]) => ({ key, doc_count: count }))
439
+ .sort((a, b) => b.doc_count - a.doc_count)
440
+ .slice(0, aggSpec.terms.size || 10)
441
+ };
442
+ }
443
+
444
+ if (aggSpec.avg || aggSpec.sum) {
445
+ // Agregação numérica
446
+ const spec = aggSpec.avg || aggSpec.sum;
447
+ const field = spec.field;
448
+ const values = documents.map(doc => Number(doc._source[field])).filter(v => !isNaN(v));
449
+
450
+ if (aggSpec.avg) {
451
+ result.aggregations[aggName] = {
452
+ value: values.length > 0 ? values.reduce((a, b) => a + b) / values.length : 0
453
+ };
454
+ } else {
455
+ result.aggregations[aggName] = {
456
+ value: values.reduce((a, b) => a + b, 0)
457
+ };
458
+ }
459
+ }
460
+ }
461
+
462
+ return result;
463
+ }
464
+
465
+ /**
466
+ * Atualiza um documento por query
467
+ */
468
+ updateByQuery(indexName, updateScript, query = {}) {
469
+ if (!this.indices.has(indexName)) {
470
+ throw new Error(`Index '${indexName}' does not exist`);
471
+ }
472
+
473
+ let documents = this.store.loadDocuments(indexName);
474
+ let updated = 0;
475
+
476
+ documents = documents.map(doc => {
477
+ let matches = true;
478
+
479
+ if (Object.keys(query).length > 0) {
480
+ for (const [field, value] of Object.entries(query)) {
481
+ if (doc._source[field] !== value) {
482
+ matches = false;
483
+ break;
484
+ }
485
+ }
486
+ }
487
+
488
+ if (matches && updateScript) {
489
+ // Simples: executar update script
490
+ doc._source = { ...doc._source, ...updateScript };
491
+ doc._version++;
492
+ doc._updatedAt = new Date().toISOString();
493
+ updated++;
494
+ }
495
+
496
+ return doc;
497
+ });
498
+
499
+ this.store.saveDocuments(indexName, documents);
500
+ this.indexCache.delete(indexName);
501
+
502
+ logger.info(`Atualizado ${updated} documentos em ${indexName}`);
503
+
504
+ return {
505
+ took: 10,
506
+ timed_out: false,
507
+ total: updated,
508
+ updated: updated,
509
+ deleted: 0,
510
+ batches: 1,
511
+ version_conflicts: 0,
512
+ noops: 0,
513
+ retries: { bulk: 0, search: 0 },
514
+ throttled_millis: 0,
515
+ requests_per_second: -1.0,
516
+ throttled_until_millis: 0,
517
+ failures: []
518
+ };
519
+ }
520
+
521
+ /**
522
+ * Deleta documentos por query
523
+ */
524
+ deleteByQuery(indexName, query = {}) {
525
+ if (!this.indices.has(indexName)) {
526
+ throw new Error(`Index '${indexName}' does not exist`);
527
+ }
528
+
529
+ let documents = this.store.loadDocuments(indexName);
530
+ let deleted = 0;
531
+
532
+ documents = documents.filter(doc => {
533
+ let matches = true;
534
+
535
+ for (const [field, value] of Object.entries(query)) {
536
+ if (doc._source[field] !== value) {
537
+ matches = false;
538
+ break;
539
+ }
540
+ }
541
+
542
+ if (matches) {
543
+ deleted++;
544
+ this.indices.get(indexName).removeDocument();
545
+ return false;
546
+ }
547
+
548
+ return true;
549
+ });
550
+
551
+ this.store.saveDocuments(indexName, documents);
552
+ this.indexCache.delete(indexName);
553
+
554
+ logger.info(`Deletado ${deleted} documentos de ${indexName}`);
555
+
556
+ return {
557
+ took: 10,
558
+ timed_out: false,
559
+ total: deleted,
560
+ deleted: deleted,
561
+ batches: 1,
562
+ version_conflicts: 0,
563
+ noops: 0,
564
+ retries: { bulk: 0, search: 0 },
565
+ throttled_millis: 0,
566
+ requests_per_second: -1.0,
567
+ throttled_until_millis: 0,
568
+ failures: []
569
+ };
570
+ }
571
+
572
+ /**
573
+ * Retorna estatísticas de cluster
574
+ */
575
+ getClusterStats() {
576
+ let totalDocs = 0;
577
+ let totalSize = 0;
578
+
579
+ for (const index of this.indices.values()) {
580
+ totalDocs += index.docsCount;
581
+ totalSize += index.storeSize;
582
+ }
583
+
584
+ return {
585
+ status: 'green',
586
+ indices: this.indices.size,
587
+ shards: this.indices.size * 2,
588
+ replicas: this.indices.size,
589
+ docs: totalDocs,
590
+ size_mb: (totalSize / 1024 / 1024).toFixed(2)
591
+ };
592
+ }
593
+
594
+ /**
595
+ * Reset - limpa tudo
596
+ */
597
+ async reset() {
598
+ this.indices.clear();
599
+ this.indexCache.clear();
600
+ this.store.clear();
601
+ logger.info('🔄 OpenSearch resetado');
602
+ }
603
+ }
604
+
605
+ module.exports = OpenSearchSimulator;