@gugananuvem/aws-local-simulator 1.0.0 → 1.0.1

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 (38) hide show
  1. package/README.md +192 -192
  2. package/bin/aws-local-simulator.js +62 -62
  3. package/package.json +8 -5
  4. package/src/config/config-loader.js +113 -0
  5. package/src/config/default-config.js +65 -0
  6. package/src/config/env-loader.js +67 -0
  7. package/src/index.js +130 -130
  8. package/src/index.mjs +124 -0
  9. package/src/server.js +219 -0
  10. package/src/services/apigateway/index.js +67 -0
  11. package/src/services/apigateway/server.js +435 -0
  12. package/src/services/apigateway/simulator.js +1252 -0
  13. package/src/services/cognito/index.js +66 -0
  14. package/src/services/cognito/server.js +229 -0
  15. package/src/services/cognito/simulator.js +848 -0
  16. package/src/services/dynamodb/index.js +71 -0
  17. package/src/services/dynamodb/server.js +122 -0
  18. package/src/services/dynamodb/simulator.js +614 -0
  19. package/src/services/eventbridge/index.js +85 -0
  20. package/src/services/index.js +19 -0
  21. package/src/services/lambda/handler-loader.js +173 -0
  22. package/src/services/lambda/index.js +73 -0
  23. package/src/services/lambda/route-registry.js +275 -0
  24. package/src/services/lambda/server.js +153 -0
  25. package/src/services/lambda/simulator.js +278 -0
  26. package/src/services/s3/index.js +70 -0
  27. package/src/services/s3/server.js +239 -0
  28. package/src/services/s3/simulator.js +740 -0
  29. package/src/services/sns/index.js +76 -0
  30. package/src/services/sqs/index.js +96 -0
  31. package/src/services/sqs/server.js +274 -0
  32. package/src/services/sqs/simulator.js +660 -0
  33. package/src/template/aws-config-template.js +88 -0
  34. package/src/template/aws-config-template.mjs +91 -0
  35. package/src/template/config-template.json +165 -0
  36. package/src/utils/aws-config.js +92 -0
  37. package/src/utils/local-store.js +68 -0
  38. package/src/utils/logger.js +60 -0
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Handler Loader - Carrega handlers de Lambda em diferentes formatos
3
+ * Suporta: CommonJS, ES Modules, TypeScript (compilado)
4
+ */
5
+
6
+ const path = require('path');
7
+ const fs = require('fs');
8
+ const { pathToFileURL } = require('url');
9
+ const logger = require('../../utils/logger');
10
+
11
+ class HandlerLoader {
12
+ /**
13
+ * Carrega um handler de Lambda
14
+ * @param {string} handlerPath - Caminho para o arquivo do handler
15
+ * @param {string} type - Tipo do módulo: 'commonjs', 'module', 'auto'
16
+ * @returns {Promise<Function>} - Função handler
17
+ */
18
+ static async load(handlerPath, type = 'auto') {
19
+ const fullPath = path.resolve(process.cwd(), handlerPath);
20
+
21
+ if (!fs.existsSync(fullPath)) {
22
+ throw new Error(`Handler não encontrado: ${fullPath}`);
23
+ }
24
+
25
+ logger.verboso(`Carregando handler: ${fullPath} (type: ${type})`);
26
+
27
+ try {
28
+ let handler;
29
+ let resolvedType = type;
30
+
31
+ // Detecta o tipo se for auto
32
+ if (type === 'auto') {
33
+ resolvedType = this.detectType(fullPath);
34
+ }
35
+
36
+ // Carrega baseado no tipo
37
+ if (resolvedType === 'module' || fullPath.endsWith('.mjs')) {
38
+ // ES Module
39
+ const fileUrl = pathToFileURL(fullPath).href;
40
+ const module = await import(fileUrl);
41
+ handler = this.extractHandler(module);
42
+ } else {
43
+ // CommonJS
44
+ // Limpa o cache para permitir hot reload em desenvolvimento
45
+ if (process.env.NODE_ENV === 'development') {
46
+ delete require.cache[require.resolve(fullPath)];
47
+ }
48
+ const module = require(fullPath);
49
+ handler = this.extractHandler(module);
50
+ }
51
+
52
+ if (typeof handler !== 'function') {
53
+ throw new Error(`Handler não é uma função em ${fullPath}. Exporte uma função ou um objeto com handler.`);
54
+ }
55
+
56
+ logger.debug(`✅ Handler carregado: ${fullPath}`);
57
+
58
+ return handler;
59
+
60
+ } catch (error) {
61
+ logger.error(`❌ Erro ao carregar handler ${fullPath}:`, error);
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Extrai a função handler de um módulo
68
+ * Suporta: module.exports = handler, exports.handler, export default
69
+ */
70
+ static extractHandler(module) {
71
+ // Verifica se é export default
72
+ if (module.default && typeof module.default === 'function') {
73
+ return module.default;
74
+ }
75
+
76
+ // Verifica se é exports.handler
77
+ if (module.handler && typeof module.handler === 'function') {
78
+ return module.handler;
79
+ }
80
+
81
+ // Verifica se o módulo inteiro é uma função
82
+ if (typeof module === 'function') {
83
+ return module;
84
+ }
85
+
86
+ // Tenta encontrar a primeira função exportada
87
+ const exportedFunctions = Object.values(module).filter(v => typeof v === 'function');
88
+ if (exportedFunctions.length === 1) {
89
+ logger.warn(`⚠️ Usando a primeira função exportada como handler: ${exportedFunctions[0].name}`);
90
+ return exportedFunctions[0];
91
+ }
92
+
93
+ // Se tem múltiplas funções, tenta encontrar uma chamada 'handler'
94
+ if (module.handler) {
95
+ return module.handler;
96
+ }
97
+
98
+ throw new Error('Não foi possível encontrar uma função handler no módulo');
99
+ }
100
+
101
+ /**
102
+ * Detecta o tipo de módulo (CommonJS ou ES Module)
103
+ */
104
+ static detectType(filePath) {
105
+ // Verifica extensão
106
+ if (filePath.endsWith('.mjs')) {
107
+ return 'module';
108
+ }
109
+ if (filePath.endsWith('.cjs')) {
110
+ return 'commonjs';
111
+ }
112
+
113
+ // Verifica package.json na mesma pasta ou superior
114
+ try {
115
+ let currentDir = path.dirname(filePath);
116
+ while (currentDir !== path.parse(currentDir).root) {
117
+ const packageJsonPath = path.join(currentDir, 'package.json');
118
+ if (fs.existsSync(packageJsonPath)) {
119
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
120
+ if (packageJson.type === 'module') {
121
+ return 'module';
122
+ }
123
+ break;
124
+ }
125
+ currentDir = path.dirname(currentDir);
126
+ }
127
+ } catch (error) {
128
+ // Ignora erro
129
+ }
130
+
131
+ return 'commonjs';
132
+ }
133
+
134
+ /**
135
+ * Recarrega um handler (útil para hot reload)
136
+ */
137
+ static async reload(handlerPath, type = 'auto') {
138
+ // Limpa cache
139
+ const fullPath = path.resolve(process.cwd(), handlerPath);
140
+ delete require.cache[require.resolve(fullPath)];
141
+
142
+ // Recarrega
143
+ return this.load(handlerPath, type);
144
+ }
145
+
146
+ /**
147
+ * Valida se um caminho de handler é válido
148
+ */
149
+ static isValid(handlerPath) {
150
+ const fullPath = path.resolve(process.cwd(), handlerPath);
151
+ return fs.existsSync(fullPath);
152
+ }
153
+
154
+ /**
155
+ * Retorna informações sobre o handler
156
+ */
157
+ static async getInfo(handlerPath) {
158
+ const fullPath = path.resolve(process.cwd(), handlerPath);
159
+ const type = this.detectType(fullPath);
160
+ const stats = fs.statSync(fullPath);
161
+
162
+ return {
163
+ path: fullPath,
164
+ type,
165
+ exists: fs.existsSync(fullPath),
166
+ size: stats.size,
167
+ modified: stats.mtime,
168
+ isValid: this.isValid(handlerPath)
169
+ };
170
+ }
171
+ }
172
+
173
+ module.exports = HandlerLoader;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Lambda Service - Ponto de entrada
3
+ * Exporta o serviço principal e seus componentes
4
+ */
5
+
6
+ const LambdaServer = require('./server');
7
+ const LambdaSimulator = require('./simulator');
8
+
9
+ class LambdaService {
10
+ constructor(config) {
11
+ this.config = config;
12
+ this.name = 'lambda';
13
+ this.port = config.ports.lambda;
14
+ this.server = null;
15
+ this.simulator = null;
16
+ this.isRunning = false;
17
+ }
18
+
19
+ async initialize() {
20
+ const logger = require('../../utils/logger');
21
+ logger.debug(`Inicializando Lambda Service na porta ${this.port}...`);
22
+
23
+ // Cria o simulador
24
+ this.simulator = new LambdaSimulator(this.config);
25
+
26
+ // Cria o servidor HTTP
27
+ this.server = new LambdaServer(this.port, this.config);
28
+ this.server.simulator = this.simulator;
29
+
30
+ await this.server.initialize();
31
+
32
+ logger.debug('Lambda Service inicializado');
33
+ }
34
+
35
+ async start() {
36
+ if (this.isRunning) return;
37
+ await this.server.start();
38
+ this.isRunning = true;
39
+ }
40
+
41
+ async stop() {
42
+ if (!this.isRunning) return;
43
+ await this.server.stop();
44
+ this.isRunning = false;
45
+ }
46
+
47
+ async reset() {
48
+ await this.simulator.reset();
49
+ }
50
+
51
+ getStatus() {
52
+ return {
53
+ running: this.isRunning,
54
+ port: this.port,
55
+ endpoint: `http://localhost:${this.port}`,
56
+ lambdasCount: this.simulator?.getLambdasCount() || 0
57
+ };
58
+ }
59
+
60
+ getHandler(path) {
61
+ return this.simulator?.getHandler(path);
62
+ }
63
+
64
+ getSimulator() {
65
+ return this.simulator;
66
+ }
67
+
68
+ getServer() {
69
+ return this.server;
70
+ }
71
+ }
72
+
73
+ module.exports = LambdaService;
@@ -0,0 +1,275 @@
1
+ /**
2
+ * Route Registry - Gerencia o registro e matching de rotas para Lambdas
3
+ * Suporta: Path parameters, Wildcards, Middlewares
4
+ */
5
+
6
+ class RouteRegistry {
7
+ constructor() {
8
+ this.routes = new Map();
9
+ this.middlewares = new Map();
10
+ this.globalMiddlewares = [];
11
+ }
12
+
13
+ /**
14
+ * Registra uma rota
15
+ * @param {string} path - Caminho da rota (ex: /users/:id, /api/*)
16
+ * @param {Function} handler - Função handler da Lambda
17
+ * @param {Object} env - Variáveis de ambiente específicas
18
+ * @param {Array} middlewares - Middlewares específicos da rota
19
+ */
20
+ register(path, handler, env = {}, middlewares = []) {
21
+ // Normaliza o path
22
+ const normalizedPath = this.normalizePath(path);
23
+
24
+ // Parse do path para extrair parâmetros
25
+ const { pattern, params } = this.parsePath(normalizedPath);
26
+
27
+ this.routes.set(normalizedPath, {
28
+ path: normalizedPath,
29
+ pattern,
30
+ params,
31
+ handler,
32
+ env,
33
+ middlewares,
34
+ isWildcard: normalizedPath.includes('*'),
35
+ hasParams: params.length > 0,
36
+ timestamp: Date.now()
37
+ });
38
+
39
+ // Adiciona versão com barra se não existir
40
+ if (!normalizedPath.endsWith('/') && normalizedPath !== '*') {
41
+ const slashPath = `${normalizedPath}/`;
42
+ if (!this.routes.has(slashPath)) {
43
+ this.routes.set(slashPath, {
44
+ path: slashPath,
45
+ pattern: this.parsePath(slashPath).pattern,
46
+ params: this.parsePath(slashPath).params,
47
+ handler,
48
+ env,
49
+ middlewares,
50
+ isWildcard: false,
51
+ hasParams: this.parsePath(slashPath).params.length > 0,
52
+ timestamp: Date.now()
53
+ });
54
+ }
55
+ }
56
+
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Registra um middleware global
62
+ */
63
+ use(middleware) {
64
+ this.globalMiddlewares.push(middleware);
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Registra um middleware para uma rota específica
70
+ */
71
+ useFor(path, middleware) {
72
+ const normalizedPath = this.normalizePath(path);
73
+ if (!this.middlewares.has(normalizedPath)) {
74
+ this.middlewares.set(normalizedPath, []);
75
+ }
76
+ this.middlewares.get(normalizedPath).push(middleware);
77
+ return this;
78
+ }
79
+
80
+ /**
81
+ * Encontra uma rota que corresponde ao path
82
+ * @param {string} path - Path da requisição
83
+ * @returns {Object|null} - Rota encontrada com parâmetros extraídos
84
+ */
85
+ find(path) {
86
+ const normalizedPath = this.normalizePath(path);
87
+
88
+ // Busca exata primeiro
89
+ if (this.routes.has(normalizedPath)) {
90
+ const route = this.routes.get(normalizedPath);
91
+ return {
92
+ ...route,
93
+ params: {}
94
+ };
95
+ }
96
+
97
+ // Busca por prefixo (para rotas sem parâmetros)
98
+ for (const [routePath, route] of this.routes.entries()) {
99
+ if (!route.hasParams && !route.isWildcard && normalizedPath.startsWith(routePath)) {
100
+ return {
101
+ ...route,
102
+ params: {}
103
+ };
104
+ }
105
+ }
106
+
107
+ // Busca por padrão com parâmetros
108
+ for (const [routePath, route] of this.routes.entries()) {
109
+ if (route.hasParams && route.pattern) {
110
+ const match = route.pattern.exec(normalizedPath);
111
+ if (match) {
112
+ const params = {};
113
+ route.params.forEach((paramName, index) => {
114
+ params[paramName] = match[index + 1];
115
+ });
116
+ return {
117
+ ...route,
118
+ params
119
+ };
120
+ }
121
+ }
122
+ }
123
+
124
+ // Busca wildcard
125
+ for (const [routePath, route] of this.routes.entries()) {
126
+ if (route.isWildcard) {
127
+ const wildcardPath = routePath.replace('*', '');
128
+ if (normalizedPath.startsWith(wildcardPath)) {
129
+ return {
130
+ ...route,
131
+ params: {
132
+ wildcard: normalizedPath.substring(wildcardPath.length)
133
+ }
134
+ };
135
+ }
136
+ }
137
+ }
138
+
139
+ return null;
140
+ }
141
+
142
+ /**
143
+ * Obtém todos os middlewares para uma rota
144
+ */
145
+ getMiddlewares(route) {
146
+ const routeMiddlewares = this.middlewares.get(route.path) || [];
147
+ return [...this.globalMiddlewares, ...routeMiddlewares, ...route.middlewares];
148
+ }
149
+
150
+ /**
151
+ * Normaliza o path
152
+ */
153
+ normalizePath(path) {
154
+ // Remove query string
155
+ const pathWithoutQuery = path.split('?')[0];
156
+
157
+ // Remove trailing slash
158
+ let normalized = pathWithoutQuery.replace(/\/+$/, '');
159
+
160
+ // Adiciona slash inicial se necessário
161
+ if (!normalized.startsWith('/')) {
162
+ normalized = `/${normalized}`;
163
+ }
164
+
165
+ // Se for vazio, usa root
166
+ if (normalized === '') {
167
+ normalized = '/';
168
+ }
169
+
170
+ return normalized;
171
+ }
172
+
173
+ /**
174
+ * Parse do path para extrair parâmetros e criar regex pattern
175
+ * @param {string} path - Path da rota (ex: /users/:id/posts/:postId)
176
+ * @returns {Object} - { pattern, params }
177
+ */
178
+ parsePath(path) {
179
+ const params = [];
180
+
181
+ // Substitui :param por regex capture group
182
+ const patternString = path.replace(/:[^\s/]+/g, (match) => {
183
+ const paramName = match.substring(1);
184
+ params.push(paramName);
185
+ return '([^/]+)';
186
+ });
187
+
188
+ // Substitui * por regex wildcard
189
+ const finalPatternString = patternString.replace(/\*/g, '(.*)');
190
+
191
+ // Cria regex
192
+ const pattern = new RegExp(`^${finalPatternString}$`);
193
+
194
+ return { pattern, params };
195
+ }
196
+
197
+ /**
198
+ * Remove uma rota
199
+ */
200
+ unregister(path) {
201
+ const normalizedPath = this.normalizePath(path);
202
+ this.routes.delete(normalizedPath);
203
+ return this;
204
+ }
205
+
206
+ /**
207
+ * Lista todas as rotas
208
+ */
209
+ list() {
210
+ const routes = [];
211
+ for (const [path, route] of this.routes.entries()) {
212
+ routes.push({
213
+ path,
214
+ handler: route.handler.name || 'anonymous',
215
+ hasParams: route.hasParams,
216
+ isWildcard: route.isWildcard,
217
+ params: route.params,
218
+ env: route.env
219
+ });
220
+ }
221
+ return routes;
222
+ }
223
+
224
+ /**
225
+ * Limpa todas as rotas
226
+ */
227
+ clear() {
228
+ this.routes.clear();
229
+ this.middlewares.clear();
230
+ this.globalMiddlewares = [];
231
+ return this;
232
+ }
233
+
234
+ /**
235
+ * Verifica se uma rota existe
236
+ */
237
+ has(path) {
238
+ const normalizedPath = this.normalizePath(path);
239
+ return this.routes.has(normalizedPath) || this.find(path) !== null;
240
+ }
241
+
242
+ /**
243
+ * Obtém todas as rotas em formato de objeto
244
+ */
245
+ getAll() {
246
+ const result = {};
247
+ for (const [path, route] of this.routes.entries()) {
248
+ result[path] = {
249
+ handler: route.handler,
250
+ env: route.env,
251
+ hasParams: route.hasParams,
252
+ params: route.params
253
+ };
254
+ }
255
+ return result;
256
+ }
257
+
258
+ /**
259
+ * Obtém estatísticas das rotas
260
+ */
261
+ getStats() {
262
+ const routes = Array.from(this.routes.values());
263
+ return {
264
+ total: routes.length,
265
+ withParams: routes.filter(r => r.hasParams).length,
266
+ wildcards: routes.filter(r => r.isWildcard).length,
267
+ routes: routes.map(r => ({
268
+ path: r.path,
269
+ handler: r.handler.name || 'anonymous'
270
+ }))
271
+ };
272
+ }
273
+ }
274
+
275
+ module.exports = RouteRegistry;
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Lambda Server - Servidor HTTP para Lambda (API Gateway style)
3
+ */
4
+
5
+ const express = require('express');
6
+ const cors = require('cors');
7
+ const LambdaSimulator = require('./simulator');
8
+ const logger = require('../../utils/logger');
9
+
10
+ class LambdaServer {
11
+ constructor(port, config) {
12
+ this.port = port;
13
+ this.config = config;
14
+ this.app = express();
15
+ this.simulator = null;
16
+ this.server = null;
17
+ this.setupMiddlewares();
18
+ }
19
+
20
+ setupMiddlewares() {
21
+ this.app.use(express.json({ limit: '10mb' }));
22
+ this.app.use(express.urlencoded({ extended: true }));
23
+ this.app.use(cors());
24
+
25
+ // Logging de requisições
26
+ if (logger.currentLogLevel === 'verboso') {
27
+ this.app.use((req, res, next) => {
28
+ const start = Date.now();
29
+ res.on('finish', () => {
30
+ const duration = Date.now() - start;
31
+ logger.verboso(`Lambda: ${req.method} ${req.path} - ${duration}ms`);
32
+ });
33
+ next();
34
+ });
35
+ }
36
+ }
37
+
38
+ async initialize() {
39
+ this.simulator = new LambdaSimulator(this.config);
40
+ await this.simulator.initialize();
41
+ this.setupRoutes();
42
+ }
43
+
44
+ setupRoutes() {
45
+ // Health check
46
+ this.app.get('/health', (req, res) => {
47
+ res.json({
48
+ status: 'healthy',
49
+ version: require('../../../package.json').version,
50
+ lambdas: this.simulator.getLambdasCount(),
51
+ routes: this.simulator.listRoutes()
52
+ });
53
+ });
54
+
55
+ // Rota catch-all para todas as Lambdas
56
+ this.app.all('*', async (req, res) => {
57
+ const result = await this.simulator.handleRequest(req, res);
58
+
59
+ if (result && result.error) {
60
+ res.status(result.status).json(result.error);
61
+ }
62
+ });
63
+
64
+ // Admin endpoints
65
+ this.setupAdminRoutes();
66
+ }
67
+
68
+ setupAdminRoutes() {
69
+ // Listar todas as Lambdas
70
+ this.app.get('/__admin/lambdas', (req, res) => {
71
+ res.json(this.simulator.listLambdas());
72
+ });
73
+
74
+ // Detalhes de uma Lambda
75
+ this.app.get('/__admin/lambdas/:path', (req, res) => {
76
+ const lambda = this.simulator.getLambda(req.params.path);
77
+ if (lambda) {
78
+ res.json(lambda);
79
+ } else {
80
+ res.status(404).json({ error: 'Lambda not found' });
81
+ }
82
+ });
83
+
84
+ // Recarregar Lambdas
85
+ this.app.post('/__admin/reload', async (req, res) => {
86
+ await this.simulator.reloadLambdas();
87
+ res.json({
88
+ message: 'Lambdas recarregadas',
89
+ count: this.simulator.getLambdasCount()
90
+ });
91
+ });
92
+
93
+ // Injetar variável de ambiente
94
+ this.app.post('/__admin/env', (req, res) => {
95
+ const { key, value } = req.body;
96
+ if (key && value !== undefined) {
97
+ this.simulator.setEnvironmentVariable(key, value);
98
+ res.json({ message: `Environment variable ${key} set` });
99
+ } else {
100
+ res.status(400).json({ error: 'Missing key or value' });
101
+ }
102
+ });
103
+
104
+ // Listar variáveis de ambiente
105
+ this.app.get('/__admin/env', (req, res) => {
106
+ res.json(this.simulator.getEnvironmentVariables());
107
+ });
108
+
109
+ // Estatísticas
110
+ this.app.get('/__admin/stats', (req, res) => {
111
+ res.json(this.simulator.getStats());
112
+ });
113
+ }
114
+
115
+ start() {
116
+ return new Promise((resolve) => {
117
+ this.server = this.app.listen(this.port, () => {
118
+ logger.info(`🚀 Lambda API rodando em http://localhost:${this.port}`);
119
+ this.printRoutes();
120
+ resolve();
121
+ });
122
+ });
123
+ }
124
+
125
+ printRoutes() {
126
+ logger.info('\n📚 Lambdas registradas:');
127
+ const lambdas = this.simulator.listLambdas();
128
+ for (const lambda of lambdas) {
129
+ logger.info(` ${lambda.path.padEnd(30)} -> ${lambda.handlerName || 'anonymous'}`);
130
+ }
131
+ }
132
+
133
+ stop() {
134
+ return new Promise((resolve) => {
135
+ if (this.server) {
136
+ this.server.close(() => resolve());
137
+ } else {
138
+ resolve();
139
+ }
140
+ });
141
+ }
142
+
143
+ getStatus() {
144
+ return {
145
+ running: !!this.server,
146
+ port: this.port,
147
+ endpoint: `http://localhost:${this.port}`,
148
+ lambdasCount: this.simulator?.getLambdasCount() || 0
149
+ };
150
+ }
151
+ }
152
+
153
+ module.exports = LambdaServer;