data-api-client
Advanced tools
Comparing version 1.2.1 to 1.3.0
555
index.js
@@ -39,53 +39,76 @@ 'use strict' | ||
// Simple error function | ||
const error = (...err) => { throw Error(...err) } | ||
const error = (...err) => { | ||
throw Error(...err) | ||
} | ||
// Parse SQL statement from provided arguments | ||
const parseSQL = args => | ||
typeof args[0] === 'string' ? args[0] | ||
: typeof args[0] === 'object' && typeof args[0].sql === 'string' ? args[0].sql | ||
: error('No \'sql\' statement provided.') | ||
const parseSQL = (args) => | ||
typeof args[0] === 'string' | ||
? args[0] | ||
: typeof args[0] === 'object' && typeof args[0].sql === 'string' | ||
? args[0].sql | ||
: error(`No 'sql' statement provided.`) | ||
// Parse the parameters from provided arguments | ||
const parseParams = args => | ||
Array.isArray(args[0].parameters) ? args[0].parameters | ||
: typeof args[0].parameters === 'object' ? [args[0].parameters] | ||
: Array.isArray(args[1]) ? args[1] | ||
: typeof args[1] === 'object' ? [args[1]] | ||
: args[0].parameters ? error('\'parameters\' must be an object or array') | ||
: args[1] ? error('Parameters must be an object or array') | ||
: [] | ||
const parseParams = (args) => | ||
Array.isArray(args[0].parameters) | ||
? args[0].parameters | ||
: typeof args[0].parameters === 'object' | ||
? [args[0].parameters] | ||
: Array.isArray(args[1]) | ||
? args[1] | ||
: typeof args[1] === 'object' | ||
? [args[1]] | ||
: args[0].parameters | ||
? error(`'parameters' must be an object or array`) | ||
: args[1] | ||
? error('Parameters must be an object or array') | ||
: [] | ||
// Parse the supplied database, or default to config | ||
const parseDatabase = (config,args) => | ||
config.transactionId ? config.database | ||
: typeof args[0].database === 'string' ? args[0].database | ||
: args[0].database ? error('\'database\' must be a string.') | ||
: config.database ? config.database | ||
: undefined // removed for #47 - error('No \'database\' provided.') | ||
const parseDatabase = (config, args) => | ||
config.transactionId | ||
? config.database | ||
: typeof args[0].database === 'string' | ||
? args[0].database | ||
: args[0].database | ||
? error(`'database' must be a string.`) | ||
: config.database | ||
? config.database | ||
: undefined // removed for #47 - error('No \'database\' provided.') | ||
// Parse the supplied hydrateColumnNames command, or default to config | ||
const parseHydrate = (config,args) => | ||
typeof args[0].hydrateColumnNames === 'boolean' ? args[0].hydrateColumnNames | ||
: args[0].hydrateColumnNames ? error('\'hydrateColumnNames\' must be a boolean.') | ||
: config.hydrateColumnNames | ||
const parseHydrate = (config, args) => | ||
typeof args[0].hydrateColumnNames === 'boolean' | ||
? args[0].hydrateColumnNames | ||
: args[0].hydrateColumnNames | ||
? error(`'hydrateColumnNames' must be a boolean.`) | ||
: config.hydrateColumnNames | ||
// Parse the supplied format options, or default to config | ||
const parseFormatOptions = (config,args) => | ||
typeof args[0].formatOptions === 'object' ? { | ||
deserializeDate: typeof args[0].formatOptions.deserializeDate === 'boolean' ? args[0].formatOptions.deserializeDate | ||
: args[0].formatOptions.deserializeDate ? error('\'formatOptions.deserializeDate\' must be a boolean.') | ||
: config.formatOptions.deserializeDate, | ||
treatAsLocalDate: typeof args[0].formatOptions.treatAsLocalDate == 'boolean' ? args[0].formatOptions.treatAsLocalDate | ||
: args[0].formatOptions.treatAsLocalDate ? error('\'formatOptions.treatAsLocalDate\' must be a boolean.') | ||
: config.formatOptions.treatAsLocalDate | ||
} | ||
: args[0].formatOptions ? error('\'formatOptions\' must be an object.') | ||
: config.formatOptions | ||
const parseFormatOptions = (config, args) => | ||
typeof args[0].formatOptions === 'object' | ||
? { | ||
deserializeDate: | ||
typeof args[0].formatOptions.deserializeDate === 'boolean' | ||
? args[0].formatOptions.deserializeDate | ||
: args[0].formatOptions.deserializeDate | ||
? error(`'formatOptions.deserializeDate' must be a boolean.`) | ||
: config.formatOptions.deserializeDate, | ||
treatAsLocalDate: | ||
typeof args[0].formatOptions.treatAsLocalDate == 'boolean' | ||
? args[0].formatOptions.treatAsLocalDate | ||
: args[0].formatOptions.treatAsLocalDate | ||
? error(`'formatOptions.treatAsLocalDate' must be a boolean.`) | ||
: config.formatOptions.treatAsLocalDate | ||
} | ||
: args[0].formatOptions | ||
? error(`'formatOptions' must be an object.`) | ||
: config.formatOptions | ||
// Prepare method params w/ supplied inputs if an object is passed | ||
const prepareParams = ({ secretArn,resourceArn },args) => { | ||
const prepareParams = ({ secretArn, resourceArn }, args) => { | ||
return Object.assign( | ||
{ secretArn,resourceArn }, // return Arns | ||
typeof args[0] === 'object' ? | ||
omit(args[0],['hydrateColumnNames','parameters']) : {} // merge any inputs | ||
{ secretArn, resourceArn }, // return Arns | ||
typeof args[0] === 'object' ? omit(args[0], ['hydrateColumnNames', 'parameters']) : {} // merge any inputs | ||
) | ||
@@ -95,31 +118,35 @@ } | ||
// Utility function for removing certain keys from an object | ||
const omit = (obj,values) => Object.keys(obj).reduce((acc,x) => | ||
values.includes(x) ? acc : Object.assign(acc,{ [x]: obj[x] }) | ||
,{}) | ||
const omit = (obj, values) => | ||
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? acc : Object.assign(acc, { [x]: obj[x] })), {}) | ||
// Utility function for picking certain keys from an object | ||
const pick = (obj,values) => Object.keys(obj).reduce((acc,x) => | ||
values.includes(x) ? Object.assign(acc,{ [x]: obj[x] }) : acc | ||
,{}) | ||
const pick = (obj, values) => | ||
Object.keys(obj).reduce((acc, x) => (values.includes(x) ? Object.assign(acc, { [x]: obj[x] }) : acc), {}) | ||
// Utility function for flattening arrays | ||
const flatten = arr => arr.reduce((acc,x) => acc.concat(x),[]) | ||
const flatten = (arr) => arr.reduce((acc, x) => acc.concat(x), []) | ||
// Normize parameters so that they are all in standard format | ||
const normalizeParams = params => params.reduce((acc, p) => | ||
Array.isArray(p) ? acc.concat([normalizeParams(p)]) | ||
: ( | ||
(Object.keys(p).length === 2 && p.name && typeof p.value !== 'undefined') || | ||
(Object.keys(p).length === 3 && p.name && typeof p.value !== 'undefined' && p.cast) | ||
) ? acc.concat(p) | ||
: acc.concat(splitParams(p)) | ||
, []) // end reduce | ||
const normalizeParams = (params) => | ||
params.reduce( | ||
(acc, p) => | ||
Array.isArray(p) | ||
? acc.concat([normalizeParams(p)]) | ||
: (Object.keys(p).length === 2 && p.name && typeof p.value !== 'undefined') || | ||
(Object.keys(p).length === 3 && p.name && typeof p.value !== 'undefined' && p.cast) | ||
? acc.concat(p) | ||
: acc.concat(splitParams(p)), | ||
[] | ||
) // end reduce | ||
// Prepare parameters | ||
const processParams = (engine,sql,sqlParams,params,formatOptions,row=0) => { | ||
const processParams = (engine, sql, sqlParams, params, formatOptions, row = 0) => { | ||
return { | ||
processedParams: params.reduce((acc,p) => { | ||
processedParams: params.reduce((acc, p) => { | ||
if (Array.isArray(p)) { | ||
const result = processParams(engine,sql,sqlParams,p,formatOptions,row) | ||
if (row === 0) { sql = result.escapedSql; row++ } | ||
const result = processParams(engine, sql, sqlParams, p, formatOptions, row) | ||
if (row === 0) { | ||
sql = result.escapedSql | ||
row++ | ||
} | ||
return acc.concat([result.processedParams]) | ||
@@ -130,10 +157,5 @@ } else if (sqlParams[p.name]) { | ||
const regex = new RegExp(':' + p.name + '\\b', 'g') | ||
sql = sql.replace( | ||
regex, | ||
engine === 'pg' | ||
? `:${p.name}::${p.cast}` | ||
: `CAST(:${p.name} AS ${p.cast})` | ||
) | ||
sql = sql.replace(regex, engine === 'pg' ? `:${p.name}::${p.cast}` : `CAST(:${p.name} AS ${p.cast})`) | ||
} | ||
acc.push(formatParam(p.name,p.value,formatOptions)) | ||
acc.push(formatParam(p.name, p.value, formatOptions)) | ||
} else if (row === 0) { | ||
@@ -147,3 +169,3 @@ const regex = new RegExp('::' + p.name + '\\b', 'g') | ||
} | ||
},[]), | ||
}, []), | ||
escapedSql: sql | ||
@@ -154,28 +176,28 @@ } | ||
// Converts parameter to the name/value format | ||
const formatParam = (n,v,formatOptions) => formatType(n,v,getType(v),getTypeHint(v),formatOptions) | ||
const formatParam = (n, v, formatOptions) => formatType(n, v, getType(v), getTypeHint(v), formatOptions) | ||
// Converts object params into name/value format | ||
const splitParams = p => Object.keys(p).reduce((arr,x) => | ||
arr.concat({ name: x, value: p[x] }),[]) | ||
const splitParams = (p) => Object.keys(p).reduce((arr, x) => arr.concat({ name: x, value: p[x] }), []) | ||
// Get all the sql parameters and assign them types | ||
const getSqlParams = sql => { | ||
const getSqlParams = (sql) => { | ||
// TODO: probably need to remove comments from the sql | ||
// TODO: placeholders? | ||
// sql.match(/\:{1,2}\w+|\?+/g).map((p,i) => { | ||
return (sql.match(/:{1,2}\w+/g) || []).map((p) => { | ||
// TODO: future support for placeholder parsing? | ||
// return p === '??' ? { type: 'id' } // identifier | ||
// : p === '?' ? { type: 'ph', label: '__d'+i } // placeholder | ||
return p.startsWith('::') ? { type: 'n_id', label: p.substr(2) } // named id | ||
: { type: 'n_ph', label: p.substr(1) } // named placeholder | ||
}).reduce((acc,x) => { | ||
return Object.assign(acc, | ||
{ | ||
return (sql.match(/:{1,2}\w+/g) || []) | ||
.map((p) => { | ||
// TODO: future support for placeholder parsing? | ||
// return p === '??' ? { type: 'id' } // identifier | ||
// : p === '?' ? { type: 'ph', label: '__d'+i } // placeholder | ||
return p.startsWith('::') | ||
? { type: 'n_id', label: p.substr(2) } // named id | ||
: { type: 'n_ph', label: p.substr(1) } // named placeholder | ||
}) | ||
.reduce((acc, x) => { | ||
return Object.assign(acc, { | ||
[x.label]: { | ||
type: x.type | ||
} | ||
} | ||
) | ||
},{}) // end reduce | ||
}) | ||
}, {}) // end reduce | ||
} | ||
@@ -185,37 +207,44 @@ | ||
// TODO: Support more types as the are released | ||
const getType = val => | ||
typeof val === 'string' ? 'stringValue' | ||
: typeof val === 'boolean' ? 'booleanValue' | ||
: typeof val === 'number' && parseInt(val) === val ? 'longValue' | ||
: typeof val === 'number' && parseFloat(val) === val ? 'doubleValue' | ||
: val === null ? 'isNull' | ||
: isDate(val) ? 'stringValue' | ||
: Buffer.isBuffer(val) ? 'blobValue' | ||
// : Array.isArray(val) ? 'arrayValue' This doesn't work yet | ||
// TODO: there is a 'structValue' now for postgres | ||
: typeof val === 'object' | ||
&& Object.keys(val).length === 1 | ||
&& supportedTypes.includes(Object.keys(val)[0]) ? null | ||
: undefined | ||
const getType = (val) => | ||
typeof val === 'string' | ||
? 'stringValue' | ||
: typeof val === 'boolean' | ||
? 'booleanValue' | ||
: typeof val === 'number' && parseInt(val) === val | ||
? 'longValue' | ||
: typeof val === 'number' && parseFloat(val) === val | ||
? 'doubleValue' | ||
: val === null | ||
? 'isNull' | ||
: isDate(val) | ||
? 'stringValue' | ||
: Buffer.isBuffer(val) | ||
? 'blobValue' | ||
: // : Array.isArray(val) ? 'arrayValue' This doesn't work yet | ||
// TODO: there is a 'structValue' now for postgres | ||
typeof val === 'object' && Object.keys(val).length === 1 && supportedTypes.includes(Object.keys(val)[0]) | ||
? null | ||
: undefined | ||
// Hint to specify the underlying object type for data type mapping | ||
const getTypeHint = val => | ||
isDate(val) ? 'TIMESTAMP' : undefined | ||
const getTypeHint = (val) => (isDate(val) ? 'TIMESTAMP' : undefined) | ||
const isDate = val => | ||
val instanceof Date | ||
const isDate = (val) => val instanceof Date | ||
// Creates a standard Data API parameter using the supplied inputs | ||
const formatType = (name,value,type,typeHint,formatOptions) => { | ||
const formatType = (name, value, type, typeHint, formatOptions) => { | ||
return Object.assign( | ||
typeHint != null ? { name, typeHint } : { name }, | ||
type === null ? { value } | ||
: { | ||
value: { | ||
[type ? type : error(`'${name}' is an invalid type`)] | ||
: type === 'isNull' ? true | ||
: isDate(value) ? formatToTimeStamp(value, formatOptions && formatOptions.treatAsLocalDate) | ||
: value | ||
} | ||
} | ||
type === null | ||
? { value } | ||
: { | ||
value: { | ||
[type ? type : error(`'${name}' is an invalid type`)]: | ||
type === 'isNull' | ||
? true | ||
: isDate(value) | ||
? formatToTimeStamp(value, formatOptions && formatOptions.treatAsLocalDate) | ||
: value | ||
} | ||
} | ||
) | ||
@@ -227,3 +256,3 @@ } // end formatType | ||
const formatToTimeStamp = (date, treatAsLocalDate) => { | ||
const pad = (val,num=2) => '0'.repeat(num-(val + '').length) + val | ||
const pad = (val, num = 2) => '0'.repeat(num - (val + '').length) + val | ||
@@ -239,3 +268,3 @@ const year = treatAsLocalDate ? date.getFullYear() : date.getUTCFullYear() | ||
const fraction = ms <= 0 ? '' : `.${pad(ms,3)}` | ||
const fraction = ms <= 0 ? '' : `.${pad(ms, 3)}` | ||
@@ -248,10 +277,11 @@ return `${year}-${pad(month)}-${pad(day)} ${pad(hours)}:${pad(minutes)}:${pad(seconds)}${fraction}` | ||
// In all other cases convert value to datetime as-is (also values with TZ info) | ||
const formatFromTimeStamp = (value,treatAsLocalDate) => | ||
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d{3})?)?$/.test(value) ? | ||
new Date(value + 'Z') : | ||
new Date(value) | ||
const formatFromTimeStamp = (value, treatAsLocalDate) => | ||
!treatAsLocalDate && /^\d{4}-\d{2}-\d{2}(\s\d{2}:\d{2}:\d{2}(\.\d+)?)?$/.test(value) | ||
? new Date(value + 'Z') | ||
: new Date(value) | ||
// Formats the results of a query response | ||
const formatResults = ( | ||
{ // destructure results | ||
{ | ||
// destructure results | ||
columnMetadata, // ONLY when hydrate or includeResultMetadata is true | ||
@@ -266,82 +296,94 @@ numberOfRecordsUpdated, // ONLY for executeStatement method | ||
formatOptions | ||
) => Object.assign( | ||
includeMeta ? { columnMetadata } : {}, | ||
numberOfRecordsUpdated !== undefined && !records ? { numberOfRecordsUpdated } : {}, | ||
records ? { | ||
records: formatRecords(records, columnMetadata, hydrate, formatOptions) | ||
} : {}, | ||
updateResults ? { updateResults: formatUpdateResults(updateResults) } : {}, | ||
generatedFields && generatedFields.length > 0 ? | ||
{ insertId: generatedFields[0].longValue } : {} | ||
) | ||
) => | ||
Object.assign( | ||
includeMeta ? { columnMetadata } : {}, | ||
numberOfRecordsUpdated !== undefined && !records ? { numberOfRecordsUpdated } : {}, | ||
records | ||
? { | ||
records: formatRecords(records, columnMetadata, hydrate, formatOptions) | ||
} | ||
: {}, | ||
updateResults ? { updateResults: formatUpdateResults(updateResults) } : {}, | ||
generatedFields && generatedFields.length > 0 ? { insertId: generatedFields[0].longValue } : {} | ||
) | ||
// Processes records and either extracts Typed Values into an array, or | ||
// object with named column labels | ||
const formatRecords = (recs,columns,hydrate,formatOptions) => { | ||
const formatRecords = (recs, columns, hydrate, formatOptions) => { | ||
// Create map for efficient value parsing | ||
let fmap = recs && recs[0] ? recs[0].map((x,i) => { | ||
return Object.assign({}, | ||
columns ? { label: columns[i].label, typeName: columns[i].typeName } : {} ) // add column label and typeName | ||
}) : {} | ||
let fmap = | ||
recs && recs[0] | ||
? recs[0].map((x, i) => { | ||
return Object.assign({}, columns ? { label: columns[i].label, typeName: columns[i].typeName } : {}) // add column label and typeName | ||
}) | ||
: {} | ||
// Map over all the records (rows) | ||
return recs ? recs.map(rec => { | ||
return recs | ||
? recs.map((rec) => { | ||
// Reduce each field in the record (row) | ||
return rec.reduce( | ||
(acc, field, i) => { | ||
// If the field is null, always return null | ||
if (field.isNull === true) { | ||
return hydrate // object if hydrate, else array | ||
? Object.assign(acc, { [fmap[i].label]: null }) | ||
: acc.concat(null) | ||
// Reduce each field in the record (row) | ||
return rec.reduce((acc,field,i) => { | ||
// If the field is mapped, return the mapped field | ||
} else if (fmap[i] && fmap[i].field) { | ||
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions) | ||
return hydrate // object if hydrate, else array | ||
? Object.assign(acc, { [fmap[i].label]: value }) | ||
: acc.concat(value) | ||
// If the field is null, always return null | ||
if (field.isNull === true) { | ||
return hydrate ? // object if hydrate, else array | ||
Object.assign(acc,{ [fmap[i].label]: null }) | ||
: acc.concat(null) | ||
// Else discover the field type | ||
} else { | ||
// Look for non-null fields | ||
Object.keys(field).map((type) => { | ||
if (type !== 'isNull' && field[type] !== null) { | ||
fmap[i]['field'] = type | ||
} | ||
}) | ||
// If the field is mapped, return the mapped field | ||
} else if (fmap[i] && fmap[i].field) { | ||
const value = formatRecordValue(field[fmap[i].field],fmap[i].typeName,formatOptions) | ||
return hydrate ? // object if hydrate, else array | ||
Object.assign(acc,{ [fmap[i].label]: value }) | ||
: acc.concat(value) | ||
// Else discover the field type | ||
} else { | ||
// Look for non-null fields | ||
Object.keys(field).map(type => { | ||
if (type !== 'isNull' && field[type] !== null) { | ||
fmap[i]['field'] = type | ||
} | ||
}) | ||
// Return the mapped field (this should NEVER be null) | ||
const value = formatRecordValue(field[fmap[i].field],fmap[i].typeName,formatOptions) | ||
return hydrate ? // object if hydrate, else array | ||
Object.assign(acc,{ [fmap[i].label]: value }) | ||
: acc.concat(value) | ||
} | ||
}, hydrate ? {} : []) // init object if hydrate, else init array | ||
}) : [] // empty record set returns an array | ||
// Return the mapped field (this should NEVER be null) | ||
const value = formatRecordValue(field[fmap[i].field], fmap[i].typeName, formatOptions) | ||
return hydrate // object if hydrate, else array | ||
? Object.assign(acc, { [fmap[i].label]: value }) | ||
: acc.concat(value) | ||
} | ||
}, | ||
hydrate ? {} : [] | ||
) // init object if hydrate, else init array | ||
}) | ||
: [] // empty record set returns an array | ||
} // end formatRecords | ||
// Format record value based on its value, the database column's typeName and the formatting options | ||
const formatRecordValue = (value,typeName,formatOptions) => formatOptions && formatOptions.deserializeDate && | ||
['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMP WITH TIME ZONE'].includes(typeName) | ||
? formatFromTimeStamp(value,(formatOptions && formatOptions.treatAsLocalDate) || typeName === 'TIMESTAMP WITH TIME ZONE') | ||
: value | ||
const formatRecordValue = (value, typeName, formatOptions) => { | ||
if ( | ||
formatOptions && | ||
formatOptions.deserializeDate && | ||
['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMPTZ', 'TIMESTAMP WITH TIME ZONE'].includes(typeName.toUpperCase()) | ||
) { | ||
return formatFromTimeStamp( | ||
value, | ||
(formatOptions && formatOptions.treatAsLocalDate) || typeName === 'TIMESTAMP WITH TIME ZONE' | ||
) | ||
} else if (typeName === 'JSON') { | ||
return JSON.parse(value) | ||
} else { | ||
return value | ||
} | ||
} | ||
// Format updateResults and extract insertIds | ||
const formatUpdateResults = res => res.map(x => { | ||
return x.generatedFields && x.generatedFields.length > 0 ? | ||
{ insertId: x.generatedFields[0].longValue } : {} | ||
}) | ||
const formatUpdateResults = (res) => | ||
res.map((x) => { | ||
return x.generatedFields && x.generatedFields.length > 0 ? { insertId: x.generatedFields[0].longValue } : {} | ||
}) | ||
// Merge configuration data with supplied arguments | ||
const mergeConfig = (initialConfig,args) => | ||
Object.assign(initialConfig,args) | ||
const mergeConfig = (initialConfig, args) => Object.assign(initialConfig, args) | ||
/********************************************************************/ | ||
@@ -352,3 +394,3 @@ /** QUERY MANAGEMENT **/ | ||
// Query function (use standard form for `this` context) | ||
const query = async function(config,..._args) { | ||
const query = async function (config, ..._args) { | ||
// Flatten array if nested arrays (fixes #30) | ||
@@ -362,6 +404,6 @@ const args = Array.isArray(_args[0]) ? flatten(_args) : _args | ||
// Parse hydration setting | ||
const hydrateColumnNames = parseHydrate(config,args) | ||
const hydrateColumnNames = parseHydrate(config, args) | ||
// Parse data format settings | ||
const formatOptions = parseFormatOptions(config,args) | ||
const formatOptions = parseFormatOptions(config, args) | ||
@@ -372,19 +414,19 @@ // Parse and normalize parameters | ||
// Process parameters and escape necessary SQL | ||
const { processedParams,escapedSql } = processParams(config.engine,sql,sqlParams,parameters,formatOptions) | ||
const { processedParams, escapedSql } = processParams(config.engine, sql, sqlParams, parameters, formatOptions) | ||
// Determine if this is a batch request | ||
const isBatch = processedParams.length > 0 | ||
&& Array.isArray(processedParams[0]) | ||
const isBatch = processedParams.length > 0 && Array.isArray(processedParams[0]) | ||
// Create/format the parameters | ||
const params = Object.assign( | ||
prepareParams(config,args), | ||
prepareParams(config, args), | ||
{ | ||
database: parseDatabase(config,args), // add database | ||
database: parseDatabase(config, args), // add database | ||
sql: escapedSql // add escaped sql statement | ||
}, | ||
// Only include parameters if they exist | ||
processedParams.length > 0 ? | ||
// Batch statements require parameterSets instead of parameters | ||
{ [isBatch ? 'parameterSets' : 'parameters']: processedParams } : {}, | ||
processedParams.length > 0 | ||
? // Batch statements require parameterSets instead of parameters | ||
{ [isBatch ? 'parameterSets' : 'parameters']: processedParams } | ||
: {}, | ||
// Force meta data if set and not a batch | ||
@@ -396,24 +438,19 @@ hydrateColumnNames && !isBatch ? { includeResultMetadata: true } : {}, | ||
try { // attempt to run the query | ||
try { | ||
// attempt to run the query | ||
// Capture the result for debugging | ||
let result = await (isBatch ? config.RDS.batchExecuteStatement(params).promise() | ||
let result = await (isBatch | ||
? config.RDS.batchExecuteStatement(params).promise() | ||
: config.RDS.executeStatement(params).promise()) | ||
// Format and return the results | ||
return formatResults( | ||
result, | ||
hydrateColumnNames, | ||
args[0].includeResultMetadata === true, | ||
formatOptions | ||
) | ||
} catch(e) { | ||
return formatResults(result, hydrateColumnNames, args[0].includeResultMetadata === true, formatOptions) | ||
} catch (e) { | ||
if (this && this.rollback) { | ||
let rollback = await config.RDS.rollbackTransaction( | ||
pick(params,['resourceArn','secretArn','transactionId']) | ||
pick(params, ['resourceArn', 'secretArn', 'transactionId']) | ||
).promise() | ||
this.rollback(e,rollback) | ||
this.rollback(e, rollback) | ||
} | ||
@@ -423,7 +460,4 @@ // Throw the error | ||
} | ||
} // end query | ||
/********************************************************************/ | ||
@@ -434,4 +468,3 @@ /** TRANSACTION MANAGEMENT **/ | ||
// Init a transaction object and return methods | ||
const transaction = (config,_args) => { | ||
const transaction = (config, _args) => { | ||
let args = typeof _args === 'object' ? [_args] : [{}] | ||
@@ -441,14 +474,11 @@ let queries = [] // keep track of queries | ||
const txConfig = Object.assign( | ||
prepareParams(config,args), | ||
{ | ||
database: parseDatabase(config,args), // add database | ||
hydrateColumnNames: parseHydrate(config,args), // add hydrate | ||
formatOptions: parseFormatOptions(config,args), // add formatOptions | ||
RDS: config.RDS // reference the RDSDataService instance | ||
} | ||
) | ||
const txConfig = Object.assign(prepareParams(config, args), { | ||
database: parseDatabase(config, args), // add database | ||
hydrateColumnNames: parseHydrate(config, args), // add hydrate | ||
formatOptions: parseFormatOptions(config, args), // add formatOptions | ||
RDS: config.RDS // reference the RDSDataService instance | ||
}) | ||
return { | ||
query: function(...args) { | ||
query: function (...args) { | ||
if (typeof args[0] === 'function') { | ||
@@ -461,7 +491,11 @@ queries.push(args[0]) | ||
}, | ||
rollback: function(fn) { | ||
if (typeof fn === 'function') { rollback = fn } | ||
rollback: function (fn) { | ||
if (typeof fn === 'function') { | ||
rollback = fn | ||
} | ||
return this | ||
}, | ||
commit: async function() { return await commit(txConfig,queries,rollback) } | ||
commit: async function () { | ||
return await commit(txConfig, queries, rollback) | ||
} | ||
} | ||
@@ -471,4 +505,3 @@ } | ||
// Commit transaction by running queries | ||
const commit = async (config,queries,rollback) => { | ||
const commit = async (config, queries, rollback) => { | ||
let results = [] // keep track of results | ||
@@ -478,3 +511,3 @@ | ||
const { transactionId } = await config.RDS.beginTransaction( | ||
pick(config,['resourceArn','secretArn','database']) | ||
pick(config, ['resourceArn', 'secretArn', 'database']) | ||
).promise() | ||
@@ -488,3 +521,3 @@ | ||
// Execute the queries, pass the rollback as context | ||
let result = await query.apply({rollback},[config,queries[i](results[results.length-1],results)]) | ||
let result = await query.apply({ rollback }, [config, queries[i](results[results.length - 1], results)]) | ||
// Add the result to the main results accumulator | ||
@@ -496,7 +529,7 @@ results.push(result) | ||
const { transactionStatus } = await txConfig.RDS.commitTransaction( | ||
pick(config,['resourceArn','secretArn','transactionId']) | ||
pick(config, ['resourceArn', 'secretArn', 'transactionId']) | ||
).promise() | ||
// Add the transaction status to the results | ||
results.push({transactionStatus}) | ||
results.push({ transactionStatus }) | ||
@@ -532,8 +565,10 @@ // Return the results | ||
*/ | ||
const init = params => { | ||
const init = (params) => { | ||
// Set the options for the RDSDataService | ||
const options = typeof params.options === 'object' ? params.options | ||
: params.options !== undefined ? error('\'options\' must be an object') | ||
: {} | ||
const options = | ||
typeof params.options === 'object' | ||
? params.options | ||
: params.options !== undefined | ||
? error(`'options' must be an object`) | ||
: {} | ||
@@ -553,21 +588,18 @@ // Update the AWS http agent with the region | ||
// Require engine | ||
engine: typeof params.engine === 'string' ? | ||
params.engine | ||
: 'mysql', | ||
engine: typeof params.engine === 'string' ? params.engine : 'mysql', | ||
// Require secretArn | ||
secretArn: typeof params.secretArn === 'string' ? | ||
params.secretArn | ||
: error('\'secretArn\' string value required'), | ||
secretArn: typeof params.secretArn === 'string' ? params.secretArn : error(`'secretArn' string value required`), | ||
// Require resourceArn | ||
resourceArn: typeof params.resourceArn === 'string' ? | ||
params.resourceArn | ||
: error('\'resourceArn\' string value required'), | ||
resourceArn: | ||
typeof params.resourceArn === 'string' ? params.resourceArn : error(`'resourceArn' string value required`), | ||
// Load optional database | ||
database: typeof params.database === 'string' ? | ||
params.database | ||
: params.database !== undefined ? error('\'database\' must be a string') | ||
: undefined, | ||
database: | ||
typeof params.database === 'string' | ||
? params.database | ||
: params.database !== undefined | ||
? error(`'database' must be a string`) | ||
: undefined, | ||
@@ -580,5 +612,3 @@ // Load optional schema DISABLED for now since this isn't used with MySQL | ||
// Set hydrateColumnNames (default to true) | ||
hydrateColumnNames: | ||
typeof params.hydrateColumnNames === 'boolean' ? | ||
params.hydrateColumnNames : true, | ||
hydrateColumnNames: typeof params.hydrateColumnNames === 'boolean' ? params.hydrateColumnNames : true, | ||
@@ -589,4 +619,3 @@ // Value formatting options. For date the deserialization is enabled and (re)stored as UTC | ||
typeof params.formatOptions === 'object' && params.formatOptions.deserializeDate === false ? false : true, | ||
treatAsLocalDate: | ||
typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate | ||
treatAsLocalDate: typeof params.formatOptions === 'object' && params.formatOptions.treatAsLocalDate | ||
}, | ||
@@ -596,4 +625,3 @@ | ||
// Create an instance of RDSDataService | ||
RDS: new AWS.RDSDataService(options) | ||
RDS: params.AWS ? new params.AWS.RDSDataService(options) : new AWS.RDSDataService(options) | ||
} // end config | ||
@@ -604,5 +632,5 @@ | ||
// Query method, pass config and parameters | ||
query: (...x) => query(config,...x), | ||
query: (...x) => query(config, ...x), | ||
// Transaction method, pass config and parameters | ||
transaction: (x) => transaction(config,x), | ||
transaction: (x) => transaction(config, x), | ||
@@ -612,24 +640,15 @@ // Export promisified versions of the RDSDataService methods | ||
config.RDS.batchExecuteStatement( | ||
mergeConfig(pick(config,['resourceArn','secretArn','database']),args) | ||
mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args) | ||
).promise(), | ||
beginTransaction: (args) => | ||
config.RDS.beginTransaction( | ||
mergeConfig(pick(config,['resourceArn','secretArn','database']),args) | ||
).promise(), | ||
config.RDS.beginTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(), | ||
commitTransaction: (args) => | ||
config.RDS.commitTransaction( | ||
mergeConfig(pick(config,['resourceArn','secretArn']),args) | ||
).promise(), | ||
config.RDS.commitTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise(), | ||
executeStatement: (args) => | ||
config.RDS.executeStatement( | ||
mergeConfig(pick(config,['resourceArn','secretArn','database']),args) | ||
).promise(), | ||
config.RDS.executeStatement(mergeConfig(pick(config, ['resourceArn', 'secretArn', 'database']), args)).promise(), | ||
rollbackTransaction: (args) => | ||
config.RDS.rollbackTransaction( | ||
mergeConfig(pick(config,['resourceArn','secretArn']),args) | ||
).promise() | ||
config.RDS.rollbackTransaction(mergeConfig(pick(config, ['resourceArn', 'secretArn']), args)).promise() | ||
} | ||
} // end exports | ||
module.exports = init |
{ | ||
"name": "data-api-client", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "A lightweight wrapper that simplifies working with the Amazon Aurora Serverless Data API", | ||
@@ -29,4 +29,6 @@ "main": "index.js", | ||
"aws-sdk": "^2.811.0", | ||
"eslint": "^6.8.0", | ||
"eslint": "^8.12.0", | ||
"eslint-config-prettier": "^8.5.0", | ||
"jest": "^27.5.1", | ||
"prettier": "^2.6.2", | ||
"rewire": "^6.0.0" | ||
@@ -33,0 +35,0 @@ }, |
@@ -137,2 +137,3 @@ ![Aurora Serverless Data API Client](https://user-images.githubusercontent.com/2053544/79285017-44053500-7e8a-11ea-8515-998ccf9c2d2e.png) | ||
| -------- | ---- | ----------- | ------- | | ||
| AWS | `AWS` | A custom `aws-sdk` instance | | | ||
| resourceArn | `string` | The ARN of your Aurora Serverless Cluster. This value is *required*, but can be overridden when querying. | | | ||
@@ -340,2 +341,26 @@ | secretArn | `string` | The ARN of the secret associated with your database credentials. This is *required*, but can be overridden when querying. | | | ||
## Custom AWS instance | ||
`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. | ||
```javascript | ||
// Instantiate data-api-client with a custom AWS instance | ||
const data = require('data-api-client')({ | ||
AWS: customAWS, | ||
... | ||
}) | ||
``` | ||
Custom AWS parameter allows to introduce, e.g. tracing Data API calls through X-Ray with: | ||
```javascript | ||
const AWSXRay = require('aws-xray-sdk') | ||
const AWS = AWSXRay.captureAWS(require('aws-sdk')) | ||
const data = require('data-api-client')({ | ||
AWS: AWS, | ||
... | ||
}) | ||
``` | ||
## Data API Limitations / Wonkiness | ||
@@ -342,0 +367,0 @@ 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. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49931
547
486
6