@nova-odm/auto-marshaller
Version:
A data marshaller that converts JavaScript types into Amazon DynamoDB AttributeValues
347 lines (346 loc) • 12.5 kB
JavaScript
"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();
}