@ttoss/lambda-postgres-query 0.3.29 → 0.4.0

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 CHANGED
@@ -28,27 +28,45 @@ export default template;
28
28
 
29
29
  ### Lambda Handler
30
30
 
31
- Create a handler file that exports the Lambda function:
31
+ Create a handler file that exports both Lambda functions:
32
32
 
33
33
  ```typescript
34
- export { handler } from '@ttoss/lambda-postgres-query';
34
+ export { handler, readOnlyHandler } from '@ttoss/lambda-postgres-query';
35
35
  ```
36
36
 
37
37
  ### Environment Variables
38
38
 
39
- Configure the following environment variables:
39
+ Configure the following environment variables for the general-purpose Lambda:
40
40
 
41
41
  ```env
42
42
  DATABASE_NAME=your_database_name
43
43
  DATABASE_USERNAME=your_username
44
44
  DATABASE_PASSWORD=your_password
45
45
  DATABASE_HOST=your_database_host
46
- DATABASE_HOST_READ_ONLY=your_read_only_host # Optional
47
46
  DATABASE_PORT=5432
48
47
  SECURITY_GROUP_IDS=sg-xxxxx,sg-yyyyy
49
48
  SUBNET_IDS=subnet-xxxxx,subnet-yyyyy
50
49
  ```
51
50
 
51
+ For the dedicated read-only Lambda (`readOnlyHandler`), define at least one `*_READ_ONLY` variable. Any read-only variable that is not set falls back to the corresponding main variable. For example, to point only the host to a read replica:
52
+
53
+ ```env
54
+ DATABASE_HOST_READ_ONLY=your_read_only_host
55
+ # DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_PORT are reused automatically
56
+ ```
57
+
58
+ To use a fully isolated read-only database user:
59
+
60
+ ```env
61
+ DATABASE_NAME_READ_ONLY=your_read_only_database_name
62
+ DATABASE_USERNAME_READ_ONLY=your_read_only_username
63
+ DATABASE_PASSWORD_READ_ONLY=your_read_only_password
64
+ DATABASE_HOST_READ_ONLY=your_read_only_host
65
+ DATABASE_PORT_READ_ONLY=5432
66
+ ```
67
+
68
+ > **Security note:** If none of the `*_READ_ONLY` variables are set, the read-only Lambda throws an error at invocation time.
69
+
52
70
  ### Deployment
53
71
 
54
72
  Add a deploy script to your `package.json`:
@@ -96,12 +114,6 @@ const result = await query({
96
114
  values: [userId],
97
115
  });
98
116
 
99
- // Use read-only connection
100
- const result = await query({
101
- text: 'SELECT * FROM users',
102
- readOnly: true, // Defaults to true
103
- });
104
-
105
117
  // Disable automatic camelCase conversion
