data-api-client 1.1.0 → 1.3.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 +72 -8
- package/index.js +321 -266
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -24,16 +24,20 @@ const data = require('data-api-client')({
|
|
|
24
24
|
|
|
25
25
|
// Simple SELECT
|
|
26
26
|
let result = await data.query(`SELECT * FROM myTable`)
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
27
|
+
// {
|
|
28
|
+
// records: [
|
|
29
|
+
// { id: 1, name: 'Alice', age: null },
|
|
30
|
+
// { id: 2, name: 'Mike', age: 52 },
|
|
31
|
+
// { id: 3, name: 'Carol', age: 50 }
|
|
32
|
+
// ]
|
|
33
|
+
// }
|
|
30
34
|
|
|
31
35
|
// SELECT with named parameters
|
|
32
36
|
let resultParams = await data.query(
|
|
33
37
|
`SELECT * FROM myTable WHERE id = :id`,
|
|
34
38
|
{ id: 2 }
|
|
35
39
|
)
|
|
36
|
-
// [ { id: 2, name: 'Mike', age: 52 } ]
|
|
40
|
+
// { records: [ { id: 2, name: 'Mike', age: 52 } ] }
|
|
37
41
|
|
|
38
42
|
// INSERT with named parameters
|
|
39
43
|
let insert = await data.query(
|
|
@@ -130,9 +134,11 @@ Below is a table containing all of the possible configuration options for the `d
|
|
|
130
134
|
|
|
131
135
|
| Property | Type | Description | Default |
|
|
132
136
|
| -------- | ---- | ----------- | ------- |
|
|
137
|
+
| AWS | `AWS` | A custom `aws-sdk` instance | |
|
|
133
138
|
| resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is *required*, but can be overridden when querying. | |
|
|
134
139
|
| secretArn | `string` | The ARN of the secret associated with your database credentials. This is *required*, but can be overridden when querying. | |
|
|
135
140
|
| database | `string` | *Optional* default database to use with queries. Can be overridden when querying. | |
|
|
141
|
+
| engine | `mysql` or `pg` | The type of database engine you're connecting to (MySQL or Postgres). | `mysql` |
|
|
136
142
|
| 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` |
|
|
137
143
|
| ~~keepAlive~~ (deprecated) | `boolean` | See [Connection Reuse](#connection-reuse) below. | |
|
|
138
144
|
| ~~sslEnabled~~ (deprecated) | `boolean` | Set this in the `options` | `true` |
|
|
@@ -232,6 +238,35 @@ SELECT `id`, `name`, `created` FROM `table_123abc` WHERE id > :id LIMIT 10
|
|
|
232
238
|
|
|
233
239
|
You'll notice that we leave the *named parameters* alone. Anything that Data API and the `RDSDataService` Class currently handles, we defer to them.
|
|
234
240
|
|
|
241
|
+
### Type-Casting
|
|
242
|
+
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
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
const result = await data.query(
|
|
246
|
+
'INSERT INTO users(id, email, full_name, metadata) VALUES(:id, :email, :fullName, :metadata)',
|
|
247
|
+
[
|
|
248
|
+
{
|
|
249
|
+
name: 'id',
|
|
250
|
+
value: newUserId,
|
|
251
|
+
cast: 'uuid'
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: 'email',
|
|
255
|
+
value: email
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'fullName',
|
|
259
|
+
value: fullName
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'metadata',
|
|
263
|
+
value: JSON.stringify(userMetadata),
|
|
264
|
+
cast: 'jsonb'
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
235
270
|
### Batch Queries
|
|
236
271
|
The `RDSDataService` 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.
|
|
237
272
|
|
|
@@ -302,6 +337,30 @@ let result = await data.executeStatement({
|
|
|
302
337
|
)
|
|
303
338
|
```
|
|
304
339
|
|
|
340
|
+
## Custom AWS instance
|
|
341
|
+
|
|
342
|
+
`data-api-client` allows for introducing a custom `AWS` as a parameter. This parameter is optional. If not present - `data-api-client` will fall back to the default `AWS` instance that comes with the library.
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
// Instantiate data-api-client with a custom AWS instance
|
|
346
|
+
const data = require('data-api-client')({
|
|
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'))
|
|
357
|
+
|
|
358
|
+
const data = require('data-api-client')({
|
|
359
|
+
AWS: AWS,
|
|
360
|
+
...
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
305
364
|
## Data API Limitations / Wonkiness
|
|
306
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.
|
|
307
366
|
|
|
@@ -349,7 +408,7 @@ No worries! The Data API Client gives you the ability to parameterize identifier
|
|
|
349
408
|
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.
|
|
350
409
|
|
|
351
410
|
## Enabling Data API
|
|
352
|
-
In order to use the Data API, you must enable it on your Aurora Serverless Cluster and create a Secret. You also
|
|
411
|
+
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.
|
|
353
412
|
|
|
354
413
|
### Enable Data API on your Aurora Serverless Cluster
|
|
355
414
|
|
|
@@ -374,7 +433,7 @@ You can then configure your rotation settings, if you want, and then you review
|
|
|
374
433
|
|
|
375
434
|
### Required Permissions
|
|
376
435
|
|
|
377
|
-
In order to use the Data API, your execution environment requires several IAM permissions. Below are the minimum permissions required.
|
|
436
|
+
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.
|
|
378
437
|
|
|
379
438
|
**YAML:**
|
|
380
439
|
```yaml
|
|
@@ -387,7 +446,7 @@ Statement:
|
|
|
387
446
|
- "rds-data:BeginTransaction"
|
|
388
447
|
- "rds-data:RollbackTransaction"
|
|
389
448
|
- "rds-data:CommitTransaction"
|
|
390
|
-
Resource: "
|
|
449
|
+
Resource: "*"
|
|
391
450
|
- Effect: "Allow"
|
|
392
451
|
Action:
|
|
393
452
|
- "secretsmanager:GetSecretValue"
|
|
@@ -407,7 +466,7 @@ Statement:
|
|
|
407
466
|
"rds-data:RollbackTransaction",
|
|
408
467
|
"rds-data:CommitTransaction"
|
|
409
468
|
],
|
|
410
|
-
"Resource": "
|
|
469
|
+
"Resource": "*"
|
|
411
470
|
},
|
|
412
471
|
{
|
|
413
472
|
"Effect": "Allow",
|
|
@@ -417,5 +476,10 @@ Statement:
|
|
|
417
476
|
]
|
|
418
477
|
```
|
|
419
478
|
|
|
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
|
+
|
|
420
484
|
## Contributions
|
|
421
485
|
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,7 +8,7 @@
|
|
|
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 1.
|
|
11
|
+
* @version 1.2.0
|
|
12
12
|
* @license MIT
|
|
13
13
|
*/
|
|
14
14
|
|
|
@@ -36,178 +36,218 @@ const supportedTypes = [
|
|
|
36
36
|
/********************************************************************/
|
|
37
37
|
|
|
38
38
|
// Simple error function
|
|
39
|
-
const error = (...err) => {
|
|
39
|
+
const error = (...err) => {
|
|
40
|
+
throw Error(...err)
|
|
41
|
+
}
|
|
40
42
|
|
|
41
43
|
// Parse SQL statement from provided arguments
|
|
42
|
-
const parseSQL = args =>
|
|
43
|
-
typeof args[0] === 'string'
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const parseSQL = (args) =>
|
|
45
|
+
typeof args[0] === 'string'
|
|
46
|
+
? args[0]
|
|
47
|
+
: typeof args[0] === 'object' && typeof args[0].sql === 'string'
|
|
48
|
+
? args[0].sql
|
|
49
|
+
: error(`No 'sql' statement provided.`)
|
|
46
50
|
|
|
47
51
|
// Parse the parameters from provided arguments
|
|
48
|
-
const parseParams = args =>
|
|
49
|
-
Array.isArray(args[0].parameters)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const parseParams = (args) =>
|
|
53
|
+
Array.isArray(args[0].parameters)
|
|
54
|
+
? args[0].parameters
|
|
55
|
+
: typeof args[0].parameters === 'object'
|
|
56
|
+
? [args[0].parameters]
|
|
57
|
+
: Array.isArray(args[1])
|
|
58
|
+
? args[1]
|
|
59
|
+
: typeof args[1] === 'object'
|
|
60
|
+
? [args[1]]
|
|
61
|
+
: args[0].parameters
|
|
62
|
+
? error(`'parameters' must be an object or array`)
|
|
63
|
+
: args[1]
|
|
64
|
+
? error('Parameters must be an object or array')
|
|
65
|
+
: []
|
|
56
66
|
|
|
57
67
|
// Parse the supplied database, or default to config
|
|
58
|
-
const parseDatabase = (config,args) =>
|
|
59
|
-
config.transactionId
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
const parseDatabase = (config, args) =>
|
|
69
|
+
config.transactionId
|
|
70
|
+
? config.database
|
|
71
|
+
: typeof args[0].database === 'string'
|
|
72
|
+
? args[0].database
|
|
73
|
+
: args[0].database
|
|
74
|
+
? error(`'database' must be a string.`)
|
|
75
|
+
: config.database
|
|
76
|
+
? config.database
|
|
77
|
+
: undefined // removed for #47 - error('No \'database\' provided.')
|
|
64
78
|
|
|
65
79
|
// Parse the supplied hydrateColumnNames command, or default to config
|
|
66
|
-
const parseHydrate = (config,args) =>
|
|
67
|
-
typeof args[0].hydrateColumnNames === 'boolean'
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
const parseHydrate = (config, args) =>
|
|
81
|
+
typeof args[0].hydrateColumnNames === 'boolean'
|
|
82
|
+
? args[0].hydrateColumnNames
|
|
83
|
+
: args[0].hydrateColumnNames
|
|
84
|
+
? error(`'hydrateColumnNames' must be a boolean.`)
|
|
85
|
+
: config.hydrateColumnNames
|
|
70
86
|
|
|
71
87
|
// Parse the supplied format options, or default to config
|
|
72
|
-
const parseFormatOptions = (config,args) =>
|
|
73
|
-
typeof args[0].formatOptions === 'object'
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
const parseFormatOptions = (config, args) =>
|
|
89
|
+
typeof args[0].formatOptions === 'object'
|
|
90
|
+
? {
|
|
91
|
+
deserializeDate:
|
|
92
|
+
typeof args[0].formatOptions.deserializeDate === 'boolean'
|
|
93
|
+
? args[0].formatOptions.deserializeDate
|
|
94
|
+
: args[0].formatOptions.deserializeDate
|
|
95
|
+
? error(`'formatOptions.deserializeDate' must be a boolean.`)
|
|
96
|
+
: config.formatOptions.deserializeDate,
|
|
97
|
+
treatAsLocalDate:
|
|
98
|
+
typeof args[0].formatOptions.treatAsLocalDate == 'boolean'
|
|
99
|
+
? args[0].formatOptions.treatAsLocalDate
|
|
100
|
+
: args[0].formatOptions.treatAsLocalDate
|
|
101
|
+
? error(`'formatOptions.treatAsLocalDate' must be a boolean.`)
|
|
102
|
+
: config.formatOptions.treatAsLocalDate
|
|
103
|
+
}
|
|
104
|
+
: args[0].formatOptions
|
|
105
|
+
? error(`'formatOptions' must be an object.`)
|
|
106
|
+
: config.formatOptions
|
|
83
107
|
|
|
84
108
|
// Prepare method params w/ supplied inputs if an object is passed
|
|
85
|
-
const prepareParams = ({ secretArn,resourceArn },args) => {
|
|
109
|
+
const prepareParams = ({ secretArn, resourceArn }, args) => {
|
|
86
110
|
return Object.assign(
|
|
87
|
-
{ secretArn,resourceArn }, // return Arns
|
|
88
|
-
typeof args[0] === 'object' ?
|
|
89
|
-
omit(args[0],['hydrateColumnNames','parameters']) : {} // merge any inputs
|
|
111
|
+
{ secretArn, resourceArn }, // return Arns
|
|
112
|
+
typeof args[0] === 'object' ? omit(args[0], ['hydrateColumnNames', 'parameters']) : {} // merge any inputs
|
|
90
113
|
)
|
|
91
114
|
}
|
|
92
115
|
|
|
93
116
|
// Utility function for removing certain keys from an object
|
|
94
|
-
const omit = (obj,values) =>
|
|
95
|
-
values.includes(x) ? acc : Object.assign(acc,{ [x]: obj[x] })
|
|
96
|
-
,{})
|
|
117
|
+
const omit = (obj, values) =>
|
|
118
|
+
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? acc : Object.assign(acc, { [x]: obj[x] })), {})
|
|
97
119
|
|
|
98
120
|
// Utility function for picking certain keys from an object
|
|
99
|
-
const pick = (obj,values) =>
|
|
100
|
-
values.includes(x) ? Object.assign(acc,{ [x]: obj[x] }) : acc
|
|
101
|
-
,{})
|
|
121
|
+
const pick = (obj, values) =>
|
|
122
|
+
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? Object.assign(acc, { [x]: obj[x] }) : acc), {})
|
|
102
123
|
|
|
103
124
|
// Utility function for flattening arrays
|
|
104
|
-
const flatten = arr => arr.reduce((acc,x) => acc.concat(x),[])
|
|
125
|
+
const flatten = (arr) => arr.reduce((acc, x) => acc.concat(x), [])
|
|
105
126
|
|
|
106
127
|
// Normize parameters so that they are all in standard format
|
|
107
|
-
const normalizeParams = params
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
128
|
+
const normalizeParams = (params) =>
|
|
129
|
+
params.reduce(
|
|
130
|
+
(acc, p) =>
|
|
131
|
+
Array.isArray(p)
|
|
132
|
+
? acc.concat([normalizeParams(p)])
|
|
133
|
+
: (Object.keys(p).length === 2 && p.name && typeof p.value !== 'undefined') ||
|
|
134
|
+
(Object.keys(p).length === 3 && p.name && typeof p.value !== 'undefined' && p.cast)
|
|
135
|
+
? acc.concat(p)
|
|
136
|
+
: acc.concat(splitParams(p)),
|
|
137
|
+
[]
|
|
138
|
+
) // end reduce
|
|
113
139
|
|
|
114
140
|
// Prepare parameters
|
|
115
|
-
const processParams = (sql,sqlParams,params,formatOptions,row=0) => {
|
|
141
|
+
const processParams = (engine, sql, sqlParams, params, formatOptions, row = 0) => {
|
|
116
142
|
return {
|
|
117
|
-
processedParams: params.reduce((acc,p) => {
|
|
143
|
+
processedParams: params.reduce((acc, p) => {
|
|
118
144
|
if (Array.isArray(p)) {
|
|
119
|
-
|
|
120
|
-
if (row === 0) {
|
|
145
|
+
const result = processParams(engine, sql, sqlParams, p, formatOptions, row)
|
|
146
|
+
if (row === 0) {
|
|
147
|
+
sql = result.escapedSql
|
|
148
|
+
row++
|
|
149
|
+
}
|
|
121
150
|
return acc.concat([result.processedParams])
|
|
122
151
|
} else if (sqlParams[p.name]) {
|
|
123
152
|
if (sqlParams[p.name].type === 'n_ph') {
|
|
124
|
-
|
|
153
|
+
if (p.cast) {
|
|
154
|
+
const regex = new RegExp(':' + p.name + '\\b', 'g')
|
|
155
|
+
sql = sql.replace(regex, engine === 'pg' ? `:${p.name}::${p.cast}` : `CAST(:${p.name} AS ${p.cast})`)
|
|
156
|
+
}
|
|
157
|
+
acc.push(formatParam(p.name, p.value, formatOptions))
|
|
125
158
|
} else if (row === 0) {
|
|
126
|
-
|
|
127
|
-
sql = sql.replace(regex,sqlString.escapeId(p.value))
|
|
159
|
+
const regex = new RegExp('::' + p.name + '\\b', 'g')
|
|
160
|
+
sql = sql.replace(regex, sqlString.escapeId(p.value))
|
|
128
161
|
}
|
|
129
162
|
return acc
|
|
130
163
|
} else {
|
|
131
164
|
return acc
|
|
132
165
|
}
|
|
133
|
-
},[]),
|
|
166
|
+
}, []),
|
|
134
167
|
escapedSql: sql
|
|
135
168
|
}
|
|
136
169
|
}
|
|
137
170
|
|
|
138
171
|
// Converts parameter to the name/value format
|
|
139
|
-
const formatParam = (n,v,formatOptions) => formatType(n,v,getType(v),getTypeHint(v),formatOptions)
|
|
172
|
+
const formatParam = (n, v, formatOptions) => formatType(n, v, getType(v), getTypeHint(v), formatOptions)
|
|
140
173
|
|
|
141
174
|
// Converts object params into name/value format
|
|
142
|
-
const splitParams = p => Object.keys(p).reduce((arr,x) =>
|
|
143
|
-
arr.concat({ name: x, value: p[x] }),[])
|
|
175
|
+
const splitParams = (p) => Object.keys(p).reduce((arr, x) => arr.concat({ name: x, value: p[x] }), [])
|
|
144
176
|
|
|
145
177
|
// Get all the sql parameters and assign them types
|
|
146
|
-
const getSqlParams = sql => {
|
|
178
|
+
const getSqlParams = (sql) => {
|
|
147
179
|
// TODO: probably need to remove comments from the sql
|
|
148
180
|
// TODO: placeholders?
|
|
149
181
|
// sql.match(/\:{1,2}\w+|\?+/g).map((p,i) => {
|
|
150
|
-
return (sql.match(/:{1,2}\w+/g) || [])
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
return (sql.match(/:{1,2}\w+/g) || [])
|
|
183
|
+
.map((p) => {
|
|
184
|
+
// TODO: future support for placeholder parsing?
|
|
185
|
+
// return p === '??' ? { type: 'id' } // identifier
|
|
186
|
+
// : p === '?' ? { type: 'ph', label: '__d'+i } // placeholder
|
|
187
|
+
return p.startsWith('::')
|
|
188
|
+
? { type: 'n_id', label: p.substr(2) } // named id
|
|
189
|
+
: { type: 'n_ph', label: p.substr(1) } // named placeholder
|
|
190
|
+
})
|
|
191
|
+
.reduce((acc, x) => {
|
|
192
|
+
return Object.assign(acc, {
|
|
159
193
|
[x.label]: {
|
|
160
194
|
type: x.type
|
|
161
195
|
}
|
|
162
|
-
}
|
|
163
|
-
)
|
|
164
|
-
},{}) // end reduce
|
|
196
|
+
})
|
|
197
|
+
}, {}) // end reduce
|
|
165
198
|
}
|
|
166
199
|
|
|
167
200
|
// Gets the value type and returns the correct value field name
|
|
168
201
|
// TODO: Support more types as the are released
|
|
169
|
-
const getType = val =>
|
|
170
|
-
typeof val === 'string'
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
const getType = (val) =>
|
|
203
|
+
typeof val === 'string'
|
|
204
|
+
? 'stringValue'
|
|
205
|
+
: typeof val === 'boolean'
|
|
206
|
+
? 'booleanValue'
|
|
207
|
+
: typeof val === 'number' && parseInt(val) === val
|
|
208
|
+
? 'longValue'
|
|
209
|
+
: typeof val === 'number' && parseFloat(val) === val
|
|
210
|
+
? 'doubleValue'
|
|
211
|
+
: val === null
|
|
212
|
+
? 'isNull'
|
|
213
|
+
: isDate(val)
|
|
214
|
+
? 'stringValue'
|
|
215
|
+
: Buffer.isBuffer(val)
|
|
216
|
+
? 'blobValue'
|
|
217
|
+
: // : Array.isArray(val) ? 'arrayValue' This doesn't work yet
|
|
218
|
+
// TODO: there is a 'structValue' now for postgres
|
|
219
|
+
typeof val === 'object' && Object.keys(val).length === 1 && supportedTypes.includes(Object.keys(val)[0])
|
|
220
|
+
? null
|
|
221
|
+
: undefined
|
|
183
222
|
|
|
184
223
|
// Hint to specify the underlying object type for data type mapping
|
|
185
|
-
const getTypeHint = val =>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const isDate = val =>
|
|
189
|
-
val instanceof Date
|
|
224
|
+
const getTypeHint = (val) => (isDate(val) ? 'TIMESTAMP' : undefined)
|
|
225
|
+
|
|
226
|
+
const isDate = (val) => val instanceof Date
|
|
190
227
|
|
|
191
228
|
// Creates a standard Data API parameter using the supplied inputs
|
|
192
|
-
const formatType = (name,value,type,typeHint,formatOptions) => {
|
|
229
|
+
const formatType = (name, value, type, typeHint, formatOptions) => {
|
|
193
230
|
return Object.assign(
|
|
194
231
|
typeHint != null ? { name, typeHint } : { name },
|
|
195
|
-
type === null
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
type === null
|
|
233
|
+
? { value }
|
|
234
|
+
: {
|
|
235
|
+
value: {
|
|
236
|
+
[type ? type : error(`'${name}' is an invalid type`)]:
|
|
237
|
+
type === 'isNull'
|
|
238
|
+
? true
|
|
239
|
+
: isDate(value)
|
|
240
|
+
? formatToTimeStamp(value, formatOptions && formatOptions.treatAsLocalDate)
|
|
241
|
+
: value
|
|
242
|
+
}
|
|
243
|
+
}
|
|
204
244
|
)
|
|
205
245
|
} // end formatType
|
|
206
246
|
|
|
207
247
|
// Formats the (UTC) date to the AWS accepted YYYY-MM-DD HH:MM:SS[.FFF] format
|
|
208
248
|
// See https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_SqlParameter.html
|
|
209
249
|
const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
210
|
-
const pad = (val,num=2) => '0'.repeat(num-(val + '').length) + val
|
|
250
|
+
const pad = (val, num = 2) => '0'.repeat(num - (val + '').length) + val
|
|
211
251
|
|
|
212
252
|
const year = treatAsLocalDate ? date.getFullYear() : date.getUTCFullYear()
|
|
213
253
|
const month = (treatAsLocalDate ? date.getMonth() : date.getUTCMonth()) + 1 // Convert to human month
|
|
@@ -218,7 +258,7 @@ const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
|
218
258
|
const seconds = treatAsLocalDate ? date.getSeconds() : date.getUTCSeconds()
|
|
219
259
|
const ms = treatAsLocalDate ? date.getMilliseconds() : date.getUTCMilliseconds()
|
|
220
260
|
|
|
221
|
-
const fraction = ms <= 0 ? '' : `.${pad(ms,3)}`
|
|
261
|
+
const fraction = ms <= 0 ? '' : `.${pad(ms, 3)}`
|
|
222
262
|
|
|
223
263
|
return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}${fraction}`
|
|
224
264
|
}
|
|
@@ -226,14 +266,15 @@ const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
|
226
266
|
// Converts the string value to a Date object.
|
|
227
267
|
// If standard TIMESTAMP format (YYYY-MM-DD[ HH:MM:SS[.FFF]]) without TZ + treatAsLocalDate=false then assume UTC Date
|
|
228
268
|
// In all other cases convert value to datetime as-is (also values with TZ info)
|
|
229
|
-
const formatFromTimeStamp = (value,treatAsLocalDate) =>
|
|
230
|
-
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d
|
|
231
|
-
new Date(value + 'Z')
|
|
232
|
-
new Date(value)
|
|
269
|
+
const formatFromTimeStamp = (value, treatAsLocalDate) =>
|
|
270
|
+
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d+)?)?$/.test(value)
|
|
271
|
+
? new Date(value + 'Z')
|
|
272
|
+
: new Date(value)
|
|
233
273
|
|
|
234
274
|
// Formats the results of a query response
|
|
235
275
|
const formatResults = (
|
|
236
|
-
{
|
|
276
|
+
{
|
|
277
|
+
// destructure results
|
|
237
278
|
columnMetadata, // ONLY when hydrate or includeResultMetadata is true
|
|
238
279
|
numberOfRecordsUpdated, // ONLY for executeStatement method
|
|
239
280
|
records, // ONLY for executeStatement method
|
|
@@ -243,93 +284,104 @@ const formatResults = (
|
|
|
243
284
|
hydrate,
|
|
244
285
|
includeMeta,
|
|
245
286
|
formatOptions
|
|
246
|
-
) =>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
records
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
)
|
|
287
|
+
) =>
|
|
288
|
+
Object.assign(
|
|
289
|
+
includeMeta ? { columnMetadata } : {},
|
|
290
|
+
numberOfRecordsUpdated !== undefined && !records ? { numberOfRecordsUpdated } : {},
|
|
291
|
+
records
|
|
292
|
+
? {
|
|
293
|
+
records: formatRecords(records, columnMetadata, hydrate, formatOptions)
|
|
294
|
+
}
|
|
295
|
+
: {},
|
|
296
|
+
updateResults ? { updateResults: formatUpdateResults(updateResults) } : {},
|
|
297
|
+
generatedFields && generatedFields.length > 0 ? { insertId: generatedFields[0].longValue } : {}
|
|
298
|
+
)
|
|
256
299
|
|
|
257
300
|
// Processes records and either extracts Typed Values into an array, or
|
|
258
301
|
// object with named column labels
|
|
259
|
-
const formatRecords = (recs,columns,hydrate,formatOptions) => {
|
|
260
|
-
|
|
302
|
+
const formatRecords = (recs, columns, hydrate, formatOptions) => {
|
|
261
303
|
// Create map for efficient value parsing
|
|
262
|
-
let fmap =
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Map over all the records (rows)
|
|
268
|
-
return recs ? recs.map(rec => {
|
|
269
|
-
|
|
270
|
-
// Reduce each field in the record (row)
|
|
271
|
-
return rec.reduce((acc,field,i) => {
|
|
272
|
-
|
|
273
|
-
// If the field is null, always return null
|
|
274
|
-
if (field.isNull === true) {
|
|
275
|
-
return hydrate ? // object if hydrate, else array
|
|
276
|
-
Object.assign(acc,{ [fmap[i].label]: null })
|
|
277
|
-
: acc.concat(null)
|
|
278
|
-
|
|
279
|
-
// If the field is mapped, return the mapped field
|
|
280
|
-
} else if (fmap[i] && fmap[i].field) {
|
|
281
|
-
const value = formatRecordValue(field[fmap[i].field],fmap[i].typeName,formatOptions)
|
|
282
|
-
return hydrate ? // object if hydrate, else array
|
|
283
|
-
Object.assign(acc,{ [fmap[i].label]: value })
|
|
284
|
-
: acc.concat(value)
|
|
285
|
-
|
|
286
|
-
// Else discover the field type
|
|
287
|
-
} else {
|
|
288
|
-
|
|
289
|
-
// Look for non-null fields
|
|
290
|
-
Object.keys(field).map(type => {
|
|
291
|
-
if (type !== 'isNull' && field[type] !== null) {
|
|
292
|
-
fmap[i]['field'] = type
|
|
293
|
-
}
|
|
304
|
+
let fmap =
|
|
305
|
+
recs && recs[0]
|
|
306
|
+
? recs[0].map((x, i) => {
|
|
307
|
+
return Object.assign({}, columns ? { label: columns[i].label, typeName: columns[i].typeName } : {}) // add column label and typeName
|
|
294
308
|
})
|
|
309
|
+
: {}
|
|
295
310
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
311
|
+
// Map over all the records (rows)
|
|
312
|
+
return recs
|
|
313
|
+
? recs.map((rec) => {
|
|
314
|
+
// Reduce each field in the record (row)
|
|
315
|
+
return rec.reduce(
|
|
316
|
+
(acc, field, i) => {
|
|
317
|
+
// If the field is null, always return null
|
|
318
|
+
if (field.isNull === true) {
|
|
319
|
+
return hydrate // object if hydrate, else array
|
|
320
|
+
? Object.assign(acc, { [fmap[i].label]: null })
|
|
321
|
+
: acc.concat(null)
|
|
322
|
+
|
|
323
|
+
// If the field is mapped, return the mapped field
|
|
324
|
+
} else if (fmap[i] && fmap[i].field) {
|
|
325
|
+
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions)
|
|
326
|
+
return hydrate // object if hydrate, else array
|
|
327
|
+
? Object.assign(acc, { [fmap[i].label]: value })
|
|
328
|
+
: acc.concat(value)
|
|
329
|
+
|
|
330
|
+
// Else discover the field type
|
|
331
|
+
} else {
|
|
332
|
+
// Look for non-null fields
|
|
333
|
+
Object.keys(field).map((type) => {
|
|
334
|
+
if (type !== 'isNull' && field[type] !== null) {
|
|
335
|
+
fmap[i]['field'] = type
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Return the mapped field (this should NEVER be null)
|
|
340
|
+
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions)
|
|
341
|
+
return hydrate // object if hydrate, else array
|
|
342
|
+
? Object.assign(acc, { [fmap[i].label]: value })
|
|
343
|
+
: acc.concat(value)
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
hydrate ? {} : []
|
|
347
|
+
) // init object if hydrate, else init array
|
|
348
|
+
})
|
|
349
|
+
: [] // empty record set returns an array
|
|
305
350
|
} // end formatRecords
|
|
306
351
|
|
|
307
352
|
// Format record value based on its value, the database column's typeName and the formatting options
|
|
308
|
-
const formatRecordValue = (value,typeName,formatOptions) =>
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
353
|
+
const formatRecordValue = (value, typeName, formatOptions) => {
|
|
354
|
+
if (
|
|
355
|
+
formatOptions &&
|
|
356
|
+
formatOptions.deserializeDate &&
|
|
357
|
+
['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMPTZ', 'TIMESTAMP WITH TIME ZONE'].includes(typeName.toUpperCase())
|
|
358
|
+
) {
|
|
359
|
+
return formatFromTimeStamp(
|
|
360
|
+
value,
|
|
361
|
+
(formatOptions && formatOptions.treatAsLocalDate) || typeName === 'TIMESTAMP WITH TIME ZONE'
|
|
362
|
+
)
|
|
363
|
+
} else if (typeName === 'JSON') {
|
|
364
|
+
return JSON.parse(value)
|
|
365
|
+
} else {
|
|
366
|
+
return value
|
|
367
|
+
}
|
|
368
|
+
}
|
|
312
369
|
|
|
313
370
|
// Format updateResults and extract insertIds
|
|
314
|
-
const formatUpdateResults = res =>
|
|
315
|
-
|
|
316
|
-
{ insertId: x.generatedFields[0].longValue } : {}
|
|
317
|
-
})
|
|
318
|
-
|
|
371
|
+
const formatUpdateResults = (res) =>
|
|
372
|
+
res.map((x) => {
|
|
373
|
+
return x.generatedFields && x.generatedFields.length > 0 ? { insertId: x.generatedFields[0].longValue } : {}
|
|
374
|
+
})
|
|
319
375
|
|
|
320
376
|
// Merge configuration data with supplied arguments
|
|
321
|
-
const mergeConfig = (initialConfig,args) =>
|
|
322
|
-
Object.assign(initialConfig,args)
|
|
323
|
-
|
|
324
|
-
|
|
377
|
+
const mergeConfig = (initialConfig, args) => Object.assign(initialConfig, args)
|
|
325
378
|
|
|
326
379
|
/********************************************************************/
|
|
327
380
|
/** QUERY MANAGEMENT **/
|
|
328
381
|
/********************************************************************/
|
|
329
382
|
|
|
330
383
|
// Query function (use standard form for `this` context)
|
|
331
|
-
const query = async function(config
|
|
332
|
-
|
|
384
|
+
const query = async function (config, ..._args) {
|
|
333
385
|
// Flatten array if nested arrays (fixes #30)
|
|
334
386
|
const args = Array.isArray(_args[0]) ? flatten(_args) : _args
|
|
335
387
|
|
|
@@ -338,92 +390,80 @@ const query = async function(config,..._args) {
|
|
|
338
390
|
const sqlParams = getSqlParams(sql)
|
|
339
391
|
|
|
340
392
|
// Parse hydration setting
|
|
341
|
-
const hydrateColumnNames = parseHydrate(config,args)
|
|
393
|
+
const hydrateColumnNames = parseHydrate(config, args)
|
|
342
394
|
|
|
343
395
|
// Parse data format settings
|
|
344
|
-
const formatOptions = parseFormatOptions(config,args)
|
|
396
|
+
const formatOptions = parseFormatOptions(config, args)
|
|
345
397
|
|
|
346
398
|
// Parse and normalize parameters
|
|
347
399
|
const parameters = normalizeParams(parseParams(args))
|
|
348
400
|
|
|
349
401
|
// Process parameters and escape necessary SQL
|
|
350
|
-
const { processedParams,escapedSql } = processParams(sql,sqlParams,parameters,formatOptions)
|
|
402
|
+
const { processedParams, escapedSql } = processParams(config.engine, sql, sqlParams, parameters, formatOptions)
|
|
351
403
|
|
|
352
404
|
// Determine if this is a batch request
|
|
353
|
-
const isBatch = processedParams.length > 0
|
|
354
|
-
&& Array.isArray(processedParams[0]) ? true : false
|
|
405
|
+
const isBatch = processedParams.length > 0 && Array.isArray(processedParams[0])
|
|
355
406
|
|
|
356
407
|
// Create/format the parameters
|
|
357
408
|
const params = Object.assign(
|
|
358
|
-
prepareParams(config,args),
|
|
409
|
+
prepareParams(config, args),
|
|
359
410
|
{
|
|
360
|
-
database: parseDatabase(config,args), // add database
|
|
411
|
+
database: parseDatabase(config, args), // add database
|
|
361
412
|
sql: escapedSql // add escaped sql statement
|
|
362
413
|
},
|
|
363
414
|
// Only include parameters if they exist
|
|
364
|
-
processedParams.length > 0
|
|
365
|
-
// Batch statements require parameterSets instead of parameters
|
|
366
|
-
|
|
415
|
+
processedParams.length > 0
|
|
416
|
+
? // Batch statements require parameterSets instead of parameters
|
|
417
|
+
{ [isBatch ? 'parameterSets' : 'parameters']: processedParams }
|
|
418
|
+
: {},
|
|
367
419
|
// Force meta data if set and not a batch
|
|
368
420
|
hydrateColumnNames && !isBatch ? { includeResultMetadata: true } : {},
|
|
369
421
|
// If a transactionId is passed, overwrite any manual input
|
|
370
422
|
config.transactionId ? { transactionId: config.transactionId } : {}
|
|
371
423
|
) // end params
|
|
372
424
|
|
|
373
|
-
try {
|
|
425
|
+
try {
|
|
426
|
+
// attempt to run the query
|
|
374
427
|
|
|
375
428
|
// Capture the result for debugging
|
|
376
|
-
let result = await (isBatch
|
|
429
|
+
let result = await (isBatch
|
|
430
|
+
? config.RDS.batchExecuteStatement(params).promise()
|
|
377
431
|
: config.RDS.executeStatement(params).promise())
|
|
378
432
|
|
|
379
433
|
// Format and return the results
|
|
380
|
-
return formatResults(
|
|
381
|
-
|
|
382
|
-
hydrateColumnNames,
|
|
383
|
-
args[0].includeResultMetadata === true ? true : false,
|
|
384
|
-
formatOptions
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
} catch(e) {
|
|
388
|
-
|
|
434
|
+
return formatResults(result, hydrateColumnNames, args[0].includeResultMetadata === true, formatOptions)
|
|
435
|
+
} catch (e) {
|
|
389
436
|
if (this && this.rollback) {
|
|
390
437
|
let rollback = await config.RDS.rollbackTransaction(
|
|
391
|
-
pick(params,['resourceArn','secretArn','transactionId'])
|
|
438
|
+
pick(params, ['resourceArn', 'secretArn', 'transactionId'])
|
|
392
439
|
).promise()
|
|
393
440
|
|
|
394
|
-
this.rollback(e,rollback)
|
|
441
|
+
this.rollback(e, rollback)
|
|
395
442
|
}
|
|
396
443
|
// Throw the error
|
|
397
444
|
throw e
|
|
398
445
|
}
|
|
399
|
-
|
|
400
446
|
} // end query
|
|
401
447
|
|
|
402
|
-
|
|
403
|
-
|
|
404
448
|
/********************************************************************/
|
|
405
449
|
/** TRANSACTION MANAGEMENT **/
|
|
406
450
|
/********************************************************************/
|
|
407
451
|
|
|
408
452
|
// Init a transaction object and return methods
|
|
409
|
-
const transaction = (config,_args) => {
|
|
410
|
-
|
|
453
|
+
const transaction = (config, _args) => {
|
|
411
454
|
let args = typeof _args === 'object' ? [_args] : [{}]
|
|
412
455
|
let queries = [] // keep track of queries
|
|
413
456
|
let rollback = () => {} // default rollback event
|
|
414
457
|
|
|
415
|
-
const txConfig = Object.assign(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
RDS: config.RDS // reference the RDSDataService instance
|
|
422
|
-
}
|
|
423
|
-
)
|
|
458
|
+
const txConfig = Object.assign(prepareParams(config, args), {
|
|
459
|
+
database: parseDatabase(config, args), // add database
|
|
460
|
+
hydrateColumnNames: parseHydrate(config, args), // add hydrate
|
|
461
|
+
formatOptions: parseFormatOptions(config, args), // add formatOptions
|
|
462
|
+
RDS: config.RDS // reference the RDSDataService instance
|
|
463
|
+
})
|
|
424
464
|
|
|
425
465
|
return {
|
|
426
|
-
query: function(...args) {
|
|
466
|
+
query: function (...args) {
|
|
427
467
|
if (typeof args[0] === 'function') {
|
|
428
468
|
queries.push(args[0])
|
|
429
469
|
} else {
|
|
@@ -431,22 +471,25 @@ const transaction = (config,_args) => {
|
|
|
431
471
|
}
|
|
432
472
|
return this
|
|
433
473
|
},
|
|
434
|
-
rollback: function(fn) {
|
|
435
|
-
if (typeof fn === 'function') {
|
|
474
|
+
rollback: function (fn) {
|
|
475
|
+
if (typeof fn === 'function') {
|
|
476
|
+
rollback = fn
|
|
477
|
+
}
|
|
436
478
|
return this
|
|
437
479
|
},
|
|
438
|
-
commit: async function() {
|
|
480
|
+
commit: async function () {
|
|
481
|
+
return await commit(txConfig, queries, rollback)
|
|
482
|
+
}
|
|
439
483
|
}
|
|
440
484
|
}
|
|
441
485
|
|
|
442
486
|
// Commit transaction by running queries
|
|
443
|
-
const commit = async (config,queries,rollback) => {
|
|
444
|
-
|
|
487
|
+
const commit = async (config, queries, rollback) => {
|
|
445
488
|
let results = [] // keep track of results
|
|
446
489
|
|
|
447
490
|
// Start a transaction
|
|
448
491
|
const { transactionId } = await config.RDS.beginTransaction(
|
|
449
|
-
pick(config,['resourceArn','secretArn','database'])
|
|
492
|
+
pick(config, ['resourceArn', 'secretArn', 'database'])
|
|
450
493
|
).promise()
|
|
451
494
|
|
|
452
495
|
// Add transactionId to the config
|
|
@@ -455,18 +498,18 @@ const commit = async (config,queries,rollback) => {
|
|
|
455
498
|
// Loop through queries
|
|
456
499
|
for (let i = 0; i < queries.length; i++) {
|
|
457
500
|
// Execute the queries, pass the rollback as context
|
|
458
|
-
let result = await query.apply({rollback},[config,queries[i](results[results.length-1],results)])
|
|
501
|
+
let result = await query.apply({ rollback }, [config, queries[i](results[results.length - 1], results)])
|
|
459
502
|
// Add the result to the main results accumulator
|
|
460
503
|
results.push(result)
|
|
461
504
|
}
|
|
462
505
|
|
|
463
506
|
// Commit our transaction
|
|
464
507
|
const { transactionStatus } = await txConfig.RDS.commitTransaction(
|
|
465
|
-
pick(config,['resourceArn','secretArn','transactionId'])
|
|
508
|
+
pick(config, ['resourceArn', 'secretArn', 'transactionId'])
|
|
466
509
|
).promise()
|
|
467
510
|
|
|
468
511
|
// Add the transaction status to the results
|
|
469
|
-
results.push({transactionStatus})
|
|
512
|
+
results.push({ transactionStatus })
|
|
470
513
|
|
|
471
514
|
// Return the results
|
|
472
515
|
return results
|
|
@@ -477,12 +520,34 @@ const commit = async (config,queries,rollback) => {
|
|
|
477
520
|
/********************************************************************/
|
|
478
521
|
|
|
479
522
|
// Export main function
|
|
480
|
-
|
|
481
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Create a Data API client instance
|
|
525
|
+
* @param {object} params
|
|
526
|
+
* @param {'mysql'|'pg'} [params.engine=mysql] The type of database (MySQL or Postgres)
|
|
527
|
+
* @param {string} params.resourceArn The ARN of your Aurora Serverless Cluster
|
|
528
|
+
* @param {string} params.secretArn The ARN of the secret associated with your
|
|
529
|
+
* database credentials
|
|
530
|
+
* @param {string} [params.database] The name of the database
|
|
531
|
+
* @param {boolean} [params.hydrateColumnNames=true] Return objects with column
|
|
532
|
+
* names as keys
|
|
533
|
+
* @param {object} [params.options={}] Configuration object passed directly
|
|
534
|
+
* into RDSDataService
|
|
535
|
+
* @param {object} [params.formatOptions] Date-related formatting options
|
|
536
|
+
* @param {boolean} [params.formatOptions.deserializeDate=false]
|
|
537
|
+
* @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
|
+
*
|
|
542
|
+
*/
|
|
543
|
+
const init = (params) => {
|
|
482
544
|
// Set the options for the RDSDataService
|
|
483
|
-
const options =
|
|
484
|
-
|
|
485
|
-
|
|
545
|
+
const options =
|
|
546
|
+
typeof params.options === 'object'
|
|
547
|
+
? params.options
|
|
548
|
+
: params.options !== undefined
|
|
549
|
+
? error(`'options' must be an object`)
|
|
550
|
+
: {}
|
|
486
551
|
|
|
487
552
|
// Update the AWS http agent with the region
|
|
488
553
|
if (typeof params.region === 'string') {
|
|
@@ -496,22 +561,23 @@ module.exports = (params) => {
|
|
|
496
561
|
|
|
497
562
|
// Set the configuration for this instance
|
|
498
563
|
const config = {
|
|
564
|
+
// Require engine
|
|
565
|
+
engine: typeof params.engine === 'string' ? params.engine : 'mysql',
|
|
499
566
|
|
|
500
567
|
// Require secretArn
|
|
501
|
-
secretArn: typeof params.secretArn === 'string' ?
|
|
502
|
-
params.secretArn
|
|
503
|
-
: error('\'secretArn\' string value required'),
|
|
568
|
+
secretArn: typeof params.secretArn === 'string' ? params.secretArn : error(`'secretArn' string value required`),
|
|
504
569
|
|
|
505
570
|
// Require resourceArn
|
|
506
|
-
resourceArn:
|
|
507
|
-
params.resourceArn
|
|
508
|
-
: error('\'resourceArn\' string value required'),
|
|
571
|
+
resourceArn:
|
|
572
|
+
typeof params.resourceArn === 'string' ? params.resourceArn : error(`'resourceArn' string value required`),
|
|
509
573
|
|
|
510
574
|
// Load optional database
|
|
511
|
-
database:
|
|
512
|
-
params.database
|
|
513
|
-
|
|
514
|
-
|
|
575
|
+
database:
|
|
576
|
+
typeof params.database === 'string'
|
|
577
|
+
? params.database
|
|
578
|
+
: params.database !== undefined
|
|
579
|
+
? error(`'database' must be a string`)
|
|
580
|
+
: undefined,
|
|
515
581
|
|
|
516
582
|
// Load optional schema DISABLED for now since this isn't used with MySQL
|
|
517
583
|
// schema: typeof params.schema === 'string' ? params.schema
|
|
@@ -519,52 +585,41 @@ module.exports = (params) => {
|
|
|
519
585
|
// : undefined,
|
|
520
586
|
|
|
521
587
|
// Set hydrateColumnNames (default to true)
|
|
522
|
-
hydrateColumnNames:
|
|
523
|
-
typeof params.hydrateColumnNames === 'boolean' ?
|
|
524
|
-
params.hydrateColumnNames : true,
|
|
588
|
+
hydrateColumnNames: typeof params.hydrateColumnNames === 'boolean' ? params.hydrateColumnNames : true,
|
|
525
589
|
|
|
526
590
|
// Value formatting options. For date the deserialization is enabled and (re)stored as UTC
|
|
527
591
|
formatOptions: {
|
|
528
592
|
deserializeDate:
|
|
529
593
|
typeof params.formatOptions === 'object' && params.formatOptions.deserializeDate === false ? false : true,
|
|
530
|
-
treatAsLocalDate:
|
|
531
|
-
typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
594
|
+
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
532
595
|
},
|
|
533
596
|
|
|
534
597
|
// TODO: Put this in a separate module for testing?
|
|
535
598
|
// Create an instance of RDSDataService
|
|
536
|
-
RDS: new AWS.RDSDataService(options)
|
|
537
|
-
|
|
538
|
-
} // end config
|
|
599
|
+
RDS: params.AWS ? new params.AWS.RDSDataService(options) : new AWS.RDSDataService(options)
|
|
600
|
+
} // end config
|
|
539
601
|
|
|
540
602
|
// Return public methods
|
|
541
603
|
return {
|
|
542
604
|
// Query method, pass config and parameters
|
|
543
|
-
query: (...x) => query(config
|
|
605
|
+
query: (...x) => query(config, ...x),
|
|
544
606
|
// Transaction method, pass config and parameters
|
|
545
|
-
transaction: (x) => transaction(config,x),
|
|
607
|
+
transaction: (x) => transaction(config, x),
|
|
546
608
|
|
|
547
609
|
// Export promisified versions of the RDSDataService methods
|
|
548
610
|
batchExecuteStatement: (args) =>
|
|
549
611
|
config.RDS.batchExecuteStatement(
|
|
550
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
612
|
+
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)
|
|
551
613
|
).promise(),
|
|
552
614
|
beginTransaction: (args) =>
|
|
553
|
-
config.RDS.beginTransaction(
|
|
554
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
555
|
-
).promise(),
|
|
615
|
+
config.RDS.beginTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(),
|
|
556
616
|
commitTransaction: (args) =>
|
|
557
|
-
config.RDS.commitTransaction(
|
|
558
|
-
mergeConfig(pick(config,['resourceArn','secretArn']),args)
|
|
559
|
-
).promise(),
|
|
617
|
+
config.RDS.commitTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise(),
|
|
560
618
|
executeStatement: (args) =>
|
|
561
|
-
config.RDS.executeStatement(
|
|
562
|
-
mergeConfig(pick(config,['resourceArn','secretArn','database']),args)
|
|
563
|
-
).promise(),
|
|
619
|
+
config.RDS.executeStatement(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(),
|
|
564
620
|
rollbackTransaction: (args) =>
|
|
565
|
-
config.RDS.rollbackTransaction(
|
|
566
|
-
mergeConfig(pick(config,['resourceArn','secretArn']),args)
|
|
567
|
-
).promise()
|
|
621
|
+
config.RDS.rollbackTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise()
|
|
568
622
|
}
|
|
569
|
-
|
|
570
623
|
} // end exports
|
|
624
|
+
|
|
625
|
+
module.exports = init
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "data-api-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.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,17 +25,17 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/jeremydaly/data-api-client#readme",
|
|
27
27
|
"devDependencies": {
|
|
28
|
-
"aws-sdk": "^2.
|
|
29
|
-
"eslint": "^
|
|
30
|
-
"
|
|
31
|
-
"
|
|
28
|
+
"aws-sdk": "^2.811.0",
|
|
29
|
+
"eslint": "^8.12.0",
|
|
30
|
+
"eslint-config-prettier": "^8.5.0",
|
|
31
|
+
"jest": "^27.5.1",
|
|
32
|
+
"prettier": "^2.6.2",
|
|
33
|
+
"rewire": "^6.0.0"
|
|
32
34
|
},
|
|
33
35
|
"dependencies": {
|
|
34
|
-
"sqlstring": "^2.3.
|
|
36
|
+
"sqlstring": "^2.3.2"
|
|
35
37
|
},
|
|
36
38
|
"files": [
|
|
37
|
-
"LICENSE",
|
|
38
|
-
"README.md",
|
|
39
39
|
"index.js"
|
|
40
40
|
]
|
|
41
41
|
}
|