@gugananuvem/aws-local-simulator 1.0.34 → 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.
- package/README.md +662 -658
- package/package.json +11 -4
- package/src/config/default-config.js +71 -0
- package/src/config/env-loader.js +4 -2
- package/src/middleware/authorization.js +271 -0
- package/src/middleware/cloudtrail-logger.js +333 -0
- package/src/middleware/error-handler.js +138 -0
- package/src/middleware/index.js +79 -0
- package/src/middleware/policy-enforcement.js +407 -0
- package/src/server.js +74 -4
- package/src/services/acm/index.js +66 -0
- package/src/services/acm/models/certificate.js +202 -0
- package/src/services/acm/server.js +224 -0
- package/src/services/acm/simulator.js +293 -0
- package/src/services/alb/index.js +104 -0
- package/src/services/alb/models/loadBalancer.js +88 -0
- package/src/services/alb/models/targetGroup.js +128 -0
- package/src/services/alb/server.js +160 -0
- package/src/services/alb/simulator.js +262 -0
- package/src/services/apigateway/index.js +22 -0
- package/src/services/apigateway/server.js +247 -35
- package/src/services/apigateway/simulator.js +780 -26
- package/src/services/asg/index.js +100 -0
- package/src/services/asg/models/autoScalingGroup.js +110 -0
- package/src/services/asg/models/launchTemplate.js +41 -0
- package/src/services/asg/models/scalingPolicy.js +51 -0
- package/src/services/asg/server.js +236 -0
- package/src/services/asg/simulator.js +410 -0
- package/src/services/aurora/cluster-manager.js +731 -0
- package/src/services/aurora/data-api.js +514 -0
- package/src/services/aurora/db-engine.js +270 -0
- package/src/services/aurora/index.js +159 -0
- package/src/services/aurora/rds-api.js +189 -0
- package/src/services/aurora/server.js +213 -0
- package/src/services/aurora/sql-translator.js +334 -0
- package/src/services/cloudformation/index.js +2 -2
- package/src/services/cloudformation/server.js +1 -0
- package/src/services/cloudfront/cache-engine.js +225 -0
- package/src/services/cloudfront/geo-restrictions.js +377 -0
- package/src/services/cloudfront/index.js +220 -0
- package/src/services/cloudfront/models/behavior.js +158 -0
- package/src/services/cloudfront/models/distribution.js +284 -0
- package/src/services/cloudfront/models/index.js +99 -0
- package/src/services/cloudfront/models/invalidation.js +109 -0
- package/src/services/cloudfront/models/origin.js +155 -0
- package/src/services/cloudfront/origin-router.js +552 -0
- package/src/services/cloudfront/origin-validator.js +398 -0
- package/src/services/cloudfront/server.js +512 -0
- package/src/services/cloudfront/signed-url-generator.js +377 -0
- package/src/services/cloudfront/simulator.js +425 -0
- package/src/services/cloudfront/store.js +326 -0
- package/src/services/cloudshell/env-builder.js +27 -0
- package/src/services/cloudshell/index.js +48 -0
- package/src/services/cloudshell/routes.js +60 -0
- package/src/services/cloudshell/session-manager.js +226 -0
- package/src/services/cloudshell/static/.gitkeep +0 -0
- package/src/services/cloudshell/static/index.html +406 -0
- package/src/services/cloudshell/websocket-handler.js +107 -0
- package/src/services/cloudtrail/index.js +2 -3
- package/src/services/cloudtrail/server.js +68 -0
- package/src/services/cognito/server.js +4 -1
- package/src/services/dynamodb/server.js +4 -1
- package/src/services/dynamodb/simulator.js +2 -1
- package/src/services/ec2/index.js +101 -0
- package/src/services/ec2/instanceServer.js +311 -0
- package/src/services/ec2/models/ami.js +39 -0
- package/src/services/ec2/models/instance.js +98 -0
- package/src/services/ec2/models/networkInterface.js +74 -0
- package/src/services/ec2/models/securityGroup.js +71 -0
- package/src/services/ec2/server.js +350 -0
- package/src/services/ec2/simulator.js +436 -0
- package/src/services/eventbridge/server.js +36 -0
- package/src/services/eventbridge/simulator.js +230 -45
- package/src/services/iam/index.js +65 -0
- package/src/services/iam/middleware.js +99 -0
- package/src/services/iam/server.js +331 -0
- package/src/services/iam/simulator.js +1432 -0
- package/src/services/index.js +28 -0
- package/src/services/kinesis-analytics/index.js +105 -0
- package/src/services/kinesis-analytics/models/application.js +325 -0
- package/src/services/kinesis-analytics/models/index.js +12 -0
- package/src/services/kinesis-analytics/server.js +250 -0
- package/src/services/kinesis-analytics/simulator.js +407 -0
- package/src/services/kinesis-analytics/store.js +176 -0
- package/src/services/kinesis-data-streams/index.js +106 -0
- package/src/services/kinesis-data-streams/models/index.js +12 -0
- package/src/services/kinesis-data-streams/models/record.js +70 -0
- package/src/services/kinesis-data-streams/models/stream.js +280 -0
- package/src/services/kinesis-data-streams/server.js +255 -0
- package/src/services/kinesis-data-streams/simulator.js +514 -0
- package/src/services/kinesis-data-streams/store.js +163 -0
- package/src/services/kinesis-firehose/index.js +106 -0
- package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
- package/src/services/kinesis-firehose/models/index.js +10 -0
- package/src/services/kinesis-firehose/server.js +215 -0
- package/src/services/kinesis-firehose/simulator.js +388 -0
- package/src/services/kinesis-firehose/store.js +163 -0
- package/src/services/kms/server.js +4 -1
- package/src/services/lambda/handler-loader.js +50 -5
- package/src/services/lambda/index.js +26 -0
- package/src/services/lambda/server.js +18 -12
- package/src/services/lambda/simulator.js +73 -17
- package/src/services/opensearch/index.js +105 -0
- package/src/services/opensearch/models/document.js +115 -0
- package/src/services/opensearch/models/index.js +73 -0
- package/src/services/opensearch/server.js +310 -0
- package/src/services/opensearch/simulator.js +605 -0
- package/src/services/opensearch/store.js +162 -0
- package/src/services/organizations/control-tower-simulator.js +321 -0
- package/src/services/organizations/control-tower-store.js +182 -0
- package/src/services/organizations/iam-integration.js +413 -0
- package/src/services/organizations/index.js +172 -0
- package/src/services/organizations/models/index.js +13 -0
- package/src/services/organizations/models/invitation.js +48 -0
- package/src/services/organizations/models/organization.js +566 -0
- package/src/services/organizations/models/role.js +244 -0
- package/src/services/organizations/models/scp.js +131 -0
- package/src/services/organizations/server.js +736 -0
- package/src/services/organizations/simulator.js +593 -0
- package/src/services/organizations/store.js +235 -0
- package/src/services/organizations/vpc-integration.js +453 -0
- package/src/services/route53/index.js +67 -0
- package/src/services/route53/models/healthCheck.js +101 -0
- package/src/services/route53/models/hostedZone.js +37 -0
- package/src/services/route53/models/resourceRecord.js +106 -0
- package/src/services/route53/server.js +841 -0
- package/src/services/route53/simulator.js +350 -0
- package/src/services/s3/index.js +62 -0
- package/src/services/s3/presigned-url-generator.js +337 -0
- package/src/services/s3/server.js +231 -0
- package/src/services/s3/simulator.js +551 -4
- package/src/services/sns/index.js +31 -0
- package/src/services/sns/server.js +9 -0
- package/src/services/sqs/index.js +38 -0
- package/src/services/sqs/server.js +57 -4
- package/src/services/sqs/simulator.js +423 -33
- package/src/services/sts/simulator.js +209 -17
- package/src/services/vpc/README.md +459 -0
- package/src/services/vpc/ec2ResponseFormatter.js +431 -0
- package/src/services/vpc/index.js +90 -0
- package/src/services/vpc/models/internetGateway.js +71 -0
- package/src/services/vpc/models/routeTable.js +160 -0
- package/src/services/vpc/models/subnet.js +48 -0
- package/src/services/vpc/models/vpc.js +37 -0
- package/src/services/vpc/models/vpcEndpoint.js +100 -0
- package/src/services/vpc/models/vpcGateway.js +134 -0
- package/src/services/vpc/server.js +836 -0
- package/src/services/vpc/simulator.js +613 -0
- package/src/utils/policy-cache.js +215 -0
- package/aws-config +0 -154
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Analytics Store - Persistência de aplicações em JSON
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const logger = require('../../utils/logger');
|
|
8
|
+
|
|
9
|
+
class KinesisAnalyticsStore {
|
|
10
|
+
constructor(dataDir) {
|
|
11
|
+
this.dataDir = dataDir;
|
|
12
|
+
this.applicationsDir = path.join(dataDir, 'kinesis-analytics');
|
|
13
|
+
this.metadataFile = path.join(this.applicationsDir, '__applications__.json');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(this.applicationsDir)) {
|
|
16
|
+
fs.mkdirSync(this.applicationsDir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Carrega metadados de todas as aplicações
|
|
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 todas as aplicações
|
|
38
|
+
*/
|
|
39
|
+
saveMetadata(metadata) {
|
|
40
|
+
try {
|
|
41
|
+
fs.writeFileSync(this.metadataFile, JSON.stringify(metadata, null, 2), 'utf8');
|
|
42
|
+
logger.debug(`Metadados de aplicações 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 aplicação
|
|
50
|
+
*/
|
|
51
|
+
getApplicationPath(applicationName) {
|
|
52
|
+
return path.join(this.applicationsDir, `${applicationName}.json`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Carrega código SQL de uma aplicação
|
|
57
|
+
*/
|
|
58
|
+
loadApplicationCode(applicationName) {
|
|
59
|
+
const appPath = this.getApplicationPath(applicationName);
|
|
60
|
+
|
|
61
|
+
if (!fs.existsSync(appPath)) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const data = fs.readFileSync(appPath, 'utf8');
|
|
67
|
+
const appData = JSON.parse(data);
|
|
68
|
+
return appData.sqlCode || '';
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.warn(`Erro ao ler aplicação ${applicationName}: ${error.message}`);
|
|
71
|
+
return '';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Salva código SQL de uma aplicação
|
|
77
|
+
*/
|
|
78
|
+
saveApplicationCode(applicationName, sqlCode) {
|
|
79
|
+
try {
|
|
80
|
+
const appPath = this.getApplicationPath(applicationName);
|
|
81
|
+
const appData = {
|
|
82
|
+
applicationName,
|
|
83
|
+
sqlCode,
|
|
84
|
+
lastModified: new Date().toISOString()
|
|
85
|
+
};
|
|
86
|
+
fs.writeFileSync(appPath, JSON.stringify(appData, null, 2), 'utf8');
|
|
87
|
+
logger.debug(`Código da aplicação ${applicationName} salvo`);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.error(`Erro ao salvar aplicação ${applicationName}: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Carrega resultados de query
|
|
95
|
+
*/
|
|
96
|
+
loadQueryResults(applicationName, queryId) {
|
|
97
|
+
const appPath = this.getApplicationPath(applicationName);
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(appPath)) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const data = fs.readFileSync(appPath, 'utf8');
|
|
105
|
+
const appData = JSON.parse(data);
|
|
106
|
+
const queryResults = appData.queryResults || {};
|
|
107
|
+
return queryResults[queryId] || [];
|
|
108
|
+
} catch (error) {
|
|
109
|
+
logger.warn(`Erro ao ler resultados de query ${queryId}: ${error.message}`);
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Salva resultados de query
|
|
116
|
+
*/
|
|
117
|
+
saveQueryResults(applicationName, queryId, results) {
|
|
118
|
+
try {
|
|
119
|
+
const appPath = this.getApplicationPath(applicationName);
|
|
120
|
+
let appData = {};
|
|
121
|
+
|
|
122
|
+
if (fs.existsSync(appPath)) {
|
|
123
|
+
const data = fs.readFileSync(appPath, 'utf8');
|
|
124
|
+
appData = JSON.parse(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!appData.queryResults) {
|
|
128
|
+
appData.queryResults = {};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
appData.queryResults[queryId] = results;
|
|
132
|
+
appData.lastModified = new Date().toISOString();
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(appPath, JSON.stringify(appData, null, 2), 'utf8');
|
|
135
|
+
logger.debug(`Resultados da query ${queryId} salvos para aplicação ${applicationName}`);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.error(`Erro ao salvar resultados de query ${queryId}: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Deleta aplicação completamente
|
|
143
|
+
*/
|
|
144
|
+
deleteApplication(applicationName) {
|
|
145
|
+
try {
|
|
146
|
+
const appPath = this.getApplicationPath(applicationName);
|
|
147
|
+
if (fs.existsSync(appPath)) {
|
|
148
|
+
fs.unlinkSync(appPath);
|
|
149
|
+
logger.debug(`Aplicação ${applicationName} deletada`);
|
|
150
|
+
}
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error(`Erro ao deletar aplicação ${applicationName}: ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Limpa todas as aplicações
|
|
158
|
+
*/
|
|
159
|
+
clear() {
|
|
160
|
+
try {
|
|
161
|
+
if (fs.existsSync(this.applicationsDir)) {
|
|
162
|
+
const files = fs.readdirSync(this.applicationsDir);
|
|
163
|
+
files.forEach(file => {
|
|
164
|
+
if (file.endsWith('.json')) {
|
|
165
|
+
fs.unlinkSync(path.join(this.applicationsDir, file));
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
logger.debug('Todas as aplicações foram limpas');
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
logger.error(`Erro ao limpar aplicações: ${error.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = KinesisAnalyticsStore;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Streams Service - Ponto de entrada
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const KinesisDataStreamsServer = require('./server');
|
|
6
|
+
const KinesisDataStreamsSimulator = require('./simulator');
|
|
7
|
+
|
|
8
|
+
class KinesisDataStreamsService {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.name = 'kinesis-data-streams';
|
|
12
|
+
this.port = config.ports['kinesis-data-streams'] || 4568;
|
|
13
|
+
this.server = null;
|
|
14
|
+
this.simulator = null;
|
|
15
|
+
this.isRunning = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async initialize() {
|
|
19
|
+
const logger = require('../../utils/logger');
|
|
20
|
+
logger.debug(`Inicializando Kinesis Data Streams Service na porta ${this.port}...`);
|
|
21
|
+
|
|
22
|
+
this.simulator = new KinesisDataStreamsSimulator(this.config);
|
|
23
|
+
await this.simulator.initialize();
|
|
24
|
+
|
|
25
|
+
this.server = new KinesisDataStreamsServer(this.port, this.config);
|
|
26
|
+
this.server.simulator = this.simulator;
|
|
27
|
+
|
|
28
|
+
await this.server.initialize();
|
|
29
|
+
|
|
30
|
+
logger.debug('Kinesis Data Streams Service inicializado');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
injectDependencies(server) {
|
|
34
|
+
// Aqui você pode injetar dependências de outros serviços se necessário
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async start() {
|
|
38
|
+
const logger = require('../../utils/logger');
|
|
39
|
+
|
|
40
|
+
if (this.isRunning) {
|
|
41
|
+
logger.warn('Kinesis Data Streams Service já está em execução');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await this.server.start();
|
|
47
|
+
this.isRunning = true;
|
|
48
|
+
logger.info('Kinesis Data Streams Service iniciado com sucesso');
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(`Erro ao iniciar Kinesis Data Streams Service: ${error.message}`);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async stop() {
|
|
56
|
+
const logger = require('../../utils/logger');
|
|
57
|
+
|
|
58
|
+
if (!this.isRunning) {
|
|
59
|
+
logger.warn('Kinesis Data Streams Service não está em execução');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
await this.server.stop();
|
|
65
|
+
this.isRunning = false;
|
|
66
|
+
logger.info('Kinesis Data Streams Service parado com sucesso');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error(`Erro ao parar Kinesis Data Streams Service: ${error.message}`);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async reset() {
|
|
74
|
+
const logger = require('../../utils/logger');
|
|
75
|
+
try {
|
|
76
|
+
await this.simulator.reset();
|
|
77
|
+
logger.info('Kinesis Data Streams Service resetado');
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(`Erro ao resetar Kinesis Data Streams Service: ${error.message}`);
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getSimulator() {
|
|
85
|
+
return this.simulator;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getStatus() {
|
|
89
|
+
const stats = this.simulator?.getStats() || { streamsCount: 0, consumersCount: 0, totalRecords: 0 };
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
name: this.name,
|
|
93
|
+
running: this.isRunning,
|
|
94
|
+
port: this.port,
|
|
95
|
+
endpoint: `http://localhost:${this.port}`,
|
|
96
|
+
resources: {
|
|
97
|
+
streamsCount: stats.streamsCount,
|
|
98
|
+
consumersCount: stats.consumersCount,
|
|
99
|
+
totalRecords: stats.totalRecords,
|
|
100
|
+
totalShards: stats.totalShards
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = KinesisDataStreamsService;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Streams - Record Model
|
|
3
|
+
* Representa um record (mensagem) dentro de um shard
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { v4: uuidv4 } = require('uuid');
|
|
7
|
+
|
|
8
|
+
class Record {
|
|
9
|
+
constructor(data, partitionKey, config = {}) {
|
|
10
|
+
this.recordId = uuidv4();
|
|
11
|
+
this.data = data; // Base64 encoded por padrão
|
|
12
|
+
this.partitionKey = partitionKey;
|
|
13
|
+
this.explicitHashKey = config.explicitHashKey || null;
|
|
14
|
+
this.sequenceNumber = null; // Será atribuído pelo shard
|
|
15
|
+
this.timestamp = new Date().toISOString();
|
|
16
|
+
this.approximateArrivalTimestamp = new Date();
|
|
17
|
+
|
|
18
|
+
// Metadados
|
|
19
|
+
this.shardId = config.shardId || null;
|
|
20
|
+
this.encryptionType = config.encryptionType || 'NONE';
|
|
21
|
+
|
|
22
|
+
// Dados do consumidor (quando acessado via consumer)
|
|
23
|
+
this.consumerSequenceNumber = null;
|
|
24
|
+
this.offsetInMilliseconds = null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Retorna record em formato Kinesis (sem dados internos)
|
|
29
|
+
*/
|
|
30
|
+
toJSON() {
|
|
31
|
+
return {
|
|
32
|
+
Data: this.data,
|
|
33
|
+
PartitionKey: this.partitionKey,
|
|
34
|
+
ExplicitHashKey: this.explicitHashKey,
|
|
35
|
+
SequenceNumber: this.sequenceNumber,
|
|
36
|
+
ApproximateArrivalTimestamp: this.approximateArrivalTimestamp.toISOString(),
|
|
37
|
+
EncryptionType: this.encryptionType
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Retorna record em formato consumer
|
|
43
|
+
*/
|
|
44
|
+
toConsumerJSON() {
|
|
45
|
+
return {
|
|
46
|
+
Data: this.data,
|
|
47
|
+
PartitionKey: this.partitionKey,
|
|
48
|
+
SequenceNumber: this.consumerSequenceNumber || this.sequenceNumber,
|
|
49
|
+
ApproximateArrivalTimestamp: this.approximateArrivalTimestamp.toISOString(),
|
|
50
|
+
EncryptionType: this.encryptionType
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Obtém tamanho do record em bytes
|
|
56
|
+
*/
|
|
57
|
+
getSize() {
|
|
58
|
+
return Buffer.byteLength(this.data) + Buffer.byteLength(this.partitionKey);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Marca record como consumido
|
|
63
|
+
*/
|
|
64
|
+
markAsConsumed(consumerArn, sequenceNumber) {
|
|
65
|
+
this.consumerSequenceNumber = sequenceNumber;
|
|
66
|
+
this.offsetInMilliseconds = Date.now();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = Record;
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kinesis Data Streams - Stream Model
|
|
3
|
+
* Representa um stream Kinesis com seus shards, registros e metadados
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { v4: uuidv4 } = require('uuid');
|
|
7
|
+
|
|
8
|
+
class Stream {
|
|
9
|
+
constructor(streamName, config = {}) {
|
|
10
|
+
this.streamName = streamName;
|
|
11
|
+
this.streamArn = `arn:aws:kinesis:us-east-1:123456789012:stream/${streamName}`;
|
|
12
|
+
this.streamStatus = 'ACTIVE';
|
|
13
|
+
this.streamModeDetails = {
|
|
14
|
+
streamMode: config.streamMode || 'PROVISIONED'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Configuração de shards (só para PROVISIONED)
|
|
18
|
+
this.shardCount = config.shardCount || 1;
|
|
19
|
+
this.shards = [];
|
|
20
|
+
this.initializeShards();
|
|
21
|
+
|
|
22
|
+
// Metadados
|
|
23
|
+
this.creationTimestamp = new Date().toISOString();
|
|
24
|
+
this.lastUpdateTimestamp = new Date().toISOString();
|
|
25
|
+
this.retentionPeriod = config.retentionPeriod || 24; // horas
|
|
26
|
+
this.enhancedMonitoring = config.enhancedMonitoring || ['ALL'];
|
|
27
|
+
|
|
28
|
+
// Consumidores
|
|
29
|
+
this.consumers = new Map();
|
|
30
|
+
this.consumerCount = 0;
|
|
31
|
+
|
|
32
|
+
// Estatísticas
|
|
33
|
+
this.recordCount = 0;
|
|
34
|
+
this.incomingRecords = 0;
|
|
35
|
+
this.incomingBytes = 0;
|
|
36
|
+
|
|
37
|
+
// Tags
|
|
38
|
+
this.tags = config.tags || {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Inicializa shards
|
|
43
|
+
*/
|
|
44
|
+
initializeShards() {
|
|
45
|
+
this.shards = [];
|
|
46
|
+
for (let i = 0; i < this.shardCount; i++) {
|
|
47
|
+
this.shards.push(new Shard(i, this.streamName));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adiciona um shard
|
|
53
|
+
*/
|
|
54
|
+
addShard() {
|
|
55
|
+
const newShard = new Shard(this.shards.length, this.streamName);
|
|
56
|
+
this.shards.push(newShard);
|
|
57
|
+
this.shardCount++;
|
|
58
|
+
this.lastUpdateTimestamp = new Date().toISOString();
|
|
59
|
+
return newShard;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove um shard (merge com outro)
|
|
64
|
+
*/
|
|
65
|
+
removeShard(shardId) {
|
|
66
|
+
const index = this.shards.findIndex(s => s.shardId === shardId);
|
|
67
|
+
if (index !== -1) {
|
|
68
|
+
this.shards.splice(index, 1);
|
|
69
|
+
this.shardCount--;
|
|
70
|
+
this.lastUpdateTimestamp = new Date().toISOString();
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Obtém shard por ID
|
|
78
|
+
*/
|
|
79
|
+
getShard(shardId) {
|
|
80
|
+
return this.shards.find(s => s.shardId === shardId);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Obtém todos os shards
|
|
85
|
+
*/
|
|
86
|
+
getAllShards() {
|
|
87
|
+
return this.shards;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Seleciona shard baseado em partition key
|
|
92
|
+
*/
|
|
93
|
+
selectShardByPartitionKey(partitionKey) {
|
|
94
|
+
if (this.shards.length === 0) return null;
|
|
95
|
+
|
|
96
|
+
// Hash simples para distribuição uniforme
|
|
97
|
+
let hash = 0;
|
|
98
|
+
for (let i = 0; i < partitionKey.length; i++) {
|
|
99
|
+
const char = partitionKey.charCodeAt(i);
|
|
100
|
+
hash = ((hash << 5) - hash) + char;
|
|
101
|
+
hash = hash & hash; // Converte para 32-bit
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const shardIndex = Math.abs(hash) % this.shards.length;
|
|
105
|
+
return this.shards[shardIndex];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Registra um consumidor
|
|
110
|
+
*/
|
|
111
|
+
registerConsumer(consumerArn, consumerName) {
|
|
112
|
+
if (!this.consumers.has(consumerArn)) {
|
|
113
|
+
this.consumers.set(consumerArn, {
|
|
114
|
+
consumerArn,
|
|
115
|
+
consumerName,
|
|
116
|
+
consumerStatus: 'ACTIVE',
|
|
117
|
+
consumerCreationTimestamp: new Date().toISOString(),
|
|
118
|
+
shardConsumers: new Map() // Map de shardId -> lastSequenceNumber
|
|
119
|
+
});
|
|
120
|
+
this.consumerCount++;
|
|
121
|
+
}
|
|
122
|
+
return this.consumers.get(consumerArn);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Remove consumidor
|
|
127
|
+
*/
|
|
128
|
+
removeConsumer(consumerArn) {
|
|
129
|
+
if (this.consumers.has(consumerArn)) {
|
|
130
|
+
this.consumers.delete(consumerArn);
|
|
131
|
+
this.consumerCount--;
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Obtém consumidor
|
|
139
|
+
*/
|
|
140
|
+
getConsumer(consumerArn) {
|
|
141
|
+
return this.consumers.get(consumerArn);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Atualiza estatísticas de incoming records
|
|
146
|
+
*/
|
|
147
|
+
recordPutRecord(recordData) {
|
|
148
|
+
this.recordCount++;
|
|
149
|
+
this.incomingRecords++;
|
|
150
|
+
this.incomingBytes += Buffer.byteLength(JSON.stringify(recordData));
|
|
151
|
+
this.lastUpdateTimestamp = new Date().toISOString();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Retorna descrição do stream
|
|
156
|
+
*/
|
|
157
|
+
describe() {
|
|
158
|
+
return {
|
|
159
|
+
StreamDescription: {
|
|
160
|
+
StreamName: this.streamName,
|
|
161
|
+
StreamArn: this.streamArn,
|
|
162
|
+
StreamStatus: this.streamStatus,
|
|
163
|
+
StreamModeDetails: this.streamModeDetails,
|
|
164
|
+
StreamCreationTimestamp: this.creationTimestamp,
|
|
165
|
+
Shards: this.shards.map(s => s.toJSON()),
|
|
166
|
+
HasMoreShards: false,
|
|
167
|
+
RetentionPeriod: this.retentionPeriod,
|
|
168
|
+
StreamLastUpdateTimestamp: this.lastUpdateTimestamp,
|
|
169
|
+
EnhancedMonitoring: this.enhancedMonitoring,
|
|
170
|
+
EncryptionType: 'NONE',
|
|
171
|
+
KeyId: null
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
toJSON() {
|
|
177
|
+
return {
|
|
178
|
+
streamName: this.streamName,
|
|
179
|
+
streamArn: this.streamArn,
|
|
180
|
+
streamStatus: this.streamStatus,
|
|
181
|
+
streamModeDetails: this.streamModeDetails,
|
|
182
|
+
creationTimestamp: this.creationTimestamp,
|
|
183
|
+
lastUpdateTimestamp: this.lastUpdateTimestamp,
|
|
184
|
+
shardCount: this.shardCount,
|
|
185
|
+
consumerCount: this.consumerCount,
|
|
186
|
+
retentionPeriod: this.retentionPeriod,
|
|
187
|
+
recordCount: this.recordCount,
|
|
188
|
+
incomingRecords: this.incomingRecords,
|
|
189
|
+
incomingBytes: this.incomingBytes
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Shard Model - Partição dentro de um stream
|
|
196
|
+
*/
|
|
197
|
+
class Shard {
|
|
198
|
+
constructor(shardIndex, streamName) {
|
|
199
|
+
this.shardId = `shardId-${String(shardIndex).padStart(12, '0')}`;
|
|
200
|
+
this.shardName = `${streamName}-shard-${shardIndex}`;
|
|
201
|
+
this.shardStatus = 'ACTIVE';
|
|
202
|
+
this.parentShardId = null;
|
|
203
|
+
this.adjacentParentShardId = null;
|
|
204
|
+
|
|
205
|
+
// Range de partition keys que este shard cobre
|
|
206
|
+
this.hashKeyRange = {
|
|
207
|
+
startingHashKey: '0',
|
|
208
|
+
endingHashKey: '18446744073709551615' // uint64 max
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Seqüência de números
|
|
212
|
+
this.sequenceNumberRange = {
|
|
213
|
+
startingSequenceNumber: '0',
|
|
214
|
+
endingSequenceNumber: null
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
this.creationTimestamp = new Date().toISOString();
|
|
218
|
+
|
|
219
|
+
// Records armazenados neste shard
|
|
220
|
+
this.records = [];
|
|
221
|
+
this.recordCount = 0;
|
|
222
|
+
this.lastSequenceNumber = 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Adiciona um record ao shard
|
|
227
|
+
*/
|
|
228
|
+
addRecord(record) {
|
|
229
|
+
record.sequenceNumber = (++this.lastSequenceNumber).toString();
|
|
230
|
+
this.records.push(record);
|
|
231
|
+
this.recordCount++;
|
|
232
|
+
return record;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Obtém records a partir de um sequence number
|
|
237
|
+
*/
|
|
238
|
+
getRecords(startingSequenceNumber = null, limit = 100) {
|
|
239
|
+
let startIndex = 0;
|
|
240
|
+
|
|
241
|
+
if (startingSequenceNumber) {
|
|
242
|
+
const seqNum = parseInt(startingSequenceNumber);
|
|
243
|
+
startIndex = this.records.findIndex(r => parseInt(r.sequenceNumber) > seqNum);
|
|
244
|
+
if (startIndex === -1) {
|
|
245
|
+
startIndex = this.records.length;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return this.records.slice(startIndex, startIndex + limit);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Obtém último record
|
|
254
|
+
*/
|
|
255
|
+
getLastSequenceNumber() {
|
|
256
|
+
return this.lastSequenceNumber.toString();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Retorna todos os records
|
|
261
|
+
*/
|
|
262
|
+
getAllRecords() {
|
|
263
|
+
return this.records;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
toJSON() {
|
|
267
|
+
return {
|
|
268
|
+
ShardId: this.shardId,
|
|
269
|
+
ShardStatus: this.shardStatus,
|
|
270
|
+
HashKeyRange: this.hashKeyRange,
|
|
271
|
+
SequenceNumberRange: this.sequenceNumberRange,
|
|
272
|
+
CreationTimestamp: this.creationTimestamp
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = {
|
|
278
|
+
Stream,
|
|
279
|
+
Shard
|
|
280
|
+
};
|