106
118
  const result = await query({
107
119
  text: 'SELECT * FROM users',
@@ -115,6 +127,12 @@ const result = await query({
115
127
  });
116
128
  ```
117
129
 
130
+ ## Security: Isolating Read-Only Access
131
+
132
+ Deploying a dedicated `readOnlyHandler` Lambda lets you enforce the principle of least privilege at the AWS IAM level. Services that only need to read data (dashboards, reports, public APIs) receive IAM permissions to invoke **only** the read-only Lambda — they have no way to invoke the general-purpose Lambda and cannot perform write operations, regardless of the SQL they send.
133
+
134
+ This means a compromised or misconfigured service can never corrupt or delete data; it is limited to SELECT queries enforced both by the database (`BEGIN READ ONLY` transaction) and by the IAM boundary.
135
+
118
136
  ## API Reference
119
137
 
120
138
  ### `createLambdaPostgresQueryTemplate(options?)`
@@ -141,7 +159,6 @@ Accepts either a SQL string or an options object extending [`QueryConfig`](https
141
159
 
142
160
  - `text` (string): SQL query text
143
161
  - `values` (array, optional): Query parameter values
144
- - `readOnly` (boolean, optional): Use read-only database host if available. Default: `true`
145
162
  - `lambdaPostgresQueryFunction` (string, optional): Name of the query Lambda function. Default: `LAMBDA_POSTGRES_QUERY_FUNCTION` environment variable
146
163
  - `camelCaseKeys` (boolean, optional): Convert snake_case column names to camelCase. Default: `true`
147
164
 
@@ -151,4 +168,8 @@ A [`QueryResult`](https://node-postgres.com/apis/result) object with transformed
151
168
 
152
169
  ### `handler`
153
170
 
154
- AWS Lambda handler function for processing database queries within the VPC.
171
+ AWS Lambda handler function for processing database queries within the VPC. Uses `DATABASE_NAME`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_HOST`, and `DATABASE_PORT`.
172
+
173
+ ### `readOnlyHandler`
174
+
175
+ AWS Lambda handler function for processing **read-only** database queries within the VPC. Enforces read-only access via a PostgreSQL `BEGIN READ ONLY` transaction. Uses dedicated `*_READ_ONLY` environment variables where defined, falling back to the main variables for any that are not set. Throws an error at invocation time if none of the `*_READ_ONLY` variables are defined.
@@ -5,14 +5,18 @@ import { QueryParams } from '../index.cjs';
5
5
  import 'pg';
6
6
 
7
7
  declare const HANDLER_DEFAULT = "handler.handler";
8
+ declare const HANDLER_READ_ONLY_DEFAULT = "handler.readOnlyHandler";
8
9
  declare const MEMORY_SIZE_DEFAULT = 128;
9
10
  declare const TIMEOUT_DEFAULT = 30;
10
- declare const createLambdaPostgresQueryTemplate: ({ handler, memorySize, timeout, }?: {
11
+ declare const createLambdaPostgresQueryTemplate: ({ handler, readOnlyHandler, memorySize, timeout, }?: {
11
12
  handler?: string;
13
+ readOnlyHandler?: string;
12
14
  memorySize?: number;
13
15
  timeout?: number;
14
16
  }) => CloudFormationTemplate;
15
17
 
16
18
  declare const handler: Handler<QueryParams>;
17
19
 
18
- export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
20
+ declare const readOnlyHandler: Handler<QueryParams>;
21
+
22
+ export { HANDLER_DEFAULT, HANDLER_READ_ONLY_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler, readOnlyHandler };
@@ -5,14 +5,18 @@ import { QueryParams } from '../index.js';
5
5
  import 'pg';
6
6
 
7
7
  declare const HANDLER_DEFAULT = "handler.handler";
8
+ declare const HANDLER_READ_ONLY_DEFAULT = "handler.readOnlyHandler";
8
9
  declare const MEMORY_SIZE_DEFAULT = 128;
9
10
  declare const TIMEOUT_DEFAULT = 30;
10
- declare const createLambdaPostgresQueryTemplate: ({ handler, memorySize, timeout, }?: {
11
+ declare const createLambdaPostgresQueryTemplate: ({ handler, readOnlyHandler, memorySize, timeout, }?: {
11
12
  handler?: string;
13
+ readOnlyHandler?: string;
12
14
  memorySize?: number;
13
15
  timeout?: number;
14
16
  }) => CloudFormationTemplate;
15
17
 
16
18
  declare const handler: Handler<QueryParams>;
17
19
 
18
- export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
20
+ declare const readOnlyHandler: Handler<QueryParams>;
21
+
22
+ export { HANDLER_DEFAULT, HANDLER_READ_ONLY_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler, readOnlyHandler };
@@ -32,19 +32,23 @@ var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
32
32
  var cloudformation_exports = {};
33
33
  __export(cloudformation_exports, {
34
34
  HANDLER_DEFAULT: () => HANDLER_DEFAULT,
35
+ HANDLER_READ_ONLY_DEFAULT: () => HANDLER_READ_ONLY_DEFAULT,
35
36
  MEMORY_SIZE_DEFAULT: () => MEMORY_SIZE_DEFAULT,
36
37
  TIMEOUT_DEFAULT: () => TIMEOUT_DEFAULT,
37
38
  createLambdaPostgresQueryTemplate: () => createLambdaPostgresQueryTemplate,
38
- handler: () => handler
39
+ handler: () => handler,
40
+ readOnlyHandler: () => readOnlyHandler
39
41
  });
40
42
  module.exports = __toCommonJS(cloudformation_exports);
41
43
 
42
44
  // src/cloudformation/createLambdaPostgresQueryTemplate.ts
43
45
  var HANDLER_DEFAULT = "handler.handler";
46
+ var HANDLER_READ_ONLY_DEFAULT = "handler.readOnlyHandler";
44
47
  var MEMORY_SIZE_DEFAULT = 128;
45
48
  var TIMEOUT_DEFAULT = 30;
46
49
  var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
47
50
  handler: handler2 = HANDLER_DEFAULT,
51
+ readOnlyHandler: readOnlyHandler2 = HANDLER_READ_ONLY_DEFAULT,
48
52
  memorySize = 128,
49
53
  timeout = 30
50
54
  } = {}) => {
@@ -70,13 +74,32 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
70
74
  },
71
75
  DatabasePassword: {
72
76
  Type: "String",
73
- Description: "Database password."
77
+ Description: "Database password.",
78
+ NoEcho: true
74
79
  },
75
80
  DatabasePort: {
76
81
  Type: "String",
77
82
  Default: "5432",
78
83
  Description: "Database port."
79
84
  },
85
+ DatabaseNameReadOnly: {
86
+ Type: "String",
87
+ Description: "Database name for read-only access."
88
+ },
89
+ DatabaseUsernameReadOnly: {
90
+ Type: "String",
91
+ Description: "Database username for read-only access."
92
+ },
93
+ DatabasePasswordReadOnly: {
94
+ Type: "String",
95
+ Description: "Database password for read-only access.",
96
+ NoEcho: true
97
+ },
98
+ DatabasePortReadOnly: {
99
+ Type: "String",
100
+ Default: "5432",
101
+ Description: "Database port for read-only access."
102
+ },
80
103
  LambdaS3Bucket: {
81
104
  Type: "String",
82
105
  Description: "The S3 bucket where the Lambda code is stored."
@@ -135,15 +158,12 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
135
158
  Role: {
136
159
  "Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
137
160
  },
138
- Runtime: "nodejs22.x",
161
+ Runtime: "nodejs24.x",
139
162
  Environment: {
140
163
  Variables: {
141
164
  DATABASE_HOST: {
142
165
  Ref: "DatabaseHost"
143
166
  },
144
- DATABASE_HOST_READ_ONLY: {
145
- Ref: "DatabaseHostReadOnly"
146
- },
147
167
  DATABASE_NAME: {
148
168
  Ref: "DatabaseName"
149
169
  },
@@ -177,7 +197,84 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
177
197
  Ref: "LambdaQueryFunction"
178
198
  }]]
179
199
  },
180
- RetentionInDays: 7
200
+ RetentionInDays: 3
201
+ }
202
+ },
203
+ LambdaReadOnlyQueryFunction: {
204
+ Type: "AWS::Lambda::Function",
205
+ Properties: {
206
+ Code: {
207
+ S3Bucket: {
208
+ Ref: "LambdaS3Bucket"
209
+ },
210
+ S3Key: {
211
+ Ref: "LambdaS3Key"
212
+ },
213
+ S3ObjectVersion: {
214
+ Ref: "LambdaS3ObjectVersion"
215
+ }
216
+ },
217
+ MemorySize: memorySize,
218
+ Timeout: timeout,
219
+ Handler: readOnlyHandler2,
220
+ Role: {
221
+ "Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
222
+ },
223
+ Runtime: "nodejs24.x",
224
+ Environment: {
225
+ Variables: {
226
+ DATABASE_HOST: {
227
+ Ref: "DatabaseHost"
228
+ },
229
+ DATABASE_NAME: {
230
+ Ref: "DatabaseName"
231
+ },
232
+ DATABASE_USERNAME: {
233
+ Ref: "DatabaseUsername"
234
+ },
235
+ DATABASE_PASSWORD: {
236
+ Ref: "DatabasePassword"
237
+ },
238
+ DATABASE_PORT: {
239
+ Ref: "DatabasePort"
240
+ },
241
+ DATABASE_HOST_READ_ONLY: {
242
+ Ref: "DatabaseHostReadOnly"
243
+ },
244
+ DATABASE_NAME_READ_ONLY: {
245
+ Ref: "DatabaseNameReadOnly"
246
+ },
247
+ DATABASE_USERNAME_READ_ONLY: {
248
+ Ref: "DatabaseUsernameReadOnly"
249
+ },
250
+ DATABASE_PASSWORD_READ_ONLY: {
251
+ Ref: "DatabasePasswordReadOnly"
252
+ },
253
+ DATABASE_PORT_READ_ONLY: {
254
+ Ref: "DatabasePortReadOnly"
255
+ }
256
+ }
257
+ },
258
+ VpcConfig: {
259
+ SecurityGroupIds: {
260
+ Ref: "SecurityGroupIds"
261
+ },
262
+ SubnetIds: {
263
+ Ref: "SubnetIds"
264
+ }
265
+ }
266
+ }
267
+ },
268
+ LambdaReadOnlyQueryFunctionLogs: {
269
+ Type: "AWS::Logs::LogGroup",
270
+ DependsOn: "LambdaReadOnlyQueryFunction",
271
+ Properties: {
272
+ LogGroupName: {
273
+ "Fn::Join": ["", ["/aws/lambda/", {
274
+ Ref: "LambdaReadOnlyQueryFunction"
275
+ }]]
276
+ },
277
+ RetentionInDays: 3
181
278
  }
182
279
  }
183
280
  },
