data-api-client 1.2.0 → 2.0.0-beta.0

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