@env0/dynamo-easy
Version:
DynamoDB client for NodeJS and browser with a fluent api to build requests. We take care of the type mapping between JS and DynamoDB, customizable trough typescript decorators.
306 lines • 8.89 kB
JavaScript
import * as tslib_1 from "tslib";
/**
* @module mapper
*/
import { isNumber, isString } from 'lodash';
import { Binary } from './type/binary.type';
import { NullType } from './type/null.type';
import { UndefinedType } from './type/undefined.type';
/**
* @hidden
*/
export function getPropertyPath(modelConstructorOrPropertyMetadata, propertyKey) {
if (modelConstructorOrPropertyMetadata && modelConstructorOrPropertyMetadata.name) {
return "[" + modelConstructorOrPropertyMetadata.name + "::" + propertyKey + "]";
}
else {
return "[unknown::" + propertyKey + "]";
}
}
/**
* @hidden
*/
export function messageWithPath(propertyPath, message) {
if (!!propertyPath) {
return propertyPath + " " + message;
}
else {
return "" + message;
}
}
/**
* @hidden
*/
var BUFFER_TYPES = [
'Buffer',
'File',
'Blob',
'ArrayBuffer',
'DataView',
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
];
/**
* Detects the dynamoDB type to which an collection value should be mapped. Empty collections will be mapped to L(ist).
* Collections of type array where all the values are either String | Number | Binary will be mapped to the corresponding S(et)
* type. If the item types are heterogeneous or it is a non supported set type the returned type will be L(ist).
* The logic for collection fo type Set is the same.
*
* @param {any[] | Set<any>} collection
* @returns {AttributeCollectionType}
* @hidden
*/
export function detectCollectionTypeFromValue(collection) {
if (Array.isArray(collection)) {
return 'L';
}
else if (isSet(collection)) {
if (collection.size) {
var _a = isHomogeneous(collection), homogeneous = _a.homogeneous, type = _a.type;
if (homogeneous) {
switch (type) {
case 'S':
return 'SS';
case 'N':
return 'NS';
case 'B':
return 'BS';
default:
throw new Error("\"Set<CustomType>\" without decorator is not supported. Add the @CollectionProperty() decorator (optionally with {itemType:CustomType}) for a Set<->[L]ist mapping)");
}
}
else {
// sets can not contain items with different types (heterogeneous)
throw new Error("\"Set with values of different types without decorator is not supported. Use an array instead.");
}
}
else {
/*
* an empty Set will not be persisted so we just return an arbitrary Set type, it is only important that it is one of
* S(et)
*/
return 'SS';
}
}
else {
throw new Error('given collection was neither array nor Set -> type could not be detected');
}
}
/**
* @hidden
*/
export function isHomogeneous(collection) {
var e_1, _a;
var collectionAsArray = isSet(collection) ? Array.from(collection) : collection;
var firstValueType = collectionAsArray.length ? detectType(collectionAsArray[0]) : null;
var homogeneous = true;
try {
for (var collectionAsArray_1 = tslib_1.__values(collectionAsArray), collectionAsArray_1_1 = collectionAsArray_1.next(); !collectionAsArray_1_1.done; collectionAsArray_1_1 = collectionAsArray_1.next()) {
var item = collectionAsArray_1_1.value;
var type = detectType(item);
if (type !== firstValueType) {
homogeneous = false;
break;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (collectionAsArray_1_1 && !collectionAsArray_1_1.done && (_a = collectionAsArray_1.return)) _a.call(collectionAsArray_1);
}
finally { if (e_1) throw e_1.error; }
}
if (homogeneous) {
return { homogeneous: true, type: firstValueType };
}
else {
return { homogeneous: false };
}
}
/**
* @hidden
*/
export function isCollection(value) {
return value && (Array.isArray(value) || isSet(value));
}
/**
* @hidden
*/
export function isSet(value) {
return ((value !== null && value !== undefined && value.hasOwnProperty('name') && value.name === 'Set') ||
value instanceof Set);
}
/**
* @hidden
*/
export function detectType(value) {
if (isCollection(value)) {
return detectCollectionTypeFromValue(value);
}
else if (isString(value)) {
return 'S';
}
else if (isNumber(value)) {
return 'N';
}
else if (isBinary(value)) {
return 'B';
}
else if (value === null) {
return 'NULL';
}
else if (typeof value === 'boolean') {
return 'BOOL';
}
else if (typeof value === 'object') {
return 'M';
}
throw new Error("the type for value " + value + " could not be detected");
}
/**
* Will resolve the type based on given property value
* @hidden
*/
export function typeOf(propertyValue, propertyPath) {
if (propertyValue === null) {
return NullType;
}
else if (Array.isArray(propertyValue)) {
return Array;
}
else if (isSet(propertyValue)) {
return Set;
}
else if (propertyValue instanceof Map) {
return Map;
}
else if (isBinary(propertyValue)) {
return Binary;
}
else {
switch (typeof propertyValue) {
case 'string':
return String;
case 'number':
return Number;
case 'boolean':
return Boolean;
case 'undefined':
return UndefinedType;
case 'object':
return Object;
}
}
throw new Error(messageWithPath(propertyPath, "typeof data " + propertyValue + " could not be detected"));
}
/**
* copied from https://github.com/aws/aws-sdk-js/blob/0c974a7ff6749a541594de584b43a040978d4b72/lib/dynamodb/types.js
* should we work with string match
* @hidden
*/
export function typeOfFromDb(attributeValue) {
if (attributeValue) {
var dynamoType = Object.keys(attributeValue)[0];
switch (dynamoType) {
case 'S':
return String;
case 'N':
return Number;
case 'B':
return Binary;
case 'BOOL':
return Boolean;
case 'SS':
case 'NS':
case 'BS':
return Set;
case 'L':
return Array;
case 'M':
return Object;
case 'NULL':
return NullType;
}
}
throw new Error("could not resolve the dynamo db type for attribute value " + attributeValue);
}
/**
* @hidden
*/
export function isBinary(data) {
if (isNode()) {
// TODO LOW:ENHANCEMENT should add || data instanceof Stream
return Buffer.isBuffer(data);
}
else {
return BUFFER_TYPES.some(function (type) { return data !== undefined && data.constructor && (isType(data, type) || typeName(data.constructor) === type); });
}
}
/**
* @hidden
*/
export function isBufferType(type) {
return BUFFER_TYPES.includes(type);
}
/**
* copied from https://github.com/aws/aws-sdk-js/blob/0c974a7ff6749a541594de584b43a040978d4b72/lib/js
* @hidden
*/
export function isType(obj, type) {
// handle cross-"frame" objects
if (typeof type === 'function') {
type = typeName(type);
}
return Object.prototype.toString.call(obj) === "[object " + type + "]";
}
/**
* @hidden
*/
// tslint:disable-next-line:function-constructor
var isGlobalScopeWindow = new Function('try {return this===window;}catch(e){ return false;}')();
/**
* @hidden
*/
export function isBrowser() {
return isGlobalScopeWindow;
}
/**
* @hidden
*/
export function isNode() {
return !isGlobalScopeWindow;
}
/**
* Returns the name of the given Type. null and undefined are special cases were we return 'Null' vs. 'Undefined'
* @hidden
*/
export function typeName(type) {
if (type !== null && type !== undefined) {
if (Object.prototype.hasOwnProperty.call(type, 'name')) {
return type.name;
}
else {
var str = type.toString();
var match = str.match(/^\s*function (.+)\(/);
return match ? match[1] : str;
}
}
else {
if (type === null) {
return 'Null';
}
else if (type === undefined) {
return 'Undefined';
}
}
throw new Error("was not able to resolve type name for type " + type);
}
//# sourceMappingURL=util.js.map