@@ -193,6 +290,18 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
193
290
  Value: {
194
291
  "Fn::GetAtt": ["LambdaQueryFunction", "Arn"]
195
292
  }
293
+ },
294
+ LambdaPostgresReadOnlyQueryFunction: {
295
+ Description: "Lambda function to query PostgreSQL (read-only).",
296
+ Value: {
297
+ Ref: "LambdaReadOnlyQueryFunction"
298
+ }
299
+ },
300
+ LambdaPostgresReadOnlyQueryFunctionArn: {
301
+ Description: "Lambda function to query PostgreSQL (read-only) ARN.",
302
+ Value: {
303
+ "Fn::GetAtt": ["LambdaReadOnlyQueryFunction", "Arn"]
304
+ }
196
305
  }
197
306
  }
198
307
  };
@@ -204,7 +313,6 @@ var database = process.env.DATABASE_NAME;
204
313
  var username = process.env.DATABASE_USERNAME;
205
314
  var password = process.env.DATABASE_PASSWORD;
206
315
  var host = process.env.DATABASE_HOST;
207
- var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
208
316
  var port = process.env.DATABASE_PORT;
209
317
  var handler = /* @__PURE__ */__name(async event => {
210
318
  try {
@@ -212,7 +320,7 @@ var handler = /* @__PURE__ */__name(async event => {
212
320
  database,
213
321
  user: username,
214
322
  password,
215
- host: event.readOnly && hostReadOnly ? hostReadOnly : host,
323
+ host,
216
324
  port: Number(port)
217
325
  });
218
326
  await client.connect();
@@ -230,11 +338,60 @@ var handler = /* @__PURE__ */__name(async event => {
230
338
  throw error;
231
339
  }
232
340
  }, "handler");
341
+
342
+ // src/cloudformation/lambdaReadOnlyQueryHandler.ts
343
+ var import_pg2 = require("pg");
344
+ var databaseReadOnly = process.env.DATABASE_NAME_READ_ONLY;
345
+ var usernameReadOnly = process.env.DATABASE_USERNAME_READ_ONLY;
346
+ var passwordReadOnly = process.env.DATABASE_PASSWORD_READ_ONLY;
347
+ var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
348
+ var portReadOnly = process.env.DATABASE_PORT_READ_ONLY;
349
+ var readOnlyHandler = /* @__PURE__ */__name(async event => {
350
+ if (!databaseReadOnly && !usernameReadOnly && !passwordReadOnly && !hostReadOnly && !portReadOnly) {
351
+ throw new Error("At least one read-only override must be defined (DATABASE_NAME_READ_ONLY, DATABASE_USERNAME_READ_ONLY, DATABASE_PASSWORD_READ_ONLY, DATABASE_HOST_READ_ONLY, DATABASE_PORT_READ_ONLY). Unset overrides fall back to the corresponding non-read-only env vars.");
352
+ }
353
+ const database2 = databaseReadOnly || process.env.DATABASE_NAME;
354
+ const username2 = usernameReadOnly || process.env.DATABASE_USERNAME;
355
+ const password2 = passwordReadOnly || process.env.DATABASE_PASSWORD;
356
+ const host2 = hostReadOnly || process.env.DATABASE_HOST;
357
+ const port2 = portReadOnly || process.env.DATABASE_PORT;
358
+ try {
359
+ const client = new import_pg2.Client({
360
+ database: database2,
361
+ user: username2,
362
+ password: password2,
363
+ host: host2,
364
+ port: Number(port2)
365
+ });
366
+ await client.connect();
367
+ try {
368
+ await client.query("BEGIN READ ONLY");
369
+ try {
370
+ const res = await client.query(event);
371
+ await client.query("COMMIT");
372
+ return res;
373
+ } catch (queryError) {
374
+ await client.query("ROLLBACK");
375
+ throw queryError;
376
+ }
377
+ } finally {
378
+ await client.end();
379
+ }
380
+ } catch (error) {
381
+ console.error("Error running read-only query", {
382
+ error,
383
+ event
384
+ });
385
+ throw error;
386
+ }
387
+ }, "readOnlyHandler");
233
388
  // Annotate the CommonJS export names for ESM import in node:
234
389
  0 && (module.exports = {
235
390
  HANDLER_DEFAULT,
391
+ HANDLER_READ_ONLY_DEFAULT,
236
392
  MEMORY_SIZE_DEFAULT,
237
393
  TIMEOUT_DEFAULT,
238
394
  createLambdaPostgresQueryTemplate,
239
- handler
395
+ handler,
396
+ readOnlyHandler
240
397
  });
@@ -3,10 +3,12 @@ import { __name } from "../chunk-V4MHYKRI.js";
3
3
 
4
4
  // src/cloudformation/createLambdaPostgresQueryTemplate.ts
5
5
  var HANDLER_DEFAULT = "handler.handler";
6
+ var HANDLER_READ_ONLY_DEFAULT = "handler.readOnlyHandler";
6
7
  var MEMORY_SIZE_DEFAULT = 128;
7
8
  var TIMEOUT_DEFAULT = 30;
8
9
  var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
9
10
  handler: handler2 = HANDLER_DEFAULT,
11
+ readOnlyHandler: readOnlyHandler2 = HANDLER_READ_ONLY_DEFAULT,
10
12
  memorySize = 128,
11
13
  timeout = 30
12
14
  } = {}) => {
@@ -32,13 +34,32 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
32
34
  },
