data-api-client 2.0.0-beta.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +639 -146
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +79 -0
- package/dist/compat/errors.d.ts +28 -0
- package/dist/compat/errors.d.ts.map +1 -0
- package/dist/compat/errors.js +163 -0
- package/dist/compat/index.d.ts +7 -0
- package/dist/compat/index.d.ts.map +1 -0
- package/dist/compat/index.js +12 -0
- package/dist/compat/mysql2.d.ts +86 -0
- package/dist/compat/mysql2.d.ts.map +1 -0
- package/dist/compat/mysql2.js +350 -0
- package/dist/compat/pg.d.ts +142 -0
- package/dist/compat/pg.d.ts.map +1 -0
- package/dist/compat/pg.js +344 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/params.d.ts +19 -0
- package/dist/params.d.ts.map +1 -0
- package/dist/params.js +125 -0
- package/dist/query.d.ts +5 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +39 -0
- package/dist/results.d.ts +12 -0
- package/dist/results.d.ts.map +1 -0
- package/dist/results.js +93 -0
- package/dist/retry.d.ts +11 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +76 -0
- package/dist/transaction.d.ts +4 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +59 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +150 -0
- package/package.json +80 -9
- package/index.js +0 -642
package/index.js
DELETED
|
@@ -1,642 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
* This module provides a simplified interface into the Aurora Serverless
|
|
5
|
-
* Data API by abstracting away the notion of field values.
|
|
6
|
-
*
|
|
7
|
-
* More detail regarding the Aurora Serverless Data APIcan be found here:
|
|
8
|
-
* https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html
|
|
9
|
-
*
|
|
10
|
-
* @author Jeremy Daly <jeremy@jeremydaly.com>
|
|
11
|
-
* @version 2.0.0-beta
|
|
12
|
-
* @license MIT
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
// Require the aws-sdk. This is a dev dependency, so if being used
|
|
16
|
-
// outside of a Lambda execution environment, it must be manually installed.
|
|
17
|
-
const {
|
|
18
|
-
RDSDataClient,
|
|
19
|
-
ExecuteStatementCommand,
|
|
20
|
-
BatchExecuteStatementCommand,
|
|
21
|
-
BeginTransactionCommand,
|
|
22
|
-
CommitTransactionCommand,
|
|
23
|
-
RollbackTransactionCommand
|
|
24
|
-
} = require('@aws-sdk/client-rds-data')
|
|
25
|
-
|
|
26
|
-
// Require sqlstring to add additional escaping capabilities
|
|
27
|
-
const sqlString = require('sqlstring')
|
|
28
|
-
|
|
29
|
-
// Supported value types in the Data API
|
|
30
|
-
const supportedTypes = [
|
|
31
|
-
'arrayValue',
|
|
32
|
-
'blobValue',
|
|
33
|
-
'booleanValue',
|
|
34
|
-
'doubleValue',
|
|
35
|
-
'isNull',
|
|
36
|
-
'longValue',
|
|
37
|
-
'stringValue',
|
|
38
|
-
'structValue'
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
/********************************************************************/
|
|
42
|
-
/** PRIVATE METHODS **/
|
|
43
|
-
/********************************************************************/
|
|
44
|
-
|
|
45
|
-
// Simple error function
|
|
46
|
-
const error = (...err) => {
|
|
47
|
-
throw Error(...err)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Parse SQL statement from provided arguments
|
|
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.`)
|
|
57
|
-
|
|
58
|
-
// Parse the parameters from provided arguments
|
|
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
|
-
: []
|
|
73
|
-
|
|
74
|
-
// Parse the supplied database, or default to config
|
|
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.')
|
|
85
|
-
|
|
86
|
-
// Parse the supplied hydrateColumnNames command, or default to config
|
|
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
|
|
93
|
-
|
|
94
|
-
// Parse the supplied format options, or default to config
|
|
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
|
|
114
|
-
|
|
115
|
-
// Prepare method params w/ supplied inputs if an object is passed
|
|
116
|
-
const prepareParams = ({ secretArn, resourceArn }, args) => {
|
|
117
|
-
return Object.assign(
|
|
118
|
-
{ secretArn, resourceArn }, // return Arns
|
|
119
|
-
typeof args[0] === 'object' ? omit(args[0], ['hydrateColumnNames', 'parameters']) : {} // merge any inputs
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Utility function for removing certain keys from an object
|
|
124
|
-
const omit = (obj, values) =>
|
|
125
|
-
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? acc : Object.assign(acc, { [x]: obj[x] })), {})
|
|
126
|
-
|
|
127
|
-
// Utility function for picking certain keys from an object
|
|
128
|
-
const pick = (obj, values) =>
|
|
129
|
-
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? Object.assign(acc, { [x]: obj[x] }) : acc), {})
|
|
130
|
-
|
|
131
|
-
// Utility function for flattening arrays
|
|
132
|
-
const flatten = (arr) => arr.reduce((acc, x) => acc.concat(x), [])
|
|
133
|
-
|
|
134
|
-
// Normize parameters so that they are all in standard format
|
|
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
|
|
146
|
-
|
|
147
|
-
// Prepare parameters
|
|
148
|
-
const processParams = (engine, sql, sqlParams, params, formatOptions, row = 0) => {
|
|
149
|
-
return {
|
|
150
|
-
processedParams: params.reduce((acc, p) => {
|
|
151
|
-
if (Array.isArray(p)) {
|
|
152
|
-
const result = processParams(engine, sql, sqlParams, p, formatOptions, row)
|
|
153
|
-
if (row === 0) {
|
|
154
|
-
sql = result.escapedSql
|
|
155
|
-
row++
|
|
156
|
-
}
|
|
157
|
-
return acc.concat([result.processedParams])
|
|
158
|
-
} else if (sqlParams[p.name]) {
|
|
159
|
-
if (sqlParams[p.name].type === 'n_ph') {
|
|
160
|
-
if (p.cast) {
|
|
161
|
-
const regex = new RegExp(':' + p.name + '\\b', 'g')
|
|
162
|
-
sql = sql.replace(regex, engine === 'pg' ? `:${p.name}::${p.cast}` : `CAST(:${p.name} AS ${p.cast})`)
|
|
163
|
-
}
|
|
164
|
-
acc.push(formatParam(p.name, p.value, formatOptions))
|
|
165
|
-
} else if (row === 0) {
|
|
166
|
-
const regex = new RegExp('::' + p.name + '\\b', 'g')
|
|
167
|
-
sql = sql.replace(regex, sqlString.escapeId(p.value))
|
|
168
|
-
}
|
|
169
|
-
return acc
|
|
170
|
-
} else {
|
|
171
|
-
return acc
|
|
172
|
-
}
|
|
173
|
-
}, []),
|
|
174
|
-
escapedSql: sql
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Converts parameter to the name/value format
|
|
179
|
-
const formatParam = (n, v, formatOptions) => formatType(n, v, getType(v), getTypeHint(v), formatOptions)
|
|
180
|
-
|
|
181
|
-
// Converts object params into name/value format
|
|
182
|
-
const splitParams = (p) => Object.keys(p).reduce((arr, x) => arr.concat({ name: x, value: p[x] }), [])
|
|
183
|
-
|
|
184
|
-
// Get all the sql parameters and assign them types
|
|
185
|
-
const getSqlParams = (sql) => {
|
|
186
|
-
// TODO: probably need to remove comments from the sql
|
|
187
|
-
// TODO: placeholders?
|
|
188
|
-
// sql.match(/\:{1,2}\w+|\?+/g).map((p,i) => {
|
|
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, {
|
|
200
|
-
[x.label]: {
|
|
201
|
-
type: x.type
|
|
202
|
-
}
|
|
203
|
-
})
|
|
204
|
-
}, {}) // end reduce
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Gets the value type and returns the correct value field name
|
|
208
|
-
// TODO: Support more types as the are released
|
|
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
|
|
229
|
-
|
|
230
|
-
// Hint to specify the underlying object type for data type mapping
|
|
231
|
-
const getTypeHint = (val) => (isDate(val) ? 'TIMESTAMP' : undefined)
|
|
232
|
-
|
|
233
|
-
const isDate = (val) => val instanceof Date
|
|
234
|
-
|
|
235
|
-
// Creates a standard Data API parameter using the supplied inputs
|
|
236
|
-
const formatType = (name, value, type, typeHint, formatOptions) => {
|
|
237
|
-
return Object.assign(
|
|
238
|
-
typeHint != null ? { name, typeHint } : { name },
|
|
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
|
-
}
|
|
251
|
-
)
|
|
252
|
-
} // end formatType
|
|
253
|
-
|
|
254
|
-
// Formats the (UTC) date to the AWS accepted YYYY-MM-DD HH:MM:SS[.FFF] format
|
|
255
|
-
// See https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_SqlParameter.html
|
|
256
|
-
const formatToTimeStamp = (date, treatAsLocalDate) => {
|
|
257
|
-
const pad = (val, num = 2) => '0'.repeat(num - (val + '').length) + val
|
|
258
|
-
|
|
259
|
-
const year = treatAsLocalDate ? date.getFullYear() : date.getUTCFullYear()
|
|
260
|
-
const month = (treatAsLocalDate ? date.getMonth() : date.getUTCMonth()) + 1 // Convert to human month
|
|
261
|
-
const day = treatAsLocalDate ? date.getDate() : date.getUTCDate()
|
|
262
|
-
|
|
263
|
-
const hours = treatAsLocalDate ? date.getHours() : date.getUTCHours()
|
|
264
|
-
const minutes = treatAsLocalDate ? date.getMinutes() : date.getUTCMinutes()
|
|
265
|
-
const seconds = treatAsLocalDate ? date.getSeconds() : date.getUTCSeconds()
|
|
266
|
-
const ms = treatAsLocalDate ? date.getMilliseconds() : date.getUTCMilliseconds()
|
|
267
|
-
|
|
268
|
-
const fraction = ms <= 0 ? '' : `.${pad(ms, 3)}`
|
|
269
|
-
|
|
270
|
-
return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}${fraction}`
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Converts the string value to a Date object.
|
|
274
|
-
// If standard TIMESTAMP format (YYYY-MM-DD[ HH:MM:SS[.FFF]]) without TZ + treatAsLocalDate=false then assume UTC Date
|
|
275
|
-
// In all other cases convert value to datetime as-is (also values with TZ info)
|
|
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)
|
|
280
|
-
|
|
281
|
-
// Formats the results of a query response
|
|
282
|
-
const formatResults = (
|
|
283
|
-
{
|
|
284
|
-
// destructure results
|
|
285
|
-
columnMetadata, // ONLY when hydrate or includeResultMetadata is true
|
|
286
|
-
numberOfRecordsUpdated, // ONLY for executeStatement method
|
|
287
|
-
records, // ONLY for executeStatement method
|
|
288
|
-
generatedFields, // ONLY for INSERTS
|
|
289
|
-
updateResults // ONLY on batchExecuteStatement
|
|
290
|
-
},
|
|
291
|
-
hydrate,
|
|
292
|
-
includeMeta,
|
|
293
|
-
formatOptions
|
|
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
|
-
)
|
|
306
|
-
|
|
307
|
-
// Processes records and either extracts Typed Values into an array, or
|
|
308
|
-
// object with named column labels
|
|
309
|
-
const formatRecords = (recs, columns, hydrate, formatOptions) => {
|
|
310
|
-
// Create map for efficient value parsing
|
|
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
|
|
315
|
-
})
|
|
316
|
-
: {}
|
|
317
|
-
|
|
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
|
|
357
|
-
} // end formatRecords
|
|
358
|
-
|
|
359
|
-
// Format record value based on its value, the database column's typeName and the formatting options
|
|
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
|
-
}
|
|
376
|
-
|
|
377
|
-
// Format updateResults and extract insertIds
|
|
378
|
-
const formatUpdateResults = (res) =>
|
|
379
|
-
res.map((x) => {
|
|
380
|
-
return x.generatedFields && x.generatedFields.length > 0 ? { insertId: x.generatedFields[0].longValue } : {}
|
|
381
|
-
})
|
|
382
|
-
|
|
383
|
-
// Merge configuration data with supplied arguments
|
|
384
|
-
const mergeConfig = (initialConfig, args) => Object.assign(initialConfig, args)
|
|
385
|
-
|
|
386
|
-
/********************************************************************/
|
|
387
|
-
/** QUERY MANAGEMENT **/
|
|
388
|
-
/********************************************************************/
|
|
389
|
-
|
|
390
|
-
// Query function (use standard form for `this` context)
|
|
391
|
-
const query = async function (config, ..._args) {
|
|
392
|
-
// Flatten array if nested arrays (fixes #30)
|
|
393
|
-
const args = Array.isArray(_args[0]) ? flatten(_args) : _args
|
|
394
|
-
|
|
395
|
-
// Parse and process sql
|
|
396
|
-
const sql = parseSQL(args)
|
|
397
|
-
const sqlParams = getSqlParams(sql)
|
|
398
|
-
|
|
399
|
-
// Parse hydration setting
|
|
400
|
-
const hydrateColumnNames = parseHydrate(config, args)
|
|
401
|
-
|
|
402
|
-
// Parse data format settings
|
|
403
|
-
const formatOptions = parseFormatOptions(config, args)
|
|
404
|
-
|
|
405
|
-
// Parse and normalize parameters
|
|
406
|
-
const parameters = normalizeParams(parseParams(args))
|
|
407
|
-
|
|
408
|
-
// Process parameters and escape necessary SQL
|
|
409
|
-
const { processedParams, escapedSql } = processParams(config.engine, sql, sqlParams, parameters, formatOptions)
|
|
410
|
-
|
|
411
|
-
// Determine if this is a batch request
|
|
412
|
-
const isBatch = processedParams.length > 0 && Array.isArray(processedParams[0])
|
|
413
|
-
|
|
414
|
-
// Create/format the parameters
|
|
415
|
-
const params = Object.assign(
|
|
416
|
-
prepareParams(config, args),
|
|
417
|
-
{
|
|
418
|
-
database: parseDatabase(config, args), // add database
|
|
419
|
-
sql: escapedSql // add escaped sql statement
|
|
420
|
-
},
|
|
421
|
-
// Only include parameters if they exist
|
|
422
|
-
processedParams.length > 0
|
|
423
|
-
? // Batch statements require parameterSets instead of parameters
|
|
424
|
-
{ [isBatch ? 'parameterSets' : 'parameters']: processedParams }
|
|
425
|
-
: {},
|
|
426
|
-
// Force meta data if set and not a batch
|
|
427
|
-
hydrateColumnNames && !isBatch ? { includeResultMetadata: true } : {},
|
|
428
|
-
// If a transactionId is passed, overwrite any manual input
|
|
429
|
-
config.transactionId ? { transactionId: config.transactionId } : {}
|
|
430
|
-
) // end params
|
|
431
|
-
|
|
432
|
-
try {
|
|
433
|
-
// attempt to run the query
|
|
434
|
-
|
|
435
|
-
// Capture the result for debugging
|
|
436
|
-
let result = await (isBatch
|
|
437
|
-
? config.methods.batchExecuteStatement(params)
|
|
438
|
-
: config.methods.executeStatement(params))
|
|
439
|
-
|
|
440
|
-
// Format and return the results
|
|
441
|
-
return formatResults(result, hydrateColumnNames, args[0].includeResultMetadata === true, formatOptions)
|
|
442
|
-
} catch (e) {
|
|
443
|
-
if (this && this.rollback) {
|
|
444
|
-
let rollback = await config.methods.rollbackTransaction(
|
|
445
|
-
pick(params, ['resourceArn', 'secretArn', 'transactionId'])
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
this.rollback(e, rollback)
|
|
449
|
-
}
|
|
450
|
-
// Throw the error
|
|
451
|
-
throw e
|
|
452
|
-
}
|
|
453
|
-
} // end query
|
|
454
|
-
|
|
455
|
-
/********************************************************************/
|
|
456
|
-
/** TRANSACTION MANAGEMENT **/
|
|
457
|
-
/********************************************************************/
|
|
458
|
-
|
|
459
|
-
// Init a transaction object and return methods
|
|
460
|
-
const transaction = (config, _args) => {
|
|
461
|
-
let args = typeof _args === 'object' ? [_args] : [{}]
|
|
462
|
-
let queries = [] // keep track of queries
|
|
463
|
-
let rollback = () => {} // default rollback event
|
|
464
|
-
|
|
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
|
-
})
|
|
471
|
-
|
|
472
|
-
return {
|
|
473
|
-
query: function (...args) {
|
|
474
|
-
if (typeof args[0] === 'function') {
|
|
475
|
-
queries.push(args[0])
|
|
476
|
-
} else {
|
|
477
|
-
queries.push(() => [...args])
|
|
478
|
-
}
|
|
479
|
-
return this
|
|
480
|
-
},
|
|
481
|
-
rollback: function (fn) {
|
|
482
|
-
if (typeof fn === 'function') {
|
|
483
|
-
rollback = fn
|
|
484
|
-
}
|
|
485
|
-
return this
|
|
486
|
-
},
|
|
487
|
-
commit: async function () {
|
|
488
|
-
return await commit(txConfig, queries, rollback)
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Commit transaction by running queries
|
|
494
|
-
const commit = async (config, queries, rollback) => {
|
|
495
|
-
let results = [] // keep track of results
|
|
496
|
-
|
|
497
|
-
// Start a transaction
|
|
498
|
-
const { transactionId } = await config.methods.beginTransaction(
|
|
499
|
-
pick(config, ['resourceArn', 'secretArn', 'database'])
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
// Add transactionId to the config
|
|
503
|
-
let txConfig = Object.assign(config, { transactionId })
|
|
504
|
-
|
|
505
|
-
// Loop through queries
|
|
506
|
-
for (let i = 0; i < queries.length; i++) {
|
|
507
|
-
// Execute the queries, pass the rollback as context
|
|
508
|
-
let result = await query.apply({ rollback }, [config, queries[i](results[results.length - 1], results)])
|
|
509
|
-
// Add the result to the main results accumulator
|
|
510
|
-
results.push(result)
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Commit our transaction
|
|
514
|
-
const { transactionStatus } = await txConfig.methods.commitTransaction(
|
|
515
|
-
pick(config, ['resourceArn', 'secretArn', 'transactionId'])
|
|
516
|
-
)
|
|
517
|
-
|
|
518
|
-
// Add the transaction status to the results
|
|
519
|
-
results.push({ transactionStatus })
|
|
520
|
-
|
|
521
|
-
// Return the results
|
|
522
|
-
return results
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/********************************************************************/
|
|
526
|
-
/** INSTANTIATION **/
|
|
527
|
-
/********************************************************************/
|
|
528
|
-
|
|
529
|
-
// Export main function
|
|
530
|
-
/**
|
|
531
|
-
* Create a Data API client instance
|
|
532
|
-
* @param {object} params
|
|
533
|
-
* @param {'mysql'|'pg'} [params.engine=mysql] The type of database (MySQL or Postgres)
|
|
534
|
-
* @param {string} params.resourceArn The ARN of your Aurora Serverless Cluster
|
|
535
|
-
* @param {string} params.secretArn The ARN of the secret associated with your
|
|
536
|
-
* database credentials
|
|
537
|
-
* @param {string} [params.database] The name of the database
|
|
538
|
-
* @param {boolean} [params.hydrateColumnNames=true] Return objects with column
|
|
539
|
-
* names as keys
|
|
540
|
-
* @param {object} [params.options={}] Configuration object passed directly
|
|
541
|
-
* into RDSDataService
|
|
542
|
-
* @param {object} [params.formatOptions] Date-related formatting options
|
|
543
|
-
* @param {boolean} [params.formatOptions.deserializeDate=false]
|
|
544
|
-
* @param {boolean} [params.formatOptions.treatAsLocalDate=false]
|
|
545
|
-
*
|
|
546
|
-
*/
|
|
547
|
-
const init = (params) => {
|
|
548
|
-
// Set the options for the RDSDataService
|
|
549
|
-
const options =
|
|
550
|
-
typeof params.options === 'object'
|
|
551
|
-
? params.options
|
|
552
|
-
: params.options !== undefined
|
|
553
|
-
? error(`'options' must be an object`)
|
|
554
|
-
: {}
|
|
555
|
-
|
|
556
|
-
// Update the AWS http agent with the region
|
|
557
|
-
if (typeof params.region === 'string') {
|
|
558
|
-
options.region = params.region
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// Set the configuration for this instance
|
|
562
|
-
const config = {
|
|
563
|
-
// Require engine
|
|
564
|
-
engine: typeof params.engine === 'string' ? params.engine : 'mysql',
|
|
565
|
-
|
|
566
|
-
// Require secretArn
|
|
567
|
-
secretArn: typeof params.secretArn === 'string' ? params.secretArn : error(`'secretArn' string value required`),
|
|
568
|
-
|
|
569
|
-
// Require resourceArn
|
|
570
|
-
resourceArn:
|
|
571
|
-
typeof params.resourceArn === 'string' ? params.resourceArn : error(`'resourceArn' string value required`),
|
|
572
|
-
|
|
573
|
-
// Load optional database
|
|
574
|
-
database:
|
|
575
|
-
typeof params.database === 'string'
|
|
576
|
-
? params.database
|
|
577
|
-
: params.database !== undefined
|
|
578
|
-
? error(`'database' must be a string`)
|
|
579
|
-
: undefined,
|
|
580
|
-
|
|
581
|
-
// Load optional schema DISABLED for now since this isn't used with MySQL
|
|
582
|
-
// schema: typeof params.schema === 'string' ? params.schema
|
|
583
|
-
// : params.schema !== undefined ? error(`'schema' must be a string`)
|
|
584
|
-
// : undefined,
|
|
585
|
-
|
|
586
|
-
// Set hydrateColumnNames (default to true)
|
|
587
|
-
hydrateColumnNames: typeof params.hydrateColumnNames === 'boolean' ? params.hydrateColumnNames : true,
|
|
588
|
-
|
|
589
|
-
// Value formatting options. For date the deserialization is enabled and (re)stored as UTC
|
|
590
|
-
formatOptions: {
|
|
591
|
-
deserializeDate:
|
|
592
|
-
typeof params.formatOptions === 'object' && params.formatOptions.deserializeDate === false ? false : true,
|
|
593
|
-
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate
|
|
594
|
-
},
|
|
595
|
-
|
|
596
|
-
// Create an instance of RDSDataService
|
|
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
|
-
}
|
|
628
|
-
} // end config
|
|
629
|
-
|
|
630
|
-
// Return public methods
|
|
631
|
-
return {
|
|
632
|
-
// Query method, pass config and parameters
|
|
633
|
-
query: (...x) => query(config, ...x),
|
|
634
|
-
// Transaction method, pass config and parameters
|
|
635
|
-
transaction: (x) => transaction(config, x),
|
|
636
|
-
|
|
637
|
-
// Export simplified v3 RDSDataClient methods
|
|
638
|
-
...config.methods
|
|
639
|
-
}
|
|
640
|
-
} // end exports
|
|
641
|
-
|
|
642
|
-
module.exports = init
|