@gugananuvem/aws-local-simulator 1.0.28 → 1.0.30

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gugananuvem/aws-local-simulator",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "Simulador local completo para serviços AWS",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -66,6 +66,6 @@
66
66
  "publishConfig": {
67
67
  "directory": "dist"
68
68
  },
69
- "buildDate": "2026-04-30T14:28:21.971Z",
69
+ "buildDate": "2026-05-01T13:34:46.765Z",
70
70
  "published": true
71
71
  }
@@ -164,6 +164,8 @@ class CognitoServer {
164
164
  return this.simulator.adminListGroupsForUser(params);
165
165
  case 'AdminUserGlobalSignOut':
166
166
  return this.simulator.adminUserGlobalSignOut(params);
167
+ case 'AdminUpdateUserAttributes':
168
+ return this.simulator.adminUpdateUserAttributes(params);
167
169
 
168
170
  // Identity Pool Operations
169
171
  case 'CreateIdentityPool':
@@ -77,8 +77,14 @@ class CognitoSimulator {
77
77
  return null;
78
78
  }
79
79
 
80
- const result = await this.lambdaSimulator.invoke(fnName, event, "RequestResponse");
81
- return result.Payload;
80
+ try {
81
+ const result = await this.lambdaSimulator.invoke(fnName, event, "RequestResponse");
82
+ return result.Payload;
83
+ } catch (error) {
84
+ const wrappedError = new Error(error.message);
85
+ wrappedError.code = "UserLambdaValidationException";
86
+ throw wrappedError;
87
+ }
82
88
  }
83
89
 