33
35
  DatabasePassword: {
34
36
  Type: "String",
35
- Description: "Database password."
37
+ Description: "Database password.",
38
+ NoEcho: true
36
39
  },
37
40
  DatabasePort: {
38
41
  Type: "String",
39
42
  Default: "5432",
40
43
  Description: "Database port."
41
44
  },
45
+ DatabaseNameReadOnly: {
46
+ Type: "String",
47
+ Description: "Database name for read-only access."
48
+ },
49
+ DatabaseUsernameReadOnly: {
50
+ Type: "String",
51
+ Description: "Database username for read-only access."
52
+ },
53
+ DatabasePasswordReadOnly: {
54
+ Type: "String",
55
+ Description: "Database password for read-only access.",
56
+ NoEcho: true
57
+ },
58
+ DatabasePortReadOnly: {
59
+ Type: "String",
60
+ Default: "5432",
61
+ Description: "Database port for read-only access."
62
+ },
42
63
  LambdaS3Bucket: {
43
64
  Type: "String",
44
65
  Description: "The S3 bucket where the Lambda code is stored."
@@ -97,15 +118,12 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
97
118
  Role: {
98
119
  "Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
99
120
  },
100
- Runtime: "nodejs22.x",
121
+ Runtime: "nodejs24.x",
101
122
  Environment: {
102
123
  Variables: {
103
124
  DATABASE_HOST: {
104
125
  Ref: "DatabaseHost"
105
126
  },
106
- DATABASE_HOST_READ_ONLY: {
107
- Ref: "DatabaseHostReadOnly"
108
- },
109
127
  DATABASE_NAME: {
110
128
  Ref: "DatabaseName"
111
129
  },
@@ -139,7 +157,84 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
139
157
  Ref: "LambdaQueryFunction"
140
158
  }]]
