@ttoss/lambda-postgres-query 0.3.17 → 0.3.18
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 +88 -38
- package/dist/cloudformation/index.d.cts +18 -0
- package/dist/cloudformation/index.d.ts +18 -0
- package/dist/cloudformation/index.js +240 -0
- package/dist/esm/chunk-V4MHYKRI.js +7 -0
- package/dist/esm/cloudformation/index.js +195 -0
- package/dist/esm/index.js +61 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +109 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
1
|
# @ttoss/lambda-postgres-query
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Create an AWS Lambda function to securely query a PostgreSQL database in a private VPC subnet without exposing the database to the internet.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## When to Use
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
2. Decompose your architecture into multiple Lambdas—some inside the VPC and some outside the VPC. The Lambda inside the VPC can query the database, and the Lambda outside the VPC can query the Lambda inside the VPC. On this approach, Lambdas outside the VPC invoke Lambdas inside the VPC using the AWS SDK to query the database. This approach is complex and requires more effort to maintain.
|
|
10
|
-
|
|
11
|
-
_Check this StackOverflow question for more information: [Why can't an AWS lambda function inside a public subnet in a VPC connect to the internet?](https://stackoverflow.com/questions/52992085/why-cant-an-aws-lambda-function-inside-a-public-subnet-in-a-vpc-connect-to-the)_
|
|
7
|
+
This package solves the challenge of querying a PostgreSQL database from AWS Lambda functions without internet access. Traditional approaches require expensive NAT Gateways or complex multi-Lambda architectures. This package provides a simpler solution by deploying a dedicated Lambda function within your VPC.
|
|
12
8
|
|
|
13
9
|
## Installation
|
|
14
10
|
|
|
15
|
-
To install this package, you need to run the following command:
|
|
16
|
-
|
|
17
11
|
```bash
|
|
18
12
|
pnpm install @ttoss/lambda-postgres-query
|
|
19
13
|
```
|
|
20
14
|
|
|
21
|
-
##
|
|
15
|
+
## Setup
|
|
22
16
|
|
|
23
|
-
### CloudFormation
|
|
17
|
+
### CloudFormation Template
|
|
24
18
|
|
|
25
|
-
Create a
|
|
19
|
+
Create a CloudFormation template to deploy the Lambda function:
|
|
26
20
|
|
|
27
21
|
```typescript
|
|
28
22
|
import { createLambdaPostgresQueryTemplate } from '@ttoss/lambda-postgres-query/cloudformation';
|
|
@@ -32,26 +26,32 @@ const template = createLambdaPostgresQueryTemplate();
|
|
|
32
26
|
export default template;
|
|
33
27
|
```
|
|
34
28
|
|
|
35
|
-
|
|
29
|
+
### Lambda Handler
|
|
30
|
+
|
|
31
|
+
Create a handler file that exports the Lambda function:
|
|
36
32
|
|
|
37
33
|
```typescript
|
|
38
34
|
export { handler } from '@ttoss/lambda-postgres-query';
|
|
39
35
|
```
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
### Environment Variables
|
|
38
|
+
|
|
39
|
+
Configure the following environment variables:
|
|
42
40
|
|
|
43
41
|
```env
|
|
44
|
-
DATABASE_NAME=
|
|
45
|
-
DATABASE_USERNAME=
|
|
46
|
-
DATABASE_PASSWORD=
|
|
47
|
-
DATABASE_HOST=
|
|
48
|
-
DATABASE_HOST_READ_ONLY=
|
|
49
|
-
DATABASE_PORT=
|
|
50
|
-
SECURITY_GROUP_IDS=
|
|
51
|
-
SUBNET_IDS=
|
|
42
|
+
DATABASE_NAME=your_database_name
|
|
43
|
+
DATABASE_USERNAME=your_username
|
|
44
|
+
DATABASE_PASSWORD=your_password
|
|
45
|
+
DATABASE_HOST=your_database_host
|
|
46
|
+
DATABASE_HOST_READ_ONLY=your_read_only_host # Optional
|
|
47
|
+
DATABASE_PORT=5432
|
|
48
|
+
SECURITY_GROUP_IDS=sg-xxxxx,sg-yyyyy
|
|
49
|
+
SUBNET_IDS=subnet-xxxxx,subnet-yyyyy
|
|
52
50
|
```
|
|
53
51
|
|
|
54
|
-
|
|
52
|
+
### Deployment
|
|
53
|
+
|
|
54
|
+
Add a deploy script to your `package.json`:
|
|
55
55
|
|
|
56
56
|
```json
|
|
57
57
|
{
|
|
@@ -61,44 +61,94 @@ Add the `deploy` script to the `package.json` file:
|
|
|
61
61
|
}
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
Deploy using Carlin:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
pnpm deploy
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
**Note:** Set `lambdaFormat: 'cjs'` in your Carlin configuration, as the `pg` package requires CommonJS.
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
## Usage
|
|
73
73
|
|
|
74
|
-
### Querying
|
|
74
|
+
### Querying from External Lambdas
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Query the database from Lambda functions outside the VPC:
|
|
77
77
|
|
|
78
78
|
```typescript
|
|
79
79
|
import { query } from '@ttoss/lambda-postgres-query';
|
|
80
80
|
import type { Handler } from 'aws-lambda';
|
|
81
81
|
|
|
82
82
|
export const handler: Handler = async (event) => {
|
|
83
|
-
const
|
|
84
|
-
const result = await query({ text });
|
|
83
|
+
const result = await query('SELECT * FROM users');
|
|
85
84
|
return result.rows;
|
|
86
85
|
};
|
|
87
86
|
```
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
### Advanced Query Options
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { query } from '@ttoss/lambda-postgres-query';
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
// Query with parameters
|
|
94
|
+
const result = await query({
|
|
95
|
+
text: 'SELECT * FROM users WHERE id = $1',
|
|
96
|
+
values: [userId],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Use read-only connection
|
|
100
|
+
const result = await query({
|
|
101
|
+
text: 'SELECT * FROM users',
|
|
102
|
+
readOnly: true, // Defaults to true
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Disable automatic camelCase conversion
|
|
106
|
+
const result = await query({
|
|
107
|
+
text: 'SELECT * FROM users',
|
|
108
|
+
camelCaseKeys: false, // Defaults to true
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Specify custom Lambda function name
|
|
112
|
+
const result = await query({
|
|
113
|
+
text: 'SELECT * FROM users',
|
|
114
|
+
lambdaPostgresQueryFunction: 'custom-function-name',
|
|
115
|
+
});
|
|
116
|
+
```
|
|
92
117
|
|
|
93
|
-
|
|
118
|
+
## API Reference
|
|
94
119
|
|
|
95
|
-
### `
|
|
120
|
+
### `createLambdaPostgresQueryTemplate(options?)`
|
|
96
121
|
|
|
97
|
-
|
|
122
|
+
Creates a CloudFormation template for the PostgreSQL query Lambda function.
|
|
98
123
|
|
|
99
124
|
#### Parameters
|
|
100
125
|
|
|
101
|
-
|
|
126
|
+
- `handler` (string, optional): Lambda handler function name. Default: `'handler.handler'`
|
|
127
|
+
- `memorySize` (number, optional): Lambda memory size in MB. Default: `128`
|
|
128
|
+
- `timeout` (number, optional): Lambda timeout in seconds. Default: `30`
|
|
129
|
+
|
|
130
|
+
#### Returns
|
|
131
|
+
|
|
132
|
+
A CloudFormation template object.
|
|
133
|
+
|
|
134
|
+
### `query(params)`
|
|
135
|
+
|
|
136
|
+
Queries the PostgreSQL database by invoking the VPC Lambda function.
|
|
137
|
+
|
|
138
|
+
#### Parameters
|
|
139
|
+
|
|
140
|
+
Accepts either a SQL string or an options object extending [`QueryConfig`](https://node-postgres.com/apis/client#queryconfig) with additional properties:
|
|
141
|
+
|
|
142
|
+
- `text` (string): SQL query text
|
|
143
|
+
- `values` (array, optional): Query parameter values
|
|
144
|
+
- `readOnly` (boolean, optional): Use read-only database host if available. Default: `true`
|
|
145
|
+
- `lambdaPostgresQueryFunction` (string, optional): Name of the query Lambda function. Default: `LAMBDA_POSTGRES_QUERY_FUNCTION` environment variable
|
|
146
|
+
- `camelCaseKeys` (boolean, optional): Convert snake_case column names to camelCase. Default: `true`
|
|
147
|
+
|
|
148
|
+
#### Returns
|
|
149
|
+
|
|
150
|
+
A [`QueryResult`](https://node-postgres.com/apis/result) object with transformed rows.
|
|
151
|
+
|
|
152
|
+
### `handler`
|
|
102
153
|
|
|
103
|
-
|
|
104
|
-
- `lambdaPostgresQueryFunction`: The name of the Lambda function that queries the database. Default is the value of the `LAMBDA_POSTGRES_QUERY_FUNCTION` environment variable.
|
|
154
|
+
AWS Lambda handler function for processing database queries within the VPC.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
2
|
+
export { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
3
|
+
import { Handler } from 'aws-lambda';
|
|
4
|
+
import { QueryParams } from '../index.cjs';
|
|
5
|
+
import 'pg';
|
|
6
|
+
|
|
7
|
+
declare const HANDLER_DEFAULT = "handler.handler";
|
|
8
|
+
declare const MEMORY_SIZE_DEFAULT = 128;
|
|
9
|
+
declare const TIMEOUT_DEFAULT = 30;
|
|
10
|
+
declare const createLambdaPostgresQueryTemplate: ({ handler, memorySize, timeout, }?: {
|
|
11
|
+
handler?: string;
|
|
12
|
+
memorySize?: number;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => CloudFormationTemplate;
|
|
15
|
+
|
|
16
|
+
declare const handler: Handler<QueryParams>;
|
|
17
|
+
|
|
18
|
+
export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
2
|
+
export { CloudFormationTemplate } from '@ttoss/cloudformation';
|
|
3
|
+
import { Handler } from 'aws-lambda';
|
|
4
|
+
import { QueryParams } from '../index.js';
|
|
5
|
+
import 'pg';
|
|
6
|
+
|
|
7
|
+
declare const HANDLER_DEFAULT = "handler.handler";
|
|
8
|
+
declare const MEMORY_SIZE_DEFAULT = 128;
|
|
9
|
+
declare const TIMEOUT_DEFAULT = 30;
|
|
10
|
+
declare const createLambdaPostgresQueryTemplate: ({ handler, memorySize, timeout, }?: {
|
|
11
|
+
handler?: string;
|
|
12
|
+
memorySize?: number;
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}) => CloudFormationTemplate;
|
|
15
|
+
|
|
16
|
+
declare const handler: Handler<QueryParams>;
|
|
17
|
+
|
|
18
|
+
export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
9
|
+
value,
|
|
10
|
+
configurable: true
|
|
11
|
+
});
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all) __defProp(target, name, {
|
|
14
|
+
get: all[name],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
21
|
+
get: () => from[key],
|
|
22
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
28
|
+
value: true
|
|
29
|
+
}), mod);
|
|
30
|
+
|
|
31
|
+
// src/cloudformation/index.ts
|
|
32
|
+
var cloudformation_exports = {};
|
|
33
|
+
__export(cloudformation_exports, {
|
|
34
|
+
HANDLER_DEFAULT: () => HANDLER_DEFAULT,
|
|
35
|
+
MEMORY_SIZE_DEFAULT: () => MEMORY_SIZE_DEFAULT,
|
|
36
|
+
TIMEOUT_DEFAULT: () => TIMEOUT_DEFAULT,
|
|
37
|
+
createLambdaPostgresQueryTemplate: () => createLambdaPostgresQueryTemplate,
|
|
38
|
+
handler: () => handler
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(cloudformation_exports);
|
|
41
|
+
|
|
42
|
+
// src/cloudformation/createLambdaPostgresQueryTemplate.ts
|
|
43
|
+
var HANDLER_DEFAULT = "handler.handler";
|
|
44
|
+
var MEMORY_SIZE_DEFAULT = 128;
|
|
45
|
+
var TIMEOUT_DEFAULT = 30;
|
|
46
|
+
var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
|
|
47
|
+
handler: handler2 = HANDLER_DEFAULT,
|
|
48
|
+
memorySize = 128,
|
|
49
|
+
timeout = 30
|
|
50
|
+
} = {}) => {
|
|
51
|
+
return {
|
|
52
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
53
|
+
Description: "A Lambda function to query PostgreSQL.",
|
|
54
|
+
Parameters: {
|
|
55
|
+
DatabaseHost: {
|
|
56
|
+
Type: "String",
|
|
57
|
+
Description: "Database host."
|
|
58
|
+
},
|
|
59
|
+
DatabaseHostReadOnly: {
|
|
60
|
+
Type: "String",
|
|
61
|
+
Description: "Database host read only."
|
|
62
|
+
},
|
|
63
|
+
DatabaseName: {
|
|
64
|
+
Type: "String",
|
|
65
|
+
Description: "Database name."
|
|
66
|
+
},
|
|
67
|
+
DatabaseUsername: {
|
|
68
|
+
Type: "String",
|
|
69
|
+
Description: "Database username."
|
|
70
|
+
},
|
|
71
|
+
DatabasePassword: {
|
|
72
|
+
Type: "String",
|
|
73
|
+
Description: "Database password."
|
|
74
|
+
},
|
|
75
|
+
DatabasePort: {
|
|
76
|
+
Type: "String",
|
|
77
|
+
Default: "5432",
|
|
78
|
+
Description: "Database port."
|
|
79
|
+
},
|
|
80
|
+
LambdaS3Bucket: {
|
|
81
|
+
Type: "String",
|
|
82
|
+
Description: "The S3 bucket where the Lambda code is stored."
|
|
83
|
+
},
|
|
84
|
+
LambdaS3Key: {
|
|
85
|
+
Type: "String",
|
|
86
|
+
Description: "The S3 key where the Lambda code is stored."
|
|
87
|
+
},
|
|
88
|
+
LambdaS3ObjectVersion: {
|
|
89
|
+
Type: "String",
|
|
90
|
+
Description: "The S3 object version of the Lambda code."
|
|
91
|
+
},
|
|
92
|
+
SecurityGroupIds: {
|
|
93
|
+
Description: "Security Group IDs",
|
|
94
|
+
Type: "List<AWS::EC2::SecurityGroup::Id>"
|
|
95
|
+
},
|
|
96
|
+
SubnetIds: {
|
|
97
|
+
Description: "Subnet IDs",
|
|
98
|
+
Type: "List<AWS::EC2::Subnet::Id>"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
Resources: {
|
|
102
|
+
LambdaQueryExecutionRole: {
|
|
103
|
+
Type: "AWS::IAM::Role",
|
|
104
|
+
Properties: {
|
|
105
|
+
AssumeRolePolicyDocument: {
|
|
106
|
+
Version: "2012-10-17",
|
|
107
|
+
Statement: [{
|
|
108
|
+
Effect: "Allow",
|
|
109
|
+
Principal: {
|
|
110
|
+
Service: "lambda.amazonaws.com"
|
|
111
|
+
},
|
|
112
|
+
Action: "sts:AssumeRole"
|
|
113
|
+
}]
|
|
114
|
+
},
|
|
115
|
+
ManagedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
LambdaQueryFunction: {
|
|
119
|
+
Type: "AWS::Lambda::Function",
|
|
120
|
+
Properties: {
|
|
121
|
+
Code: {
|
|
122
|
+
S3Bucket: {
|
|
123
|
+
Ref: "LambdaS3Bucket"
|
|
124
|
+
},
|
|
125
|
+
S3Key: {
|
|
126
|
+
Ref: "LambdaS3Key"
|
|
127
|
+
},
|
|
128
|
+
S3ObjectVersion: {
|
|
129
|
+
Ref: "LambdaS3ObjectVersion"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
MemorySize: memorySize,
|
|
133
|
+
Timeout: timeout,
|
|
134
|
+
Handler: handler2,
|
|
135
|
+
Role: {
|
|
136
|
+
"Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
|
|
137
|
+
},
|
|
138
|
+
Runtime: "nodejs22.x",
|
|
139
|
+
Environment: {
|
|
140
|
+
Variables: {
|
|
141
|
+
DATABASE_HOST: {
|
|
142
|
+
Ref: "DatabaseHost"
|
|
143
|
+
},
|
|
144
|
+
DATABASE_HOST_READ_ONLY: {
|
|
145
|
+
Ref: "DatabaseHostReadOnly"
|
|
146
|
+
},
|
|
147
|
+
DATABASE_NAME: {
|
|
148
|
+
Ref: "DatabaseName"
|
|
149
|
+
},
|
|
150
|
+
DATABASE_USERNAME: {
|
|
151
|
+
Ref: "DatabaseUsername"
|
|
152
|
+
},
|
|
153
|
+
DATABASE_PASSWORD: {
|
|
154
|
+
Ref: "DatabasePassword"
|
|
155
|
+
},
|
|
156
|
+
DATABASE_PORT: {
|
|
157
|
+
Ref: "DatabasePort"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
VpcConfig: {
|
|
162
|
+
SecurityGroupIds: {
|
|
163
|
+
Ref: "SecurityGroupIds"
|
|
164
|
+
},
|
|
165
|
+
SubnetIds: {
|
|
166
|
+
Ref: "SubnetIds"
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
LambdaQueryFunctionLogs: {
|
|
172
|
+
Type: "AWS::Logs::LogGroup",
|
|
173
|
+
DependsOn: "LambdaQueryFunction",
|
|
174
|
+
Properties: {
|
|
175
|
+
LogGroupName: {
|
|
176
|
+
"Fn::Join": ["", ["/aws/lambda/", {
|
|
177
|
+
Ref: "LambdaQueryFunction"
|
|
178
|
+
}]]
|
|
179
|
+
},
|
|
180
|
+
RetentionInDays: 7
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
Outputs: {
|
|
185
|
+
LambdaPostgresQueryFunction: {
|
|
186
|
+
Description: "Lambda function to query PostgreSQL.",
|
|
187
|
+
Value: {
|
|
188
|
+
Ref: "LambdaQueryFunction"
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
LambdaPostgresQueryFunctionArn: {
|
|
192
|
+
Description: "Lambda function to query PostgreSQL ARN.",
|
|
193
|
+
Value: {
|
|
194
|
+
"Fn::GetAtt": ["LambdaQueryFunction", "Arn"]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
}, "createLambdaPostgresQueryTemplate");
|
|
200
|
+
|
|
201
|
+
// src/cloudformation/lambdaQueryHandler.ts
|
|
202
|
+
var import_pg = require("pg");
|
|
203
|
+
var database = process.env.DATABASE_NAME;
|
|
204
|
+
var username = process.env.DATABASE_USERNAME;
|
|
205
|
+
var password = process.env.DATABASE_PASSWORD;
|
|
206
|
+
var host = process.env.DATABASE_HOST;
|
|
207
|
+
var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
|
|
208
|
+
var port = process.env.DATABASE_PORT;
|
|
209
|
+
var handler = /* @__PURE__ */__name(async event => {
|
|
210
|
+
try {
|
|
211
|
+
const client = new import_pg.Client({
|
|
212
|
+
database,
|
|
213
|
+
user: username,
|
|
214
|
+
password,
|
|
215
|
+
host: event.readOnly && hostReadOnly ? hostReadOnly : host,
|
|
216
|
+
port: Number(port)
|
|
217
|
+
});
|
|
218
|
+
await client.connect();
|
|
219
|
+
try {
|
|
220
|
+
const res = await client.query(event);
|
|
221
|
+
return res;
|
|
222
|
+
} finally {
|
|
223
|
+
await client.end();
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error("Error running query", {
|
|
227
|
+
error,
|
|
228
|
+
event
|
|
229
|
+
});
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
}, "handler");
|
|
233
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
234
|
+
0 && (module.exports = {
|
|
235
|
+
HANDLER_DEFAULT,
|
|
236
|
+
MEMORY_SIZE_DEFAULT,
|
|
237
|
+
TIMEOUT_DEFAULT,
|
|
238
|
+
createLambdaPostgresQueryTemplate,
|
|
239
|
+
handler
|
|
240
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import { __name } from "../chunk-V4MHYKRI.js";
|
|
3
|
+
|
|
4
|
+
// src/cloudformation/createLambdaPostgresQueryTemplate.ts
|
|
5
|
+
var HANDLER_DEFAULT = "handler.handler";
|
|
6
|
+
var MEMORY_SIZE_DEFAULT = 128;
|
|
7
|
+
var TIMEOUT_DEFAULT = 30;
|
|
8
|
+
var createLambdaPostgresQueryTemplate = /* @__PURE__ */__name(({
|
|
9
|
+
handler: handler2 = HANDLER_DEFAULT,
|
|
10
|
+
memorySize = 128,
|
|
11
|
+
timeout = 30
|
|
12
|
+
} = {}) => {
|
|
13
|
+
return {
|
|
14
|
+
AWSTemplateFormatVersion: "2010-09-09",
|
|
15
|
+
Description: "A Lambda function to query PostgreSQL.",
|
|
16
|
+
Parameters: {
|
|
17
|
+
DatabaseHost: {
|
|
18
|
+
Type: "String",
|
|
19
|
+
Description: "Database host."
|
|
20
|
+
},
|
|
21
|
+
DatabaseHostReadOnly: {
|
|
22
|
+
Type: "String",
|
|
23
|
+
Description: "Database host read only."
|
|
24
|
+
},
|
|
25
|
+
DatabaseName: {
|
|
26
|
+
Type: "String",
|
|
27
|
+
Description: "Database name."
|
|
28
|
+
},
|
|
29
|
+
DatabaseUsername: {
|
|
30
|
+
Type: "String",
|
|
31
|
+
Description: "Database username."
|
|
32
|
+
},
|
|
33
|
+
DatabasePassword: {
|
|
34
|
+
Type: "String",
|
|
35
|
+
Description: "Database password."
|
|
36
|
+
},
|
|
37
|
+
DatabasePort: {
|
|
38
|
+
Type: "String",
|
|
39
|
+
Default: "5432",
|
|
40
|
+
Description: "Database port."
|
|
41
|
+
},
|
|
42
|
+
LambdaS3Bucket: {
|
|
43
|
+
Type: "String",
|
|
44
|
+
Description: "The S3 bucket where the Lambda code is stored."
|
|
45
|
+
},
|
|
46
|
+
LambdaS3Key: {
|
|
47
|
+
Type: "String",
|
|
48
|
+
Description: "The S3 key where the Lambda code is stored."
|
|
49
|
+
},
|
|
50
|
+
LambdaS3ObjectVersion: {
|
|
51
|
+
Type: "String",
|
|
52
|
+
Description: "The S3 object version of the Lambda code."
|
|
53
|
+
},
|
|
54
|
+
SecurityGroupIds: {
|
|
55
|
+
Description: "Security Group IDs",
|
|
56
|
+
Type: "List<AWS::EC2::SecurityGroup::Id>"
|
|
57
|
+
},
|
|
58
|
+
SubnetIds: {
|
|
59
|
+
Description: "Subnet IDs",
|
|
60
|
+
Type: "List<AWS::EC2::Subnet::Id>"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
Resources: {
|
|
64
|
+
LambdaQueryExecutionRole: {
|
|
65
|
+
Type: "AWS::IAM::Role",
|
|
66
|
+
Properties: {
|
|
67
|
+
AssumeRolePolicyDocument: {
|
|
68
|
+
Version: "2012-10-17",
|
|
69
|
+
Statement: [{
|
|
70
|
+
Effect: "Allow",
|
|
71
|
+
Principal: {
|
|
72
|
+
Service: "lambda.amazonaws.com"
|
|
73
|
+
},
|
|
74
|
+
Action: "sts:AssumeRole"
|
|
75
|
+
}]
|
|
76
|
+
},
|
|
77
|
+
ManagedPolicyArns: ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"]
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
LambdaQueryFunction: {
|
|
81
|
+
Type: "AWS::Lambda::Function",
|
|
82
|
+
Properties: {
|
|
83
|
+
Code: {
|
|
84
|
+
S3Bucket: {
|
|
85
|
+
Ref: "LambdaS3Bucket"
|
|
86
|
+
},
|
|
87
|
+
S3Key: {
|
|
88
|
+
Ref: "LambdaS3Key"
|
|
89
|
+
},
|
|
90
|
+
S3ObjectVersion: {
|
|
91
|
+
Ref: "LambdaS3ObjectVersion"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
MemorySize: memorySize,
|
|
95
|
+
Timeout: timeout,
|
|
96
|
+
Handler: handler2,
|
|
97
|
+
Role: {
|
|
98
|
+
"Fn::GetAtt": ["LambdaQueryExecutionRole", "Arn"]
|
|
99
|
+
},
|
|
100
|
+
Runtime: "nodejs22.x",
|
|
101
|
+
Environment: {
|
|
102
|
+
Variables: {
|
|
103
|
+
DATABASE_HOST: {
|
|
104
|
+
Ref: "DatabaseHost"
|
|
105
|
+
},
|
|
106
|
+
DATABASE_HOST_READ_ONLY: {
|
|
107
|
+
Ref: "DatabaseHostReadOnly"
|
|
108
|
+
},
|
|
109
|
+
DATABASE_NAME: {
|
|
110
|
+
Ref: "DatabaseName"
|
|
111
|
+
},
|
|
112
|
+
DATABASE_USERNAME: {
|
|
113
|
+
Ref: "DatabaseUsername"
|
|
114
|
+
},
|
|
115
|
+
DATABASE_PASSWORD: {
|
|
116
|
+
Ref: "DatabasePassword"
|
|
117
|
+
},
|
|
118
|
+
DATABASE_PORT: {
|
|
119
|
+
Ref: "DatabasePort"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
VpcConfig: {
|
|
124
|
+
SecurityGroupIds: {
|
|
125
|
+
Ref: "SecurityGroupIds"
|
|
126
|
+
},
|
|
127
|
+
SubnetIds: {
|
|
128
|
+
Ref: "SubnetIds"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
LambdaQueryFunctionLogs: {
|
|
134
|
+
Type: "AWS::Logs::LogGroup",
|
|
135
|
+
DependsOn: "LambdaQueryFunction",
|
|
136
|
+
Properties: {
|
|
137
|
+
LogGroupName: {
|
|
138
|
+
"Fn::Join": ["", ["/aws/lambda/", {
|
|
139
|
+
Ref: "LambdaQueryFunction"
|
|
140
|
+
}]]
|
|
141
|
+
},
|
|
142
|
+
RetentionInDays: 7
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
Outputs: {
|
|
147
|
+
LambdaPostgresQueryFunction: {
|
|
148
|
+
Description: "Lambda function to query PostgreSQL.",
|
|
149
|
+
Value: {
|
|
150
|
+
Ref: "LambdaQueryFunction"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
LambdaPostgresQueryFunctionArn: {
|
|
154
|
+
Description: "Lambda function to query PostgreSQL ARN.",
|
|
155
|
+
Value: {
|
|
156
|
+
"Fn::GetAtt": ["LambdaQueryFunction", "Arn"]
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}, "createLambdaPostgresQueryTemplate");
|
|
162
|
+
|
|
163
|
+
// src/cloudformation/lambdaQueryHandler.ts
|
|
164
|
+
import { Client } from "pg";
|
|
165
|
+
var database = process.env.DATABASE_NAME;
|
|
166
|
+
var username = process.env.DATABASE_USERNAME;
|
|
167
|
+
var password = process.env.DATABASE_PASSWORD;
|
|
168
|
+
var host = process.env.DATABASE_HOST;
|
|
169
|
+
var hostReadOnly = process.env.DATABASE_HOST_READ_ONLY;
|
|
170
|
+
var port = process.env.DATABASE_PORT;
|
|
171
|
+
var handler = /* @__PURE__ */__name(async event => {
|
|
172
|
+
try {
|
|
173
|
+
const client = new Client({
|
|
174
|
+
database,
|
|
175
|
+
user: username,
|
|
176
|
+
password,
|
|
177
|
+
host: event.readOnly && hostReadOnly ? hostReadOnly : host,
|
|
178
|
+
port: Number(port)
|
|
179
|
+
});
|
|
180
|
+
await client.connect();
|
|
181
|
+
try {
|
|
182
|
+
const res = await client.query(event);
|
|
183
|
+
return res;
|
|
184
|
+
} finally {
|
|
185
|
+
await client.end();
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error("Error running query", {
|
|
189
|
+
error,
|
|
190
|
+
event
|
|
191
|
+
});
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}, "handler");
|
|
195
|
+
export { HANDLER_DEFAULT, MEMORY_SIZE_DEFAULT, TIMEOUT_DEFAULT, createLambdaPostgresQueryTemplate, handler };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
import { __name } from "./chunk-V4MHYKRI.js";
|
|
3
|
+
|
|
4
|
+
// src/query.ts
|
|
5
|
+
import { InvokeCommand, LambdaClient } from "@aws-sdk/client-lambda";
|
|
6
|
+
import camelcaseKeys from "camelcase-keys";
|
|
7
|
+
var lambdaClient = new LambdaClient();
|
|
8
|
+
var textDecoder = new TextDecoder("utf-8");
|
|
9
|
+
var query = /* @__PURE__ */__name(async params => {
|
|
10
|
+
try {
|
|
11
|
+
const {
|
|
12
|
+
readOnly = true,
|
|
13
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
14
|
+
lambdaPostgresQueryFunction = process.env.LAMBDA_POSTGRES_QUERY_FUNCTION,
|
|
15
|
+
camelCaseKeys = true,
|
|
16
|
+
...pgParams
|
|
17
|
+
} = typeof params === "string" ? {
|
|
18
|
+
text: params
|
|
19
|
+
} : params;
|
|
20
|
+
const input = {
|
|
21
|
+
FunctionName: lambdaPostgresQueryFunction,
|
|
22
|
+
Payload: JSON.stringify({
|
|
23
|
+
readOnly,
|
|
24
|
+
...pgParams
|
|
25
|
+
})
|
|
26
|
+
};
|
|
27
|
+
const {
|
|
28
|
+
Payload
|
|
29
|
+
} = await lambdaClient.send(new InvokeCommand(input));
|
|
30
|
+
if (!Payload) {
|
|
31
|
+
console.error("No payload returned from lambda query", {
|
|
32
|
+
input
|
|
33
|
+
});
|
|
34
|
+
throw new Error("No payload returned from lambda query");
|
|
35
|
+
}
|
|
36
|
+
const data = textDecoder.decode(Payload);
|
|
37
|
+
const result = JSON.parse(data);
|
|
38
|
+
if ("errorType" in result) {
|
|
39
|
+
throw new Error(result.errorMessage);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...result,
|
|
43
|
+
rows: result.rows.map(row => {
|
|
44
|
+
if (!camelCaseKeys) {
|
|
45
|
+
return row;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
...row,
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
...camelcaseKeys(row, {
|
|
51
|
+
deep: true
|
|
52
|
+
})
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
};
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error("Error invoking lambda-postgres-query: ", error);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}, "query");
|
|
61
|
+
export { query };
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as pg from 'pg';
|
|
2
|
+
import { QueryConfig, QueryResultRow } from 'pg';
|
|
3
|
+
|
|
4
|
+
type QueryParams = {
|
|
5
|
+
readOnly?: boolean;
|
|
6
|
+
lambdaPostgresQueryFunction?: string;
|
|
7
|
+
camelCaseKeys?: boolean;
|
|
8
|
+
} & QueryConfig;
|
|
9
|
+
type LambdaError = {
|
|
10
|
+
errorType: 'Error';
|
|
11
|
+
errorMessage: string;
|
|
12
|
+
trace: string[];
|
|
13
|
+
};
|
|
14
|
+
declare const query: <Rows extends QueryResultRow = any>(params: QueryParams | string) => Promise<{
|
|
15
|
+
rows: any[];
|
|
16
|
+
command: string;
|
|
17
|
+
rowCount: number | null;
|
|
18
|
+
oid: number;
|
|
19
|
+
fields: pg.FieldDef[];
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export { type LambdaError, type QueryParams, query };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as pg from 'pg';
|
|
2
|
+
import { QueryConfig, QueryResultRow } from 'pg';
|
|
3
|
+
|
|
4
|
+
type QueryParams = {
|
|
5
|
+
readOnly?: boolean;
|
|
6
|
+
lambdaPostgresQueryFunction?: string;
|
|
7
|
+
camelCaseKeys?: boolean;
|
|
8
|
+
} & QueryConfig;
|
|
9
|
+
type LambdaError = {
|
|
10
|
+
errorType: 'Error';
|
|
11
|
+
errorMessage: string;
|
|
12
|
+
trace: string[];
|
|
13
|
+
};
|
|
14
|
+
declare const query: <Rows extends QueryResultRow = any>(params: QueryParams | string) => Promise<{
|
|
15
|
+
rows: any[];
|
|
16
|
+
command: string;
|
|
17
|
+
rowCount: number | null;
|
|
18
|
+
oid: number;
|
|
19
|
+
fields: pg.FieldDef[];
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
export { type LambdaError, type QueryParams, query };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __name = (target, value) => __defProp(target, "name", {
|
|
11
|
+
value,
|
|
12
|
+
configurable: true
|
|
13
|
+
});
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all) __defProp(target, name, {
|
|
16
|
+
get: all[name],
|
|
17
|
+
enumerable: true
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
23
|
+
get: () => from[key],
|
|
24
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return to;
|
|
28
|
+
};
|
|
29
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
30
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
31
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
32
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
33
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
34
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
35
|
+
value: mod,
|
|
36
|
+
enumerable: true
|
|
37
|
+
}) : target, mod));
|
|
38
|
+
var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
|
|
39
|
+
value: true
|
|
40
|
+
}), mod);
|
|
41
|
+
|
|
42
|
+
// src/index.ts
|
|
43
|
+
var index_exports = {};
|
|
44
|
+
__export(index_exports, {
|
|
45
|
+
query: () => query
|
|
46
|
+
});
|
|
47
|
+
module.exports = __toCommonJS(index_exports);
|
|
48
|
+
|
|
49
|
+
// src/query.ts
|
|
50
|
+
var import_client_lambda = require("@aws-sdk/client-lambda");
|
|
51
|
+
var import_camelcase_keys = __toESM(require("camelcase-keys"), 1);
|
|
52
|
+
var lambdaClient = new import_client_lambda.LambdaClient();
|
|
53
|
+
var textDecoder = new TextDecoder("utf-8");
|
|
54
|
+
var query = /* @__PURE__ */__name(async params => {
|
|
55
|
+
try {
|
|
56
|
+
const {
|
|
57
|
+
readOnly = true,
|
|
58
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
59
|
+
lambdaPostgresQueryFunction = process.env.LAMBDA_POSTGRES_QUERY_FUNCTION,
|
|
60
|
+
camelCaseKeys = true,
|
|
61
|
+
...pgParams
|
|
62
|
+
} = typeof params === "string" ? {
|
|
63
|
+
text: params
|
|
64
|
+
} : params;
|
|
65
|
+
const input = {
|
|
66
|
+
FunctionName: lambdaPostgresQueryFunction,
|
|
67
|
+
Payload: JSON.stringify({
|
|
68
|
+
readOnly,
|
|
69
|
+
...pgParams
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
const {
|
|
73
|
+
Payload
|
|
74
|
+
} = await lambdaClient.send(new import_client_lambda.InvokeCommand(input));
|
|
75
|
+
if (!Payload) {
|
|
76
|
+
console.error("No payload returned from lambda query", {
|
|
77
|
+
input
|
|
78
|
+
});
|
|
79
|
+
throw new Error("No payload returned from lambda query");
|
|
80
|
+
}
|
|
81
|
+
const data = textDecoder.decode(Payload);
|
|
82
|
+
const result = JSON.parse(data);
|
|
83
|
+
if ("errorType" in result) {
|
|
84
|
+
throw new Error(result.errorMessage);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
...result,
|
|
88
|
+
rows: result.rows.map(row => {
|
|
89
|
+
if (!camelCaseKeys) {
|
|
90
|
+
return row;
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
...row,
|
|
94
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
95
|
+
...(0, import_camelcase_keys.default)(row, {
|
|
96
|
+
deep: true
|
|
97
|
+
})
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("Error invoking lambda-postgres-query: ", error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}, "query");
|
|
106
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
107
|
+
0 && (module.exports = {
|
|
108
|
+
query
|
|
109
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/lambda-postgres-query",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.18",
|
|
4
4
|
"description": "Create a Lambda function that queries a PostgreSQL database.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ttoss",
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
"aws-sdk-client-mock": "^4.1.0",
|
|
41
41
|
"jest": "^30.2.0",
|
|
42
42
|
"tsup": "^8.5.1",
|
|
43
|
-
"@ttoss/
|
|
44
|
-
"@ttoss/
|
|
43
|
+
"@ttoss/config": "^1.35.12",
|
|
44
|
+
"@ttoss/test-utils": "^4.0.2"
|
|
45
45
|
},
|
|
46
46
|
"keywords": [
|
|
47
47
|
"aws",
|