84
90
  async initialize() {
@@ -1278,6 +1284,28 @@ class CognitoSimulator {
1278
1284
  return password.sort(() => Math.random() - 0.5).join("");
1279
1285
  }
1280
1286
 
1287
+ adminUpdateUserAttributes(params) {
1288
+ const { UserPoolId, Username, UserAttributes } = params;
1289
+ const userPool = this.userPools.get(UserPoolId);
1290
+
1291
+ if (!userPool) {
1292
+ throw new Error(`User pool ${UserPoolId} not found`);
1293
+ }
1294
+
1295
+ const user = this.findUserByUsername(Username, null, UserPoolId);
1296
+ if (!user) {
1297
+ throw new Error(`User not found: ${Username}`);
1298
+ }
1299
+
1300
+ const updates = this.normalizeUserAttributes(UserAttributes || []);
1301
+ Object.assign(user.Attributes, updates);
1302
+ user.LastModifiedDate = Math.floor(Date.now() / 1000);
1303
+ this.persistUsers();
1304
+
1305
+ logger.debug(`✅ AdminUpdateUserAttributes: ${Username} in ${UserPoolId}`);
1306
+ return {};
1307
+ }
1308
+
1281
1309
  adminSetUserPassword(params) {
1282
1310
  const { UserPoolId, Username, Password, Permanent } = params;
1283
1311
  const user = this.findUserByUsername(Username, null, UserPoolId);
@@ -36,6 +36,9 @@ class LambdaService {
36
36
  injectDependencies(server) {
37
37
  const ct = server.getService('cloudtrail');
38
38
  if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
39
+
40
+ const cw = server.getService('cloudwatch');
41
+ if (cw?.simulator) this.simulator.cloudwatchSimulator = cw.simulator;
39
42
  }
40
43
 
41
44
  async start() {
@@ -18,7 +18,6 @@ class LambdaServer {
18
18
  }
19
19
 
20
20
  setupMiddlewares() {
21
- this.app.use(express.json({ limit: '10mb' }));
22
21
  this.app.use(express.urlencoded({ extended: true }));
23
22
  this.app.use(cors());
24
23
 
@@ -51,7 +50,22 @@ class LambdaServer {
51
50
  this.app.post('/2015-03-31/functions/:functionName/invocations', async (req, res) => {
52
51
  const { functionName } = req.params;
53
52
  const invocationType = req.headers['x-amz-invocation-type'] || 'RequestResponse';
54
- const event = req.body || {};
53
+
54
+ // Read body directly from stream, bypassing all body parsers
55
+ let event = {};
56
+ try {
57
+ const rawBody = await new Promise((resolve, reject) => {
58
+ const chunks = [];
59
+ req.on('data', chunk => chunks.push(chunk));
60
+ req.on('end', () => resolve(Buffer.concat(chunks)));
61
+ req.on('error', reject);
62
+ });
63
+ if (rawBody.length > 0) {
64
+ event = JSON.parse(rawBody.toString('utf8'));
65
+ }
66
+ } catch {
67
+ event = {};
68
+ }
55
69
 
56
70
  logger.debug(`Lambda invoke: ${functionName} (${invocationType})`);
57
71
 
@@ -77,11 +91,19 @@ class LambdaServer {
77
91
  }
78
92
 
79
93
  setupAdminRoutes() {
94
+ // Helper: parse Buffer body as JSON for admin routes
95
+ const parseJson = (req, res, next) => {
96
+ if (Buffer.isBuffer(req.body) && req.body.length > 0) {
97
+ try { req.body = JSON.parse(req.body.toString('utf8')); } catch { req.body = {}; }
98
+ }
99
+ next();
100
+ };
101
+
80
102
  this.app.get('/__admin/functions', (req, res) => {
81
103
  res.json(this.simulator.listLambdas());
82
104
  });
83
105
 
84
- this.app.post('/__admin/functions', async (req, res) => {
106
+ this.app.post('/__admin/functions', parseJson, async (req, res) => {
85
107
  try {
86
108
  const lambda = await this.simulator.createFunction(req.body);
87
109
  res.status(201).json(lambda);
@@ -90,7 +112,7 @@ class LambdaServer {
90
112
  }
91
113
  });
92
114
 
93
- this.app.put('/__admin/functions/:name', async (req, res) => {
115
+ this.app.put('/__admin/functions/:name', parseJson, async (req, res) => {
94
116
  try {
95
117
  const lambda = await this.simulator.updateFunction(req.params.name, req.body);
96
118
  res.json(lambda);
@@ -110,7 +132,7 @@ class LambdaServer {
110
132
  res.json({ message: 'Lambdas recarregadas', count: this.simulator.getLambdasCount() });
111
133
  });
112
134
 
113
- this.app.post('/__admin/env', (req, res) => {
135
+ this.app.post('/__admin/env', parseJson, (req, res) => {
114
136
  const { key, value } = req.body;
115
137
  if (key && value !== undefined) {
116
138
  this.simulator.setEnvironmentVariable(key, value);
@@ -13,6 +13,7 @@ class LambdaSimulator {
13
13
  this.lambdas = new Map(); // functionName -> { handler, env, config }
14
14
  this.environment = { ...process.env };
15
15
  this.audit = new CloudTrailAudit("lambda.amazonaws.com");
16
+ this.cloudwatchSimulator = null; // injected via injectDependencies
16
17
  }
17
18
 
18
19
  async initialize() {
@@ -108,11 +109,17 @@ class LambdaSimulator {
108
109
  logger.debug(`🎯 Invocando Lambda: ${functionName}`);
109
110
 
110
111
  if (invocationType === "Event") {
111
- this.executeHandler(lambda.handler, event).catch((err) => logger.error(`❌ Async Lambda error (${functionName}):`, err));
112
+ this.executeHandler(lambda, functionName, event).catch((err) => logger.error(`❌ Async Lambda error (${functionName}):`, err));
112
113
  return { StatusCode: 202 };
113
114
  }
114
115
 
115
- const result = await this.executeHandler(lambda.handler, event);
116
+ let result;
117
+ try {
118
+ result = await this.executeHandler(lambda, functionName, event);
119
+ } catch (error) {
120
+ logger.error(`❌ Lambda handler error (${functionName}):`, error);
121
+ throw error;
122
+ }
116
123
  this.audit.record({
117
124
  eventName: "Invoke",
118
125
  readOnly: false,
@@ -122,29 +129,63 @@ class LambdaSimulator {
122
129
  return { StatusCode: result.statusCode || 200, Payload: result };
123
130
  }
124
131
 
125
- async executeHandler(handler, event) {
132
+ async executeHandler(lambda, functionName, event) {
133
+ const requestId = Math.random().toString(36).substring(2, 10) + Math.random().toString(36).substring(2, 10);
134
+ const capturedLogs = [];
135
+
136
+ const context = this.createContext(functionName, requestId);
137
+
138
+ // Intercept console output during handler execution
139
+ const origLog = console.log;
140
+ const origError = console.error;
141
+ const origWarn = console.warn;
142
+ const origInfo = console.info;
143
+
144
+ const capture = (...args) => {
145
+ const line = args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ');
146
+ capturedLogs.push(line);
147
+ };
148
+
149
+ console.log = (...args) => { capture(...args); origLog(...args); };
150
+ console.error = (...args) => { capture(`[ERROR] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origError(...args); };
151
+ console.warn = (...args) => { capture(`[WARN] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origWarn(...args); };
152
+ console.info = (...args) => { capture(`[INFO] ${args.map(a => (typeof a === 'object' ? JSON.stringify(a) : String(a))).join(' ')}`); origInfo(...args); };
153
+
154
+ let result;
155
+ let execError;
126
156
  try {
127
- const context = this.createContext();
128
- const result = await handler(event, context);
129
- return result;
130
- } catch (error) {
131
- logger.error("❌ Erro no handler:", error);
132
- return {
133
- statusCode: 500,
134
- body: JSON.stringify({ error: "Internal Server Error", message: error.message }),
135
- };
157
+ result = await lambda.handler(event, context);
158
+ } catch (err) {
159
+ execError = err;
160
+ capturedLogs.push(`[ERROR] ${err.message}`);
161
+ } finally {
162
+ console.log = origLog;
163
+ console.error = origError;
164
+ console.warn = origWarn;
165
+ console.info = origInfo;
136
166
  }
167
+
168
+ // Send logs to CloudWatch asynchronously (non-blocking)
169
+ if (this.cloudwatchSimulator) {
170
+ this.cloudwatchSimulator
171
+ .putLambdaLogs(functionName, requestId, capturedLogs)
172
+ .catch((err) => logger.debug(`[CloudWatch] Failed to store Lambda logs: ${err.message}`));
173
+ }
174
+
175
+ if (execError) throw execError;
176
+ return result;
137
177
  }
138
178
 
139
- createContext() {
179
+ createContext(functionName = "local-lambda", requestId = null) {
180
+ const reqId = requestId || Math.random().toString(36).substring(2, 18);
140
181
  return {
141
- awsRequestId: Math.random().toString(36).substring(7),
142
- functionName: "local-lambda",
182
+ awsRequestId: reqId,
183
+ functionName,
143
184
  functionVersion: "$LATEST",
144
- invokedFunctionArn: "arn:aws:lambda:local:000000000000:function:local-lambda",
185
+ invokedFunctionArn: `arn:aws:lambda:local:000000000000:function:${functionName}`,
145
186
  memoryLimitInMB: "1024",
146
- logGroupName: "/aws/lambda/local-lambda",
147
- logStreamName: "local-stream",
187
+ logGroupName: `/aws/lambda/${functionName}`,
188
+ logStreamName: `${new Date().toISOString().slice(0, 10).replace(/-/g, '/')}/${reqId.slice(0, 8)}`,
148
189
  getRemainingTimeInMillis: () => 30000,
149
190
  callbackWaitsForEmptyEventLoop: true,
150
191
  identity: null,