UNPKG

@nova-odm/auto-marshaller

Version:

A data marshaller that converts JavaScript types into Amazon DynamoDB AttributeValues

347 lines (346 loc) 12.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Marshaller = exports.InvalidHandlingStrategies = exports.EmptyHandlingStrategies = void 0; var tslib_1 = require("tslib"); var BinarySet_1 = require("./BinarySet"); var isArrayBuffer_1 = require("./isArrayBuffer"); var NumberValue_1 = require("./NumberValue"); var NumberValueSet_1 = require("./NumberValueSet"); exports.EmptyHandlingStrategies = { omit: 'omit', nullify: 'nullify', leave: 'leave', }; exports.InvalidHandlingStrategies = { /** * Remove any invalid values from the serialized output. */ omit: 'omit', /** * Throw an error when an unserializable value is encountered. */ throw: 'throw', }; /** * A class that will convert arbitrary JavaScript data types to their most * logical in the DynamoDB schema. */ var Marshaller = /** @class */ (function () { function Marshaller(_a) { var _b = _a === void 0 ? {} : _a, _c = _b.onEmpty, onEmpty = _c === void 0 ? 'leave' : _c, _d = _b.onInvalid, onInvalid = _d === void 0 ? 'throw' : _d, _e = _b.unwrapNumbers, unwrapNumbers = _e === void 0 ? false : _e; this.onEmpty = onEmpty; this.onInvalid = onInvalid; this.unwrapNumbers = unwrapNumbers; } /** * Convert a JavaScript object with string keys and arbitrary values into an * object with string keys and DynamoDB AttributeValue objects as values. */ Marshaller.prototype.marshallItem = function (item) { var value = this.marshallValue(item); if (!(value && value.M) && this.onInvalid === 'throw') { throw new Error("Cannot serialize ".concat(typeof item, " as an attribute map")); } return value && value.M ? value.M : {}; }; /** * Convert a JavaScript value into a DynamoDB AttributeValue or `undefined`. * * @throws Error if the value cannot be converted to a DynamoDB type and the * marshaller has been configured to throw on invalid input. */ Marshaller.prototype.marshallValue = function (value) { switch (typeof value) { case 'boolean': return { BOOL: value }; case 'number': return { N: value.toString(10) }; case 'object': return this.marshallComplexType(value); case 'string': return value ? { S: value } : this.handleEmptyString(value); case 'undefined': return undefined; case 'function': case 'symbol': default: if (this.onInvalid === 'throw') { throw new Error("Cannot serialize values of the ".concat(typeof value, " type")); } } }; /** * Convert a DynamoDB operation result (an object with string keys and * AttributeValue values) to an object with string keys and native * JavaScript values. */ Marshaller.prototype.unmarshallItem = function (item) { return this.unmarshallValue({ M: item }); }; /** * Convert a DynamoDB AttributeValue into a native JavaScript value. */ Marshaller.prototype.unmarshallValue = function (item) { var e_1, _a, e_2, _b; var _this = this; if (item.S !== undefined) { return item.S; } if (item.N !== undefined) { return this.unwrapNumbers ? Number(item.N) : new NumberValue_1.NumberValue(item.N); } if (item.B !== undefined) { return item.B; } if (item.BOOL !== undefined) { return item.BOOL; } if (item.NULL !== undefined) { return null; } if (item.SS !== undefined) { var set = new Set(); try { for (var _c = tslib_1.__values(item.SS), _d = _c.next(); !_d.done; _d = _c.next()) { var member = _d.value; set.add(member); } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } return set; } if (item.NS !== undefined) { if (this.unwrapNumbers) { var set = new Set(); try { for (var _e = tslib_1.__values(item.NS), _f = _e.next(); !_f.done; _f = _e.next()) { var member = _f.value; set.add(Number(member)); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_2) throw e_2.error; } } return set; } return new NumberValueSet_1.NumberValueSet(item.NS.map(function (numberString) { return new NumberValue_1.NumberValue(numberString); })); } if (item.BS !== undefined) { return new BinarySet_1.BinarySet(item.BS); } if (item.L !== undefined) { return item.L.map(this.unmarshallValue.bind(this)); } var _g = item.M, M = _g === void 0 ? {} : _g; return Object.keys(M).reduce(function (map, key) { map[key] = _this.unmarshallValue(M[key]); return map; }, {}); }; Marshaller.prototype.marshallComplexType = function (value) { if (value === null) { return { NULL: true }; } if (NumberValue_1.NumberValue.isNumberValue(value)) { return { N: value.toString() }; } if (isBinaryValue(value)) { return this.marshallBinaryValue(value); } if (isSet(value)) { return this.marshallSet(value); } if (isMap(value)) { return this.marshallMap(value); } if (isIterable(value)) { return this.marshallList(value); } return this.marshallObject(value); }; Marshaller.prototype.marshallBinaryValue = function (binary) { if (binary.byteLength > 0 || this.onEmpty === 'leave') { if (binary.buffer) { return { B: new Uint8Array(binary.buffer) }; } return { B: new Uint8Array(binary) }; } if (this.onEmpty === 'nullify') { return { NULL: true }; } }; Marshaller.prototype.marshallList = function (list) { var e_3, _a; var values = []; try { for (var list_1 = tslib_1.__values(list), list_1_1 = list_1.next(); !list_1_1.done; list_1_1 = list_1.next()) { var value = list_1_1.value; var marshalled = this.marshallValue(value); if (marshalled) { values.push(marshalled); } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (list_1_1 && !list_1_1.done && (_a = list_1.return)) _a.call(list_1); } finally { if (e_3) throw e_3.error; } } return { L: values }; }; Marshaller.prototype.marshallMap = function (map) { var e_4, _a; var members = {}; try { for (var map_1 = tslib_1.__values(map), map_1_1 = map_1.next(); !map_1_1.done; map_1_1 = map_1.next()) { var _b = tslib_1.__read(map_1_1.value, 2), key = _b[0], value = _b[1]; if (typeof key !== 'string') { if (this.onInvalid === 'omit') { continue; } throw new Error("MapAttributeValues must have strings as keys; ".concat(typeof key, " received instead")); } var marshalled = this.marshallValue(value); if (marshalled) { members[key] = marshalled; } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (map_1_1 && !map_1_1.done && (_a = map_1.return)) _a.call(map_1); } finally { if (e_4) throw e_4.error; } } return { M: members }; }; Marshaller.prototype.marshallObject = function (object) { var _this = this; return { M: Object.keys(object).reduce(function (map, key) { var marshalled = _this.marshallValue(object[key]); if (marshalled) { map[key] = marshalled; } return map; }, {}), }; }; Marshaller.prototype.marshallSet = function (arg) { switch (getSetType(arg[Symbol.iterator]().next().value)) { case 'binary': return this.collectSet(arg, isBinaryEmpty, 'BS', 'binary'); case 'number': return this.collectSet(arg, isNumberEmpty, 'NS', 'number', stringifyNumber); case 'string': return this.collectSet(arg, isStringEmpty, 'SS', 'string'); case 'unknown': if (this.onInvalid === 'throw') { throw new Error('Sets must be composed of strings,' + ' binary values, or numbers'); } return undefined; case 'undefined': if (this.onEmpty === 'nullify') { return { NULL: true }; } } }; Marshaller.prototype.collectSet = function (set, isEmpty, tag, elementType, transform) { var e_5, _a, _b; var values = []; try { for (var set_1 = tslib_1.__values(set), set_1_1 = set_1.next(); !set_1_1.done; set_1_1 = set_1.next()) { var element = set_1_1.value; if (getSetType(element) !== elementType) { if (this.onInvalid === 'omit') { continue; } throw new Error("Unable to serialize ".concat(typeof element, " as a member of a ").concat(elementType, " set")); } if (!isEmpty(element) || this.onEmpty === 'leave') { values.push(transform ? transform(element) : element); } } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (set_1_1 && !set_1_1.done && (_a = set_1.return)) _a.call(set_1); } finally { if (e_5) throw e_5.error; } } if (values.length > 0 || this.onEmpty === 'leave') { // @ts-ignore return _b = {}, _b[tag] = values, _b; } if (this.onEmpty === 'nullify') { return { NULL: true }; } }; Marshaller.prototype.handleEmptyString = function (value) { switch (this.onEmpty) { case 'leave': return { S: value }; case 'nullify': return { NULL: true }; } }; return Marshaller; }()); exports.Marshaller = Marshaller; function getSetType(arg) { var type = typeof arg; if (type === 'string' || type === 'number' || type === 'undefined') { return type; } if (NumberValue_1.NumberValue.isNumberValue(arg)) { return 'number'; } if (ArrayBuffer.isView(arg) || (0, isArrayBuffer_1.isArrayBuffer)(arg)) { return 'binary'; } return 'unknown'; } function isBinaryEmpty(arg) { return arg.byteLength === 0; } function isBinaryValue(arg) { return ArrayBuffer.isView(arg) || (0, isArrayBuffer_1.isArrayBuffer)(arg); } function isIterable(arg) { return Boolean(arg) && typeof arg[Symbol.iterator] === 'function'; } function isMap(arg) { return Boolean(arg) && Object.prototype.toString.call(arg) === '[object Map]'; } function isNumberEmpty() { return false; } function isSet(arg) { return Boolean(arg) && Object.prototype.toString.call(arg) === '[object Set]'; } function isStringEmpty(arg) { return arg.length === 0; } function stringifyNumber(arg) { return arg.toString(); }