data-api-client 1.3.0 → 2.0.0-beta.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 +126 -125
- package/index.js +51 -34
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/data-api-client)
|
|
5
5
|
[](https://www.npmjs.com/package/data-api-client)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## v2.0 BETA with support for AWS SDK v3
|
|
8
|
+
|
|
9
|
+
The **Data API Client** is a lightweight wrapper that simplifies working with the Amazon Aurora Serverless Data API by abstracting away the notion of field values. This abstraction annotates native JavaScript types supplied as input parameters, as well as converts annotated response data to native JavaScript types. It's basically a [DocumentClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB/DocumentClient.html) for the Data API. It also exposes simplified versions of the native AWS SDK v3 `RDSDataClient` methods to make working with `async/await` or Promise chains easier AND dramatically simplifies **transactions**.
|
|
8
10
|
|
|
9
11
|
For more information about the Aurora Serverless Data API, you can review the [official documentation](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) or read [Aurora Serverless Data API: An (updated) First Look](https://www.jeremydaly.com/aurora-serverless-data-api-a-first-look/) for some more insights on performance.
|
|
10
12
|
|
|
@@ -33,34 +35,31 @@ let result = await data.query(`SELECT * FROM myTable`)
|
|
|
33
35
|
// }
|
|
34
36
|
|
|
35
37
|
// SELECT with named parameters
|
|
36
|
-
let resultParams = await data.query(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
)
|
|
38
|
+
let resultParams = await data.query(`SELECT * FROM myTable WHERE id = :id`, {
|
|
39
|
+
id: 2
|
|
40
|
+
})
|
|
40
41
|
// { records: [ { id: 2, name: 'Mike', age: 52 } ] }
|
|
41
42
|
|
|
42
43
|
// INSERT with named parameters
|
|
43
|
-
let insert = await data.query(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
let insert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, {
|
|
45
|
+
name: 'Greg',
|
|
46
|
+
age: 18,
|
|
47
|
+
curls: false
|
|
48
|
+
})
|
|
47
49
|
|
|
48
50
|
// BATCH INSERT with named parameters
|
|
49
|
-
let batchInsert = await data.query(
|
|
50
|
-
|
|
51
|
-
[
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
[{ name: 'Bobby', age: 12, curls: false }]
|
|
57
|
-
]
|
|
58
|
-
)
|
|
51
|
+
let batchInsert = await data.query(`INSERT INTO myTable (name,age,has_curls) VALUES(:name,:age,:curls)`, [
|
|
52
|
+
[{ name: 'Marcia', age: 17, curls: false }],
|
|
53
|
+
[{ name: 'Peter', age: 15, curls: false }],
|
|
54
|
+
[{ name: 'Jan', age: 15, curls: false }],
|
|
55
|
+
[{ name: 'Cindy', age: 12, curls: true }],
|
|
56
|
+
[{ name: 'Bobby', age: 12, curls: false }]
|
|
57
|
+
])
|
|
59
58
|
// Update with named parameters
|
|
60
|
-
let update = await data.query(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
)
|
|
59
|
+
let update = await data.query(`UPDATE myTable SET age = :age WHERE id = :id`, {
|
|
60
|
+
age: 13,
|
|
61
|
+
id: 5
|
|
62
|
+
})
|
|
64
63
|
|
|
65
64
|
// Delete with named parameters
|
|
66
65
|
let remove = await data.query(
|
|
@@ -73,24 +72,19 @@ let custom = data.query({
|
|
|
73
72
|
sql: `SELECT * FROM myOtherTable WHERE id = :id AND active = :isActive`,
|
|
74
73
|
continueAfterTimeout: true,
|
|
75
74
|
database: 'myOtherDatabase',
|
|
76
|
-
parameters: [
|
|
77
|
-
{ id: 123},
|
|
78
|
-
{ name: 'isActive', value: { booleanValue: true } }
|
|
79
|
-
]
|
|
75
|
+
parameters: [{ id: 123 }, { name: 'isActive', value: { booleanValue: true } }]
|
|
80
76
|
})
|
|
81
77
|
```
|
|
82
78
|
|
|
83
79
|
## Why do I need this?
|
|
84
|
-
The [Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) requires you to specify data types when passing in parameters. The basic `INSERT` example above would look like this using the native `AWS.RDSDataService` class:
|
|
85
80
|
|
|
86
|
-
|
|
87
|
-
const AWS = require('aws-sdk')
|
|
88
|
-
const data = new AWS.RDSDataService()
|
|
81
|
+
The [Data API](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) requires you to specify data types when passing in parameters. The basic `INSERT` example above would look like this using the native `RDSDataClient` class:
|
|
89
82
|
|
|
90
|
-
|
|
83
|
+
```javascript
|
|
84
|
+
const { RDSDataClient, ExecuteStatementCommand } = require('@aws-sdk/client-rds-data')
|
|
85
|
+
const client = new RDSDataClient({ region: 'us-east-1' })
|
|
91
86
|
|
|
92
|
-
|
|
93
|
-
let insert = await data.executeStatement({
|
|
87
|
+
const params = {
|
|
94
88
|
secretArn: 'arn:aws:secretsmanager:us-east-1:XXXXXXXXXXXX:secret:mySecret',
|
|
95
89
|
resourceArn: 'arn:aws:rds:us-east-1:XXXXXXXXXXXX:cluster:my-cluster-name',
|
|
96
90
|
database: 'myDatabase',
|
|
@@ -100,10 +94,20 @@ let insert = await data.executeStatement({
|
|
|
100
94
|
{ name: 'age', value: { longValue: 10 } },
|
|
101
95
|
{ name: 'curls', value: { booleanValue: false } }
|
|
102
96
|
]
|
|
103
|
-
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const command = new ExecuteStatementCommand(params)
|
|
100
|
+
|
|
101
|
+
// async/await.
|
|
102
|
+
try {
|
|
103
|
+
const data = await client.send(command)
|
|
104
|
+
// process data.
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// error handling.
|
|
107
|
+
}
|
|
104
108
|
```
|
|
105
109
|
|
|
106
|
-
Specifying all of those data types in the parameters is a bit clunky. In addition to requiring types for parameters, it also returns each field as an object with its value assigned to a key that represents its data type, like this:
|
|
110
|
+
Specifying all of those data types in the parameters and writing all that SDK code is a bit clunky. In addition to requiring types for parameters, it also returns each field as an object with its value assigned to a key that represents its data type, like this:
|
|
107
111
|
|
|
108
112
|
```javascript
|
|
109
113
|
{ // id field
|
|
@@ -119,9 +123,11 @@ Specifying all of those data types in the parameters is a bit clunky. In additio
|
|
|
119
123
|
"booleanValue": false
|
|
120
124
|
}
|
|
121
125
|
```
|
|
126
|
+
|
|
122
127
|
Not only are there no column names, but you have to pull the value from the data type field. Lots of extra work that the **Data API Client** handles automatically for you. 😀
|
|
123
128
|
|
|
124
129
|
## Installation and Setup
|
|
130
|
+
|
|
125
131
|
```
|
|
126
132
|
npm i data-api-client
|
|
127
133
|
```
|
|
@@ -132,27 +138,25 @@ For more information on enabling Data API, see [Enabling Data API](#enabling-dat
|
|
|
132
138
|
|
|
133
139
|
Below is a table containing all of the possible configuration options for the `data-api-client`. Additional details are provided throughout the documentation.
|
|
134
140
|
|
|
135
|
-
| Property
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
|
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
|
|
|
145
|
-
| options | `object` | An *optional* configuration object that is passed directly into the RDSDataService constructor. See [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/RDSDataService.html#constructor-property) for available options. | `{}` |
|
|
146
|
-
| ~~region~~ (deprecated) | `string` | Set this in the `options` | |
|
|
147
|
-
| formatOptions | `object` | Formatting options to auto parse dates and coerce native JavaScript date objects to MySQL supported date formats. Valid keys are `deserializeDate` and `treatAsLocalDate`. Both accept boolean values. | Both `false` |
|
|
141
|
+
| Property | Type | Description | Default |
|
|
142
|
+
| ------------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
|
|
143
|
+
| resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is _required_, but can be overridden when querying. | |
|
|
144
|
+
| secretArn | `string` | The ARN of the secret associated with your database credentials. This is _required_, but can be overridden when querying. | |
|
|
145
|
+
| database | `string` | _Optional_ default database to use with queries. Can be overridden when querying. | |
|
|
146
|
+
| engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `mysql` |
|
|
147
|
+
| hydrateColumnNames | `boolean` | When `true`, results will be returned as objects with column names as keys. If `false`, results will be returned as an array of values. | `true` |
|
|
148
|
+
| options | `object` | An _optional_ configuration object that is passed directly into the v3 RDSDataClient constructor. Use this to set `region`, `tls`, etc. See [here](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/interfaces/rdsdataclientconfig.html) for available options. | `{}` |
|
|
149
|
+
| formatOptions | `object` | Formatting options to auto parse dates and coerce native JavaScript date objects to MySQL supported date formats. Valid keys are `deserializeDate` and `treatAsLocalDate`. Both accept boolean values. | Both `false` |
|
|
150
|
+
| wrapper | `function` | A custom wrapper around `@aws-sdk/client-rds-data` used to enable AWS X-Ray. | |
|
|
148
151
|
|
|
149
152
|
### Connection Reuse
|
|
153
|
+
|
|
150
154
|
It is recommended to enable connection reuse as this dramatically decreases the latency of subsequent calls to the AWS API. This can be done by setting an environment variable
|
|
151
155
|
`AWS_NODEJS_CONNECTION_REUSE_ENABLED=1`. For more information see the [AWS SDK documentation](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/node-reusing-connections.html).
|
|
152
156
|
|
|
153
157
|
## How to use this module
|
|
154
158
|
|
|
155
|
-
The **Data API Client** wraps the [
|
|
159
|
+
The **Data API Client** wraps the AWS SDK v3 [RDSDataClient Class](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-rds-data/index.html), providing you with a number of convenience features to make your workflow easier. The module also exposes simplified versions of all the native `RDSDataClient` methods, with your default configuration information already merged in. 😉
|
|
156
160
|
|
|
157
161
|
To use the Data API Client, require the module and instantiate it with your [Configuration options](#configuration-options). If you are using it with AWS Lambda, require it **OUTSIDE** your main handler function. This will allow you to reuse the initialized module on subsequent invocations.
|
|
158
162
|
|
|
@@ -166,6 +170,7 @@ const data = require('data-api-client')({
|
|
|
166
170
|
```
|
|
167
171
|
|
|
168
172
|
### Running a query
|
|
173
|
+
|
|
169
174
|
Once initialized, running a query is super simple. Use the `query()` method and pass in your SQL statement:
|
|
170
175
|
|
|
171
176
|
```javascript
|
|
@@ -173,8 +178,9 @@ let result = await data.query(`SELECT * FROM myTable`)
|
|
|
173
178
|
```
|
|
174
179
|
|
|
175
180
|
By default, this will return your rows as an array of objects with column names as property names:
|
|
181
|
+
|
|
176
182
|
```javascript
|
|
177
|
-
[
|
|
183
|
+
;[
|
|
178
184
|
{ id: 1, name: 'Alice', age: null },
|
|
179
185
|
{ id: 2, name: 'Mike', age: 52 },
|
|
180
186
|
{ id: 3, name: 'Carol', age: 50 }
|
|
@@ -184,23 +190,22 @@ By default, this will return your rows as an array of objects with column names
|
|
|
184
190
|
To query with parameters, you can use named parameters in your SQL, and then provider an object containing your parameters as the second argument to the `query()` method:
|
|
185
191
|
|
|
186
192
|
```javascript
|
|
187
|
-
let result = await data.query(
|
|
193
|
+
let result = await data.query(
|
|
194
|
+
`
|
|
188
195
|
SELECT * FROM myTable WHERE id = :id AND created > :createDate`,
|
|
189
196
|
{ id: 2, createDate: '2019-06-01' }
|
|
190
197
|
)
|
|
191
198
|
```
|
|
192
199
|
|
|
193
|
-
The Data API Client will automatically convert your parameters into the correct Data API parameter format using native JavaScript types. If you prefer to use the clunky format, or you need more control over the data type, you can just pass in the `
|
|
200
|
+
The Data API Client will automatically convert your parameters into the correct Data API parameter format using native JavaScript types. If you prefer to use the clunky format, or you need more control over the data type, you can just pass in the `RDSDataClient` format:
|
|
194
201
|
|
|
195
202
|
```javascript
|
|
196
|
-
let result = await data.query(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
]
|
|
203
|
-
)
|
|
203
|
+
let result = await data.query(`SELECT * FROM myTable WHERE id = :id AND created > :createDate`, [
|
|
204
|
+
// An array of objects is totally cool, too. We'll merge them for you.
|
|
205
|
+
{ id: 2 },
|
|
206
|
+
// Data API Client just passes this straight on through
|
|
207
|
+
{ name: 'createDate', value: { blobValue: new Buffer('2019-06-01') } }
|
|
208
|
+
])
|
|
204
209
|
```
|
|
205
210
|
|
|
206
211
|
If you want even more control, you can pass in an `object` as the first parameter. This will allow you to add additional configuration options and override defaults as well.
|
|
@@ -210,35 +215,34 @@ let result = await data.query({
|
|
|
210
215
|
sql: `SELECT * FROM myTable WHERE id = :id`,
|
|
211
216
|
parameters: [ { id: 2 } ], // or just { id: 2 }
|
|
212
217
|
database: 'someOtherDatabase', // override default database
|
|
213
|
-
schema: 'mySchema', //
|
|
214
|
-
continueAfterTimeout: true, //
|
|
215
|
-
includeResultMetadata: true, //
|
|
218
|
+
schema: 'mySchema', // RDSDataClient config option
|
|
219
|
+
continueAfterTimeout: true, // RDSDataClient config option (non-batch only)
|
|
220
|
+
includeResultMetadata: true, // RDSDataClient config option (non-batch only)
|
|
216
221
|
hydrateColumnNames: false, // Returns each record as an arrays of values
|
|
217
|
-
transactionId: 'AQC5SRDIm...ZHXP/WORU=' //
|
|
222
|
+
transactionId: 'AQC5SRDIm...ZHXP/WORU=' // RDSDataClient config option
|
|
218
223
|
}
|
|
219
224
|
```
|
|
220
225
|
|
|
221
|
-
Sometimes you might want to have
|
|
226
|
+
Sometimes you might want to have _dynamic identifiers_ in your SQL statements. Unfortunately, the `RDSDataClient` doesn't do this, but the **Data API Client** does! We're using the [sqlstring](https://github.com/mysqljs/sqlstring) module under the hood, so as long as [NO_BACKSLASH_ESCAPES](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_no_backslash_escapes) SQL mode is disabled (which is the default state for Aurora Serverless), you're good to go. Use a double colon (`::`) prefix to create _named identifiers_ and you can do cool things like this:
|
|
222
227
|
|
|
223
228
|
```javascript
|
|
224
|
-
let result = await data.query(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
id: 1
|
|
230
|
-
}
|
|
231
|
-
)
|
|
229
|
+
let result = await data.query(`SELECT ::fields FROM ::table WHERE id > :id`, {
|
|
230
|
+
fields: ['id', 'name', 'created'],
|
|
231
|
+
table: 'table_' + someScaryUserInput, // someScaryUserInput = 123abc
|
|
232
|
+
id: 1
|
|
233
|
+
})
|
|
232
234
|
```
|
|
233
235
|
|
|
234
236
|
Which will produce a query like this:
|
|
237
|
+
|
|
235
238
|
```sql
|
|
236
239
|
SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id LIMIT 10
|
|
237
240
|
```
|
|
238
241
|
|
|
239
|
-
You'll notice that we leave the
|
|
242
|
+
You'll notice that we leave the _named parameters_ alone. Anything that Data API and the `RDSDataClient` Class currently handles, we defer to them.
|
|
240
243
|
|
|
241
244
|
### Type-Casting
|
|
245
|
+
|
|
242
246
|
The Aurora Data API can sometimes give you trouble with certain data types, such as uuid, unless you explicitly cast them. While you can certainly do this manually in your SQL string, the Data API Client offers a really easy way to handle this for you.
|
|
243
247
|
|
|
244
248
|
```javascript
|
|
@@ -268,28 +272,28 @@ const result = await data.query(
|
|
|
268
272
|
```
|
|
269
273
|
|
|
270
274
|
### Batch Queries
|
|
271
|
-
|
|
275
|
+
|
|
276
|
+
The `RDSDataClient` Class provides a `batchExecuteStatement` method that allows you to execute a prepared statement multiple times using different parameter sets. This is only allowed for `INSERT`, `UPDATE` and `DELETE` queries, but is much more efficient than issuing multiple `executeStatement` calls. The Data API Client handles the switching for you based on _how_ you send in your parameters.
|
|
272
277
|
|
|
273
278
|
To issue a batch query, use the `query()` method (either by passing an object or using the two arity form), and provide multiple parameter sets as nested arrays. For example, if you wanted to update multiple records at once, your query might look like this:
|
|
274
279
|
|
|
275
280
|
```javascript
|
|
276
|
-
let result = await data.query(
|
|
277
|
-
|
|
278
|
-
[
|
|
279
|
-
|
|
280
|
-
[ { id: 7, newName: 'Jan Glass' } ]
|
|
281
|
-
]
|
|
282
|
-
)
|
|
281
|
+
let result = await data.query(`UPDATE myTable SET name = :newName WHERE id = :id`, [
|
|
282
|
+
[{ id: 1, newName: 'Alice Franklin' }],
|
|
283
|
+
[{ id: 7, newName: 'Jan Glass' }]
|
|
284
|
+
])
|
|
283
285
|
```
|
|
284
286
|
|
|
285
|
-
You can also use
|
|
287
|
+
You can also use _named identifiers_ in batch queries, which will update and escape your SQL statement. **ONLY** parameters from the first parameter set will be used to update the query. Subsequent parameter sets will only update _named parameters_ supported by the Data API.
|
|
286
288
|
|
|
287
289
|
Whenever a batch query is executed, it returns an `updateResults` field. Other than for `INSERT` statements, however, there is no useful feedback provided by this field.
|
|
288
290
|
|
|
289
291
|
### Retrieving Insert IDs
|
|
292
|
+
|
|
290
293
|
The Data API returns a `generatedFields` array that contains the value of auto-incrementing primary keys. If this value is returned, the Data API Client will parse this and return it as the `insertId`. This also works for batch queries as well.
|
|
291
294
|
|
|
292
295
|
## Transaction Support
|
|
296
|
+
|
|
293
297
|
Transaction support in the Data API Client has been dramatically simplified. Start a new transaction using the `transaction()` method, and then chain queries using the `query()` method. The `query()` method supports all standard query options. Alternatively, you can specify a function as the only argument in a `query()` method call and return the arguments as an array of values. The function receives two arguments, the result of the last query executed, and an array containing all the previous query results. This is useful if you need values from a previous query as part of your transaction.
|
|
294
298
|
|
|
295
299
|
You can specify an optional `rollback()` method in the chain. This will receive the `error` object and the `transactionStatus` object, allowing you to add additional logging or perform some other action. Call the `commit()` method when you are ready to execute the queries.
|
|
@@ -305,10 +309,13 @@ let results = await mysql.transaction()
|
|
|
305
309
|
With a function to get the `insertId` from the previous query:
|
|
306
310
|
|
|
307
311
|
```javascript
|
|
308
|
-
let results = await mysql
|
|
312
|
+
let results = await mysql
|
|
313
|
+
.transaction()
|
|
309
314
|
.query('INSERT INTO myTable (name) VALUES(:name)', { name: 'Tiger' })
|
|
310
|
-
.query((r) => [
|
|
311
|
-
.rollback((e,status) => {
|
|
315
|
+
.query((r) => ['UPDATE myTable SET age = :age WHERE id = :id', { age: 4, id: r.insertId }])
|
|
316
|
+
.rollback((e, status) => {
|
|
317
|
+
/* do something with the error */
|
|
318
|
+
}) // optional
|
|
312
319
|
.commit() // execute the queries
|
|
313
320
|
```
|
|
314
321
|
|
|
@@ -318,7 +325,8 @@ By default, the `transaction()` method will use the `resourceArn`, `secretArn` a
|
|
|
318
325
|
|
|
319
326
|
### Using native methods directly
|
|
320
327
|
|
|
321
|
-
The Data API Client exposes
|
|
328
|
+
The Data API Client exposes simplified versions of the five `RDSDataClient` methods. These are:
|
|
329
|
+
|
|
322
330
|
- `batchExecuteStatement`
|
|
323
331
|
- `beginTransaction`
|
|
324
332
|
- `commitTransaction`
|
|
@@ -337,35 +345,28 @@ let result = await data.executeStatement({
|
|
|
337
345
|
)
|
|
338
346
|
```
|
|
339
347
|
|
|
340
|
-
## Custom
|
|
348
|
+
## Custom SDK Wrapper
|
|
341
349
|
|
|
342
|
-
`data-api-client` allows
|
|
350
|
+
`data-api-client` allows you to wrap the instance of `RDSDataClient` to enable additional features like AWS X-Ray. You can specify a function using the optional `wrapper` paramenter. The function will receive one parameter which contains an instantiated `RDSDataClient` instance.
|
|
343
351
|
|
|
344
352
|
```javascript
|
|
345
|
-
//
|
|
346
|
-
const
|
|
347
|
-
AWS: customAWS,
|
|
348
|
-
...
|
|
349
|
-
})
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
Custom AWS parameter allows to introduce, e.g. tracing Data API calls through X-Ray with:
|
|
353
|
-
|
|
354
|
-
```javascript
|
|
355
|
-
const AWSXRay = require('aws-xray-sdk')
|
|
356
|
-
const AWS = AWSXRay.captureAWS(require('aws-sdk'))
|
|
353
|
+
// Import X-Ray
|
|
354
|
+
const AWSXRay = require('aws-xray-sdk');
|
|
357
355
|
|
|
356
|
+
// Instantiate data-api-client with the AWSXRay.captureAWSv3Client wrapper
|
|
358
357
|
const data = require('data-api-client')({
|
|
359
|
-
|
|
358
|
+
wrapper: AWSXRay.captureAWSv3Client,
|
|
360
359
|
...
|
|
361
360
|
})
|
|
362
361
|
```
|
|
363
362
|
|
|
364
363
|
## Data API Limitations / Wonkiness
|
|
365
|
-
|
|
364
|
+
|
|
365
|
+
The first GA release of the Data API has _a lot_ of promise, unfortunately, there are still quite a few things that make it a bit wonky and may require you to implement some workarounds. I've outlined some of my findings below.
|
|
366
366
|
|
|
367
367
|
### You can't send in an array of values
|
|
368
|
-
|
|
368
|
+
|
|
369
|
+
The GitHub repo for RDSDataClient mentions something about `arrayValues`, but I've been unable to get arrays (including TypedArrays and Buffers) to be used for parameters with `IN` clauses. For example, the following query will **NOT** work:
|
|
369
370
|
|
|
370
371
|
```javascript
|
|
371
372
|
let result = await data.executeStatement({
|
|
@@ -382,9 +383,11 @@ let result = await data.executeStatement({
|
|
|
382
383
|
I'm using `blobValue` because it's the only generic value field. You could send it in as a string, but then it only uses the first value. Hopefully they will add an `arrayValues` or something similar to support this in the future.
|
|
383
384
|
|
|
384
385
|
### ~~Named parameters MUST be sent in order~~
|
|
385
|
-
|
|
386
|
+
|
|
387
|
+
~~Read that again if you need to. So parameters have to be **BOTH** named and _in order_, otherwise the query **may** fail. I stress **may**, because if you send in two fields of compatible type in the wrong order, the query will work, just with your values flipped. 🤦🏻♂️ Watch out for this one.~~ 👈This was fixed!
|
|
386
388
|
|
|
387
389
|
### You can't parameterize identifiers
|
|
390
|
+
|
|
388
391
|
If you want to use dynamic column or field names, there is no way to do it automatically with the Data API. The `mysql` package, for example, lets you use `??` to dynamically insert escaped identifiers. Something like the example below is currently not possible.
|
|
389
392
|
|
|
390
393
|
```javascript
|
|
@@ -403,18 +406,19 @@ let result = await data.executeStatement({
|
|
|
403
406
|
|
|
404
407
|
No worries! The Data API Client gives you the ability to parameterize identifiers and auto escape them. Just use a double colon (`::`) to prefix your named identifiers.
|
|
405
408
|
|
|
406
|
-
|
|
407
409
|
### Batch statements do not give you updated record counts
|
|
410
|
+
|
|
408
411
|
This one is a bit frustrating. If you execute a standard `executeStatement`, then it will return a `numberOfRecordsUpdated` field for `UPDATE` and `DELETE` queries. This is handy for knowing if your query succeeded. Unfortunately, a `batchExecuteStatement` does not return this field for you.
|
|
409
412
|
|
|
410
413
|
## Enabling Data API
|
|
414
|
+
|
|
411
415
|
In order to use the Data API, you must enable it on your Aurora Serverless Cluster and create a Secret. You also must grant your execution environment a number of permission as outlined in the following sections.
|
|
412
416
|
|
|
413
417
|
### Enable Data API on your Aurora Serverless Cluster
|
|
414
418
|
|
|
415
419
|

|
|
416
420
|
|
|
417
|
-
You need to modify your Aurora Serverless cluster by clicking “ACTIONS” and then “Modify Cluster”. Just check the Data API box in the
|
|
421
|
+
You need to modify your Aurora Serverless cluster by clicking “ACTIONS” and then “Modify Cluster”. Just check the Data API box in the _Network & Security_ section and you’re good to go. Remember that your Aurora Serverless cluster still runs in a VPC, even though you don’t need to run your Lambdas in a VPC to access it via the Data API.
|
|
418
422
|
|
|
419
423
|
### Set up a secret in the Secrets Manager
|
|
420
424
|
|
|
@@ -422,7 +426,6 @@ Next you need to set up a secret in the Secrets Manager. This is actually quite
|
|
|
422
426
|
|
|
423
427
|

|
|
424
428
|
|
|
425
|
-
|
|
426
429
|
Next we give it a name, this is important, because this will be part of the arn when we set up permissions later. You can give it a description as well so you don’t forget what this secret is about when you look at it in a few weeks.
|
|
427
430
|
|
|
428
431
|

|
|
@@ -436,24 +439,26 @@ You can then configure your rotation settings, if you want, and then you review
|
|
|
436
439
|
In order to use the Data API, your execution environment requires several IAM permissions. Below are the minimum permissions required. **Please Note:** The `Resource: "*"` permission for `rds-data` is recommended by AWS (see [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/list_amazonrdsdataapi.html#amazonrdsdataapi-resources-for-iam-policies)) because Amazon RDS Data API does not support specifying a resource ARN. The credentials specified in Secrets Manager can be used to restrict access to specific databases.
|
|
437
440
|
|
|
438
441
|
**YAML:**
|
|
442
|
+
|
|
439
443
|
```yaml
|
|
440
444
|
Statement:
|
|
441
|
-
- Effect:
|
|
445
|
+
- Effect: 'Allow'
|
|
442
446
|
Action:
|
|
443
|
-
-
|
|
444
|
-
-
|
|
445
|
-
-
|
|
446
|
-
-
|
|
447
|
-
-
|
|
448
|
-
-
|
|
449
|
-
Resource:
|
|
450
|
-
- Effect:
|
|
447
|
+
- 'rds-data:ExecuteSql'
|
|
448
|
+
- 'rds-data:ExecuteStatement'
|
|
449
|
+
- 'rds-data:BatchExecuteStatement'
|
|
450
|
+
- 'rds-data:BeginTransaction'
|
|
451
|
+
- 'rds-data:RollbackTransaction'
|
|
452
|
+
- 'rds-data:CommitTransaction'
|
|
453
|
+
Resource: '*'
|
|
454
|
+
- Effect: 'Allow'
|
|
451
455
|
Action:
|
|
452
|
-
-
|
|
453
|
-
Resource:
|
|
456
|
+
- 'secretsmanager:GetSecretValue'
|
|
457
|
+
Resource: 'arn:aws:secretsmanager:{REGION}:{ACCOUNT-ID}:secret:{PATH-TO-SECRET}/*'
|
|
454
458
|
```
|
|
455
459
|
|
|
456
460
|
**JSON:**
|
|
461
|
+
|
|
457
462
|
```javascript
|
|
458
463
|
"Statement" : [
|
|
459
464
|
{
|
|
@@ -476,10 +481,6 @@ Statement:
|
|
|
476
481
|
]
|
|
477
482
|
```
|
|
478
483
|
|
|
479
|
-
## Sponsors
|
|
480
|
-
|
|
481
|
-
[](https://ad.doubleclick.net/ddm/trackclk/N1116303.3950900PODSEARCH.COM/B24770737.285235234;dc_trk_aid=479074825;dc_trk_cid=139488579;dc_lat=;dc_rdid=;tag_for_child_directed_treatment=;tfua=;gdpr=${GDPR};gdpr_consent=${GDPR_CONSENT_755})
|
|
482
|
-
<IMG SRC="https://ad.doubleclick.net/ddm/trackimp/N1116303.3950900PODSEARCH.COM/B24770737.285235234;dc_trk_aid=479074825;dc_trk_cid=139488579;ord=[timestamp];dc_lat=;dc_rdid=;tag_for_child_directed_treatment=;tfua=;gdpr=${GDPR};gdpr_consent=${GDPR_CONSENT_755}?" BORDER="0" HEIGHT="1" WIDTH="1" ALT="Advertisement">
|
|
483
|
-
|
|
484
484
|
## Contributions
|
|
485
|
+
|
|
485
486
|
Contributions, ideas and bug reports are welcome and greatly appreciated. Please add [issues](https://github.com/jeremydaly/data-api-client/issues) for suggestions and bug reports or create a pull request. You can also contact me on Twitter: [@jeremy_daly](https://twitter.com/jeremy_daly).
|
package/index.js
CHANGED
|
@@ -8,13 +8,20 @@
|
|
|
8
8
|
* https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html
|
|
9
9
|
*
|
|
10
10
|
* @author Jeremy Daly <jeremy@jeremydaly.com>
|
|
11
|
-
* @version
|
|
11
|
+
* @version 2.0.0-beta
|
|
12
12
|
* @license MIT
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// Require the aws-sdk. This is a dev dependency, so if being used
|
|
16
16
|
// outside of a Lambda execution environment, it must be manually installed.
|
|
17
|
-
const
|
|
17
|
+
const {
|
|
18
|
+
RDSDataClient,
|
|
19
|
+
ExecuteStatementCommand,
|
|
20
|
+
BatchExecuteStatementCommand,
|
|
21
|
+
BeginTransactionCommand,
|
|
22
|
+
CommitTransactionCommand,
|
|
23
|
+
RollbackTransactionCommand
|
|
24
|
+
} = require('@aws-sdk/client-rds-data')
|
|
18
25
|
|
|
19
26
|
// Require sqlstring to add additional escaping capabilities
|
|
20
27
|
const sqlString = require('sqlstring')
|
|
@@ -427,16 +434,16 @@ const query = async function (config, ..._args) {
|
|
|
427
434
|
|
|
428
435
|
// Capture the result for debugging
|
|
429
436
|
let result = await (isBatch
|
|
430
|
-
? config.
|
|
431
|
-
: config.
|
|
437
|
+
? config.methods.batchExecuteStatement(params)
|
|
438
|
+
: config.methods.executeStatement(params))
|
|
432
439
|
|
|
433
440
|
// Format and return the results
|
|
434
441
|
return formatResults(result, hydrateColumnNames, args[0].includeResultMetadata === true, formatOptions)
|
|
435
442
|
} catch (e) {
|
|
436
443
|
if (this && this.rollback) {
|
|
437
|
-
let rollback = await config.
|
|
444
|
+
let rollback = await config.methods.rollbackTransaction(
|
|
438
445
|
pick(params, ['resourceArn', 'secretArn', 'transactionId'])
|
|
439
|
-
)
|
|
446
|
+
)
|
|
440
447
|
|
|
441
448
|
this.rollback(e, rollback)
|
|
442
449
|
}
|
|
@@ -459,7 +466,7 @@ const transaction = (config, _args) => {
|
|
|
459
466
|
database: parseDatabase(config, args), // add database
|
|
460
467
|
hydrateColumnNames: parseHydrate(config, args), // add hydrate
|
|
461
468
|
formatOptions: parseFormatOptions(config, args), // add formatOptions
|
|
462
|
-
|
|
469
|
+
methods: config.methods // pass the methods
|
|
463
470
|
})
|
|
464
471
|
|
|
465
472
|
return {
|
|
@@ -488,9 +495,9 @@ const commit = async (config, queries, rollback) => {
|
|
|
488
495
|
let results = [] // keep track of results
|
|
489
496
|
|
|
490
497
|
// Start a transaction
|
|
491
|
-
const { transactionId } = await config.
|
|
498
|
+
const { transactionId } = await config.methods.beginTransaction(
|
|
492
499
|
pick(config, ['resourceArn', 'secretArn', 'database'])
|
|
493
|
-
)
|
|
500
|
+
)
|
|
494
501
|
|
|
495
502
|
// Add transactionId to the config
|
|
496
503
|
let txConfig = Object.assign(config, { transactionId })
|
|
@@ -504,9 +511,9 @@ const commit = async (config, queries, rollback) => {
|
|
|
504
511
|
}
|
|
505
512
|
|
|
506
513
|
// Commit our transaction
|
|
507
|
-
const { transactionStatus } = await txConfig.
|
|
514
|
+
const { transactionStatus } = await txConfig.methods.commitTransaction(
|
|
508
515
|
pick(config, ['resourceArn', 'secretArn', 'transactionId'])
|
|
509
|
-
)
|
|
516
|
+
)
|
|
510
517
|
|
|
511
518
|
// Add the transaction status to the results
|
|
512
519
|
results.push({ transactionStatus })
|
|
@@ -535,9 +542,6 @@ const commit = async (config, queries, rollback) => {
|
|
|
535
542
|
* @param {object} [params.formatOptions] Date-related formatting options
|
|
536
543
|
* @param {boolean} [params.formatOptions.deserializeDate=false]
|
|
537
544
|
* @param {boolean} [params.formatOptions.treatAsLocalDate=false]
|
|
538
|
-
* @param {boolean} [params.keepAlive] DEPRECATED
|
|
539
|
-
* @param {boolean} [params.sslEnabled=true] DEPRECATED
|
|
540
|
-
* @param {string} [params.region] DEPRECATED
|
|
541
545
|
*
|
|
542
546
|
*/
|
|
543
547
|
const init = (params) => {
|
|
@@ -554,11 +558,6 @@ const init = (params) => {
|
|
|
554
558
|
options.region = params.region
|
|
555
559
|
}
|
|
556
560
|
|
|
557
|
-
// Disable ssl if wanted for local development
|
|
558
|
-
if (params.sslEnabled === false) {
|
|
559
|
-
options.sslEnabled = false
|
|
560
|
-
}
|
|
561
|
-
|
|
562
561
|
// Set the configuration for this instance
|
|
563
562
|
const config = {
|
|
564
563
|
// Require engine
|
|
@@ -594,9 +593,38 @@ const init = (params) => {
|
|
|
594
593
|
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
595
594
|
},
|
|
596
595
|
|
|
597
|
-
// TODO: Put this in a separate module for testing?
|
|
598
596
|
// Create an instance of RDSDataService
|
|
599
|
-
|
|
597
|
+
client: params.wrapper ? params.wrapper(new RDSDataClient(options)) : new RDSDataClient(options),
|
|
598
|
+
|
|
599
|
+
// Create simplified v3 RDSDataClient method
|
|
600
|
+
methods: {
|
|
601
|
+
executeStatement: async (args) => {
|
|
602
|
+
const command = new ExecuteStatementCommand(
|
|
603
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
604
|
+
)
|
|
605
|
+
return await config.client.send(command)
|
|
606
|
+
},
|
|
607
|
+
batchExecuteStatement: async (args) => {
|
|
608
|
+
const command = new BatchExecuteStatementCommand(
|
|
609
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
610
|
+
)
|
|
611
|
+
return await config.client.send(command)
|
|
612
|
+
},
|
|
613
|
+
beginTransaction: async (args) => {
|
|
614
|
+
const command = new BeginTransactionCommand(
|
|
615
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
616
|
+
)
|
|
617
|
+
return await config.client.send(command)
|
|
618
|
+
},
|
|
619
|
+
commitTransaction: async (args) => {
|
|
620
|
+
const command = new CommitTransactionCommand(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args))
|
|
621
|
+
return await config.client.send(command)
|
|
622
|
+
},
|
|
623
|
+
rollbackTransaction: async (args) => {
|
|
624
|
+
const command = new RollbackTransactionCommand(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args))
|
|
625
|
+
return await config.client.send(command)
|
|
626
|
+
}
|
|
627
|
+
}
|
|
600
628
|
} // end config
|
|
601
629
|
|
|
602
630
|
// Return public methods
|
|
@@ -606,19 +634,8 @@ const init = (params) => {
|
|
|
606
634
|
// Transaction method, pass config and parameters
|
|
607
635
|
transaction: (x) => transaction(config, x),
|
|
608
636
|
|
|
609
|
-
// Export
|
|
610
|
-
|
|
611
|
-
config.RDS.batchExecuteStatement(
|
|
612
|
-
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
613
|
-
).promise(),
|
|
614
|
-
beginTransaction: (args) =>
|
|
615
|
-
config.RDS.beginTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(),
|
|
616
|
-
commitTransaction: (args) =>
|
|
617
|
-
config.RDS.commitTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise(),
|
|
618
|
-
executeStatement: (args) =>
|
|
619
|
-
config.RDS.executeStatement(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(),
|
|
620
|
-
rollbackTransaction: (args) =>
|
|
621
|
-
config.RDS.rollbackTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise()
|
|
637
|
+
// Export simplified v3 RDSDataClient methods
|
|
638
|
+
...config.methods
|
|
622
639
|
}
|
|
623
640
|
} // end exports
|
|
624
641
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-api-client",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
4
4
|
"description": "A lightweight wrapper that simplifies working with the Amazon Aurora Serverless Data API",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -25,14 +25,13 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/jeremydaly/data-api-client#readme",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"aws-sdk": "^2.811.0",
|
|
29
28
|
"eslint": "^8.12.0",
|
|
30
29
|
"eslint-config-prettier": "^8.5.0",
|
|
31
30
|
"jest": "^27.5.1",
|
|
32
|
-
"prettier": "^2.6.2",
|
|
33
31
|
"rewire": "^6.0.0"
|
|
34
32
|
},
|
|
35
33
|
"dependencies": {
|
|
34
|
+
"@aws-sdk/client-rds-data": "^3.58.0",
|
|
36
35
|
"sqlstring": "^2.3.2"
|
|
37
36
|
},
|
|
38
37
|
"files": [
|