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