141
159
  },
142
- RetentionInDays: 7
160
+ RetentionInDays: 3
161
+ }
162
+ },
163
+ LambdaReadOnlyQueryFunction: {
164
+ Type: "AWS::Lambda::Function",
165
+ Properties: {
166
+ Code: {
167
+ S3Bucket: {
168
+ Ref: "LambdaS3Bucket"
169
+ },
170
+ S3Key: {
171
+ Ref: "LambdaS3Key"
172
+ },
173
+ S3ObjectVersion: {
174
+ Ref: "LambdaS3ObjectVersion"
175
+ }
176
+ },
177
+ MemorySize: memorySize,
178
+ Timeout: timeout,
179
+ Handler: readOnlyHandler2,
180
+ Role: {
181
+ "Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
182
+ },
183
+ Runtime: "nodejs24.x",
184
+ Environment: {
185
+ Variables: {
186
+ DATABASE_HOST: {
187
+ Ref: "DatabaseHost"
188
+ },
189
+ DATABASE_NAME: {
190
+ Ref: "DatabaseName"
191
+ },
192
+ DATABASE_USERNAME: {
193
+ Ref: "DatabaseUsername"
194
+ },
195
+ DATABASE_PASSWORD: {
196
+ Ref: "DatabasePassword"
197
+ },
198
+ DATABASE_PORT: {
199
+ Ref: "DatabasePort"
200
+ },
201
+ DATABASE_HOST_READ_ONLY: {
202
+ Ref: "DatabaseHostReadOnly"
203
+ },
204
+ DATABASE_NAME_READ_ONLY: {
205
+ Ref: "DatabaseNameReadOnly"
206
+ },
207
+ DATABASE_USERNAME_READ_ONLY: {
208
+ Ref: "DatabaseUsernameReadOnly"
209
+ },
210
+ DATABASE_PASSWORD_READ_ONLY: {
211
+ Ref: "DatabasePasswordReadOnly"
212
+ },
213
+ DATABASE_PORT_READ_ONLY: {
214
+ Ref: "DatabasePortReadOnly"
215
+ }
216
+ }
217
+ },
218
+ VpcConfig: {
219
+ SecurityGroupIds: {
220
+ Ref: "SecurityGroupIds"
221
+ },
222
+ SubnetIds: {
223
+ Ref: "SubnetIds"
224
+ }
225
+ }
226
+ }
227
+ },
228
+ LambdaReadOnlyQueryFunctionLogs: {
229
+ Type: "AWS::Logs::LogGroup",
230
+ DependsOn: "LambdaReadOnlyQueryFunction",
231
+ Properties: {
232
+ LogGroupName: {
233
+ "Fn::Join": ["", ["/aws/lambda/", {
234
+ Ref: "LambdaReadOnlyQueryFunction"
235
+ }]]
236
+ },
237
+ RetentionInDays: 3
143
238
  }
144
239
  }
