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.
Files changed (3) hide show
  1. package/README.md +72 -8
  2. package/index.js +321 -266
  3. 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
- // [ { id: 1, name: 'Alice', age: null },
28
- // { id: 2, name: 'Mike', age: 52 },
29
- // { id: 3, name: 'Carol', age: 50 } ]
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 musst grant your execution environment a number of permission as outlined in the following sections.
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: "arn:aws:rds:{REGION}:{ACCOUNT-ID}:cluster:{YOUR-CLUSTER-NAME}"
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": "arn:aws:rds:{REGION}:{ACCOUNT-ID}:cluster:{YOUR-CLUSTER-NAME}"
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
+ [![New Relic](https://user-images.githubusercontent.com/2053544/96728664-55238700-1382-11eb-93cb-82fe7cb5e043.png)](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.1.0
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) => { throw 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' ? args[0]
44
- : typeof args[0] === 'object' && typeof args[0].sql === 'string' ? args[0].sql
45
- : error('No \'sql\' statement provided.')
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) ? args[0].parameters
50
- : typeof args[0].parameters === 'object' ? [args[0].parameters]
51
- : Array.isArray(args[1]) ? args[1]
52
- : typeof args[1] === 'object' ? [args[1]]
53
- : args[0].parameters ? error('\'parameters\' must be an object or array')
54
- : args[1] ? error('Parameters must be an object or array')
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 ? config.database
60
- : typeof args[0].database === 'string' ? args[0].database
61
- : args[0].database ? error('\'database\' must be a string.')
62
- : config.database ? config.database
63
- : undefined // removed for #47 - error('No \'database\' provided.')
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' ? args[0].hydrateColumnNames
68
- : args[0].hydrateColumnNames ? error('\'hydrateColumnNames\' must be a boolean.')
69
- : config.hydrateColumnNames
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
- deserializeDate: typeof args[0].formatOptions.deserializeDate === 'boolean' ? args[0].formatOptions.deserializeDate
75
- : args[0].formatOptions.deserializeDate ? error('\'formatOptions.deserializeDate\' must be a boolean.')
76
- : config.formatOptions.deserializeDate,
77
- treatAsLocalDate: typeof args[0].formatOptions.treatAsLocalDate == 'boolean' ? args[0].formatOptions.treatAsLocalDate
78
- : args[0].formatOptions.treatAsLocalDate ? error('\'formatOptions.treatAsLocalDate\' must be a boolean.')
79
- : config.formatOptions.treatAsLocalDate
80
- }
81
- : args[0].formatOptions ? error('\'formatOptions\' must be an object.')
82
- : config.formatOptions
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) => Object.keys(obj).reduce((acc,x) =>
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) => Object.keys(obj).reduce((acc,x) =>
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 => params.reduce((acc,p) =>
108
- Array.isArray(p) ? acc.concat([normalizeParams(p)])
109
- : Object.keys(p).length === 2 && p.name && p.value ? acc.concat(p)
110
- : acc.concat(splitParams(p))
111
- ,[]) // end reduce
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
- let result = processParams(sql,sqlParams,p,formatOptions,row)
120
- if (row === 0) { sql = result.escapedSql; row++ }
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
- acc.push(formatParam(p.name,p.value,formatOptions))
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
- let regex = new RegExp('::' + p.name + '\\b','g')
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) || []).map((p) => {
151
- // TODO: future support for placeholder parsing?
152
- // return p === '??' ? { type: 'id' } // identifier
153
- // : p === '?' ? { type: 'ph', label: '__d'+i } // placeholder
154
- return p.startsWith('::') ? { type: 'n_id', label: p.substr(2) } // named id
155
- : { type: 'n_ph', label: p.substr(1) } // named placeholder
156
- }).reduce((acc,x) => {
157
- return Object.assign(acc,
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' ? 'stringValue'
171
- : typeof val === 'boolean' ? 'booleanValue'
172
- : typeof val === 'number' && parseInt(val) === val ? 'longValue'
173
- : typeof val === 'number' && parseFloat(val) === val ? 'doubleValue'
174
- : val === null ? 'isNull'
175
- : isDate(val) ? 'stringValue'
176
- : Buffer.isBuffer(val) ? 'blobValue'
177
- // : Array.isArray(val) ? 'arrayValue' This doesn't work yet
178
- // TODO: there is a 'structValue' now for postgres
179
- : typeof val === 'object'
180
- && Object.keys(val).length === 1
181
- && supportedTypes.includes(Object.keys(val)[0]) ? null
182
- : undefined
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
- isDate(val) ? 'TIMESTAMP' : undefined
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 ? { value }
196
- : {
197
- value: {
198
- [type ? type : error(`'${name}' is an invalid type`)]
199
- : type === 'isNull' ? true
200
- : isDate(value) ? formatToTimeStamp(value, formatOptions && formatOptions.treatAsLocalDate)
201
- : value
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{3})?)?$/.test(value) ?
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
- { // destructure results
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
- ) => Object.assign(
247
- includeMeta ? { columnMetadata } : {},
248
- numberOfRecordsUpdated !== undefined && !records ? { numberOfRecordsUpdated } : {},
249
- records ? {
250
- records: formatRecords(records, columnMetadata, hydrate, formatOptions)
251
- } : {},
252
- updateResults ? { updateResults: formatUpdateResults(updateResults) } : {},
253
- generatedFields && generatedFields.length > 0 ?
254
- { insertId: generatedFields[0].longValue } : {}
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 = recs && recs[0] ? recs[0].map((x,i) => {
263
- return Object.assign({},
264
- columns ? { label: columns[i].label, typeName: columns[i].typeName } : {} ) // add column label and typeName
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
- // Return the mapped field (this should NEVER be null)
297
- const value = formatRecordValue(field[fmap[i].field],fmap[i].typeName,formatOptions)
298
- return hydrate ? // object if hydrate, else array
299
- Object.assign(acc,{ [fmap[i].label]: value })
300
- : acc.concat(value)
301
- }
302
-
303
- }, hydrate ? {} : []) // init object if hydrate, else init array
304
- }) : [] // empty record set returns an array
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) => formatOptions && formatOptions.deserializeDate &&
309
- ['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMP WITH TIME ZONE'].includes(typeName)
310
- ? formatFromTimeStamp(value,(formatOptions && formatOptions.treatAsLocalDate) || typeName === 'TIMESTAMP WITH TIME ZONE')
311
- : value
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 => res.map(x => {
315
- return x.generatedFields && x.generatedFields.length > 0 ?
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,..._args) {
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
- { [isBatch ? 'parameterSets' : 'parameters']: processedParams } : {},
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 { // attempt to run the query
425
+ try {
426
+ // attempt to run the query
374
427
 
375
428
  // Capture the result for debugging
376
- let result = await (isBatch ? config.RDS.batchExecuteStatement(params).promise()
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
- result,
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
- prepareParams(config,args),
417
- {
418
- database: parseDatabase(config,args), // add database
419
- hydrateColumnNames: parseHydrate(config,args), // add hydrate
420
- formatOptions: parseFormatOptions(config,args), // add formatOptions
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') { rollback = fn }
474
+ rollback: function (fn) {
475
+ if (typeof fn === 'function') {
476
+ rollback = fn
477
+ }
436
478
  return this
437
479
  },
438
- commit: async function() { return await commit(txConfig,queries,rollback) }
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
- module.exports = (params) => {
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 = typeof params.options === 'object' ? params.options
484
- : params.options !== undefined ? error('\'options\' must be an object')
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: typeof params.resourceArn === 'string' ?
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: typeof params.database === 'string' ?
512
- params.database
513
- : params.database !== undefined ? error('\'database\' must be a string')
514
- : undefined,
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,...x),
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.1.0",
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.713.0",
29
- "eslint": "^6.8.0",
30
- "jest": "^25.5.4",
31
- "rewire": "^4.0.1"
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.1"
36
+ "sqlstring": "^2.3.2"
35
37
  },
36
38
  "files": [
37
- "LICENSE",
38
- "README.md",
39
39
  "index.js"
40
40
  ]
41
41
  }