145
240
  },
@@ -155,6 +250,18 @@ var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
155
250
  Value: {
156
251
  "Fn::GetAtt": ["LambdaQueryFunction", "Arn"]
157
252
  }
253
+ },
254
+ LambdaPostgresReadOnlyQueryFunction: {
255
+ Description: "Lambda function to query PostgreSQL (read-only).",
256
+ Value: {
257
+ Ref: "LambdaReadOnlyQueryFunction"
258
+ }
259
+ },
260
+ LambdaPostgresReadOnlyQueryFunctionArn: {
261
+ Description: "Lambda function to query PostgreSQL (read-only) ARN.",
262
+ Value: {
263
+ "Fn::GetAtt": ["LambdaReadOnlyQueryFunction", "Arn"]
264
+ }
158
265
  }
159
266
  }
160
267
  };
@@ -166,7 +273,6 @@ var database = process.env.DATABASE_NAME;
166
273
  var username = process.env.DATABASE_USERNAME;
167
274
  var password = process.env.DATABASE_PASSWORD;
168
275
  var host = process.env.DATABASE_HOST;
169
- var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
170
276
  var port = process.env.DATABASE_PORT;
171
277
  var handler = /* @__PURE__ */__name(async event => {
172
278
  try {
@@ -174,7 +280,7 @@ var handler = /* @__PURE__ */__name(async event => {
174
280
  database,
175
281
  user: username,
176
282
  password,
177
- host: event.readOnly && hostReadOnly ? hostReadOnly : host,
283
+ host,
178
284
  port: Number(port)
179
285
  });
180
286
  await client.connect();
@@ -192,4 +298,51 @@ var handler = /* @__PURE__ */__name(async event => {
192
298
  throw error;
193
299
  }
194
300
  }, "handler");
195
- export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
301
+
302
+ // src/cloudformation/lambdaReadOnlyQueryHandler.ts
303
+ import { Client as Client2 } from "pg";
304
+ var databaseReadOnly = process.env.DATABASE_NAME_READ_ONLY;
305
+ var usernameReadOnly = process.env.DATABASE_USERNAME_READ_ONLY;
306
+ var passwordReadOnly = process.env.DATABASE_PASSWORD_READ_ONLY;
307
+ var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
308
+ var portReadOnly = process.env.DATABASE_PORT_READ_ONLY;
309
+ var readOnlyHandler = /* @__PURE__ */__name(async event => {
310
+ if (!databaseReadOnly && !usernameReadOnly && !passwordReadOnly && !hostReadOnly && !portReadOnly) {
311
+ throw new Error("At least one read-only override must be defined (DATABASE_NAME_READ_ONLY, DATABASE_USERNAME_READ_ONLY, DATABASE_PASSWORD_READ_ONLY, DATABASE_HOST_READ_ONLY, DATABASE_PORT_READ_ONLY). Unset overrides fall back to the corresponding non-read-only env vars.");
312
+ }
313
+ const database2 = databaseReadOnly || process.env.DATABASE_NAME;
314
+ const username2 = usernameReadOnly || process.env.DATABASE_USERNAME;
315
+ const password2 = passwordReadOnly || process.env.DATABASE_PASSWORD;
316
+ const host2 = hostReadOnly || process.env.DATABASE_HOST;
317
+ const port2 = portReadOnly || process.env.DATABASE_PORT;
318
+ try {
319
+ const client = new Client2({
320
+ database: database2,
321
+ user: username2,
322
+ password: password2,
323
+ host: host2,
324
+ port: Number(port2)
325
+ });
326
+ await client.connect();
327
+ try {
328
+ await client.query("BEGIN READ ONLY");
329
+ try {
330
+ const res = await client.query(event);
331
+ await client.query("COMMIT");
332
+ return res;
333
+ } catch (queryError) {
334
+ await client.query("ROLLBACK");
335
+ throw queryError;
336
+ }
337
+ } finally {
338
+ await client.end();
339
+ }
340
+ } catch (error) {
341
+ console.error("Error running read-only query", {
342
+ error,
343
+ event
344
+ });
345
+ throw error;
346
+ }
347
+ }, "readOnlyHandler");
348
+ export { HANDLER_DEFAULT, HANDLER_READ_ONLY_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler, readOnlyHandler };
package/dist/esm/index.js CHANGED
@@ -9,7 +9,6 @@ var textDecoder = new TextDecoder("utf-8");
9
9
  var query = /* @__PURE__ */__name(async params => {
10
10
  try {
11
11
  const {
12
- readOnly = true,
13
12
  // eslint-disable-next-line turbo/no-undeclared-env-vars
14
13
  lambdaPostgresQueryFunction = process.env.LAMBDA_POSTGRES_QUERY_FUNCTION,
15
14
  camelCaseKeys = true,
@@ -20,7 +19,6 @@ var query = /* @__PURE__ */__name(async params => {
20
19
  const input = {
21
20
  FunctionName: lambdaPostgresQueryFunction,
22
21
  Payload: JSON.stringify({
23
- readOnly,
24
22
  ...pgParams
25
23
  })
26
24
  };
package/dist/index.d.cts CHANGED
@@ -2,7 +2,6 @@ import * as pg from 'pg';
2
2
  import { QueryConfig, QueryResultRow } from 'pg';
3
3
 
4
4
  type QueryParams = {
5
- readOnly?: boolean;
6
5
  lambdaPostgresQueryFunction?: string;
7
6
  camelCaseKeys?: boolean;
8
7
  } & QueryConfig;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,6 @@ import * as pg from 'pg';
2
2
  import { QueryConfig, QueryResultRow } from 'pg';
3
3
 
4
4
  type QueryParams = {
5
- readOnly?: boolean;
6
5
  lambdaPostgresQueryFunction?: string;
7
6
  camelCaseKeys?: boolean;
8
7
  } & QueryConfig;
package/dist/index.js CHANGED
@@ -54,7 +54,6 @@ var textDecoder = new TextDecoder("utf-8");
54
54
  var query = /* @__PURE__ */__name(async params => {
55
55
  try {
56
56
  const {
57
- readOnly = true,
58
57
  // eslint-disable-next-line turbo/no-undeclared-env-vars
59
58
  lambdaPostgresQueryFunction = process.env.LAMBDA_POSTGRES_QUERY_FUNCTION,
60
59
  camelCaseKeys = true,
@@ -65,7 +64,6 @@ var query = /* @__PURE__ */__name(async params => {
65
64
  const input = {
66
65
  FunctionName: lambdaPostgresQueryFunction,
67
66
  Payload: JSON.stringify({
68
- readOnly,
69
67
  ...pgParams
70
68
  })
71
69
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/lambda-postgres-query",
3
- "version": "0.3.29",
3
+ "version": "0.4.0",
4
4
  "description": "Create a Lambda function that queries a PostgreSQL database.",
5
5
  "license": "MIT",
6
6
  "author": "ttoss",
@@ -29,19 +29,19 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@aws-sdk/client-lambda": "^3.731.1",
32
+ "@aws-sdk/client-lambda": "^3.1025.0",
33
33
  "camelcase-keys": "^7.0.2",
34
- "pg": "^8.16.3",
35
- "@ttoss/cloudformation": "^0.12.9"
34
+ "pg": "^8.20.0",
35
+ "@ttoss/cloudformation": "^0.12.10"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/jest": "^30.0.0",
39
- "@types/pg": "^8.11.10",
39
+ "@types/pg": "^8.20.0",
40
40
  "aws-sdk-client-mock": "^4.1.0",
41
41
  "jest": "^30.3.0",
42
42
  "tsup": "^8.5.1",
43
- "@ttoss/test-utils": "^4.2.7",
44
- "@ttoss/config": "^1.37.7"
43
+ "@ttoss/config": "^1.37.8",
44
+ "@ttoss/test-utils": "^4.2.8"
45
45
  },
46
46
  "keywords": [
47
47
  "aws",