ravendb
Version:
RavenDB client for Node.js
354 lines • 13.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TypesAwareObjectMapper = void 0;
const index_js_1 = require("../Exceptions/index.js");
const TypeUtil_js_1 = require("../Utility/TypeUtil.js");
const LogUtil_js_1 = require("../Utility/LogUtil.js");
const ObjectUtil_js_1 = require("../Utility/ObjectUtil.js");
const Constants_js_1 = require("../Constants.js");
const log = (0, LogUtil_js_1.getLogger)({ module: "ObjectMapper" });
class TypesAwareObjectMapper {
_throwMappingErrors = false;
_conventions;
constructor(opts) {
if (opts) {
if (!opts.documentConventions) {
(0, index_js_1.throwError)("InvalidArgumentException", "Document conventions cannot be empty.");
}
this._conventions = opts.documentConventions;
}
}
get throwMappingErrors() {
return this._throwMappingErrors;
}
set throwMappingErrors(value) {
this._throwMappingErrors = value;
}
fromObjectLiteral(rawResult, typeInfo, knownTypes) {
rawResult = ObjectUtil_js_1.ObjectUtil.deepLiteralClone(rawResult);
const typeName = typeInfo ? typeInfo.typeName : null;
const nestedTypes = typeInfo ? typeInfo.nestedTypes : null;
const types = knownTypes || this._conventions.knownEntityTypesByName;
const ctorOrTypeDescriptor = this._getKnownType(typeName, types);
const result = this._instantiateObject(typeName, rawResult, ctorOrTypeDescriptor);
this._applyNestedTypes(result, nestedTypes, types);
return result;
}
_applyNestedTypes(obj, nestedTypes, knownTypes) {
if (!nestedTypes || Object.keys(nestedTypes).length === 0) {
return obj;
}
const nestedTypesKeys = Object.keys(nestedTypes);
nestedTypesKeys.sort();
for (const propertyPath of nestedTypesKeys) {
const typeName = nestedTypes[propertyPath];
const objPathSegments = propertyPath
.replace(/\[/g, "![")
.replace(/\$MAP/g, "!$MAP")
.replace(/\$SET/g, "!$SET")
.split(/[!.]/g);
const fieldContext = this._getFieldContext(obj, objPathSegments);
const fieldContexts = Array.isArray(fieldContext) ? fieldContext : [fieldContext];
for (const [i, c] of fieldContexts.entries()) {
this._applyTypeToNestedProperty(typeName, c, knownTypes);
}
}
return obj;
}
toObjectLiteral(obj, typeInfoCallback, knownTypes) {
const types = (knownTypes || this._conventions.knownEntityTypesByName);
let nestedTypes;
const result = this._makeObjectLiteral(obj, null, (nestedType) => {
nestedTypes = Object.assign(nestedTypes || {}, nestedType);
}, Array.from(types.values()));
let typeName;
if (TypeUtil_js_1.TypeUtil.isClass(obj)) {
typeName = obj.constructor.name;
}
else {
const typeDescriptor = TypeUtil_js_1.TypeUtil.findType(obj, Array.from(types.values()));
typeName = typeDescriptor ? typeDescriptor.name : null;
}
const typeInfo = {};
typeInfo.typeName = typeName || null;
typeInfo.nestedTypes = nestedTypes || {};
if (typeInfoCallback) {
typeInfoCallback(typeInfo);
}
return result;
}
_getFieldContext(parent, objPath) {
if (!parent) {
return null;
}
// eslint-disable-next-line prefer-const
let [field, ...fieldsPathTail] = objPath;
const isFieldArray = field.endsWith("[]");
if (isFieldArray) {
field = field.replace(/\[\]$/g, "");
}
const isFieldSet = field.endsWith("$SET");
if (isFieldSet) {
field = field.replace(/\$SET$/g, "");
}
const isFieldMap = field.endsWith("$MAP");
if (isFieldMap) {
field = field.replace(/\$MAP$/g, "");
}
const fieldNameConvention = this._conventions.serverToLocalFieldNameConverter;
if (fieldNameConvention) {
field = fieldNameConvention(field);
}
let fieldVal = parent[field];
// eslint-disable-next-line no-prototype-builtins
if (!parent.hasOwnProperty(field)) {
if (isFieldArray || isFieldSet || isFieldMap) {
fieldVal = parent;
}
else {
return null;
}
}
if (isFieldArray) {
return this._getFieldContextsForArrayElements(fieldVal, fieldsPathTail);
}
if (isFieldSet) {
return this._getFieldContextsForSetElements(fieldVal, fieldsPathTail);
}
if (isFieldMap) {
return this._getFieldContextsForMapEntries(fieldVal, fieldsPathTail);
}
if (fieldsPathTail.length) {
return this._getFieldContext(parent[field], fieldsPathTail);
}
return {
parent,
field,
getValue() {
return parent[field];
},
setValue(val) {
parent[field] = val;
}
};
}
_getFieldContextsForMapEntries(mapFieldVal, fieldsPathTail) {
const result = Array.from(mapFieldVal.entries()).map(([key, val]) => {
if (!fieldsPathTail.length) {
return {
parent: mapFieldVal,
field: key,
getValue: () => val,
setValue: (newVal) => {
mapFieldVal.set(key, newVal);
}
};
}
else {
return this._getFieldContext(val, fieldsPathTail);
}
});
return this._flattenFieldContexts(result);
}
_getFieldContextsForSetElements(setFieldVal, fieldsPathTail) {
const result = Array.from(setFieldVal).map(x => {
if (!fieldsPathTail.length) {
return {
parent: setFieldVal,
field: x,
getValue: () => x,
setValue: (val) => {
setFieldVal.delete(x);
setFieldVal.add(val);
}
};
}
else {
return this._getFieldContext(x, fieldsPathTail);
}
});
return this._flattenFieldContexts(result);
}
_getFieldContextsForArrayElements(fieldVal, fieldsPathTail) {
const result = fieldVal.map((x, i) => {
if (x === null) {
return null;
}
if (!fieldsPathTail.length) {
return {
parent: fieldVal,
field: i.toString(),
getValue() {
return fieldVal[i];
},
setValue(val) {
fieldVal[i] = val;
}
};
}
else {
return this._getFieldContext(x, fieldsPathTail);
}
});
return this._flattenFieldContexts(result);
}
_flattenFieldContexts(arr) {
return arr.reduce((result, next) => {
if (Array.isArray(next)) {
return result.concat(next);
}
result.push(next);
return result;
}, []);
}
_applyTypeToNestedProperty(fieldTypeName, fieldContext, knownTypes) {
let parent;
let field;
if (fieldContext) {
({ parent, field } = fieldContext);
}
if (typeof parent === "undefined") {
return;
}
const fieldVal = fieldContext.getValue();
if (typeof fieldVal === "undefined") {
return;
}
if (fieldVal === null) {
fieldContext.setValue(null);
return;
}
if (fieldTypeName === "date") {
fieldContext.setValue(this._conventions.dateUtil.parse(fieldVal));
return;
}
if (fieldTypeName === "Set") {
fieldContext.setValue(new Set(fieldVal));
return;
}
if (fieldTypeName === "Map") {
const map = new Map(fieldVal);
fieldContext.setValue(map);
return;
}
if (Array.isArray(fieldVal)) {
for (const [i, item] of fieldVal.entries()) {
this._applyTypeToNestedProperty(fieldTypeName, {
field: i.toString(),
parent: fieldVal,
getValue: () => fieldVal[i],
setValue: (val) => fieldVal[i] = val
}, knownTypes);
}
return;
}
const ctorOrTypeDescriptor = this._getKnownType(fieldTypeName, knownTypes);
const instance = this._instantiateObject(fieldTypeName, fieldVal, ctorOrTypeDescriptor);
fieldContext.setValue(instance);
}
_instantiateObject(typeName, rawValue, ctorOrTypeDescriptor) {
let instance = null;
if (!ctorOrTypeDescriptor) {
instance = Object.assign({}, rawValue);
}
else if (TypeUtil_js_1.TypeUtil.isClass(ctorOrTypeDescriptor)) {
instance = this.createEmptyObject(ctorOrTypeDescriptor, rawValue);
}
else if (TypeUtil_js_1.TypeUtil.isObjectLiteralTypeDescriptor(ctorOrTypeDescriptor)) {
instance = ctorOrTypeDescriptor.construct(rawValue);
}
else {
(0, index_js_1.throwError)("InvalidArgumentException", `Invalid type descriptor for type ${typeName}: ${ctorOrTypeDescriptor}`);
}
return instance;
}
_getKnownType(typeName, knownTypes) {
if (!typeName) {
return null;
}
const ctorOrTypeDescriptor = knownTypes.get(typeName);
if (!ctorOrTypeDescriptor) {
if (this._throwMappingErrors) {
(0, index_js_1.throwError)("MappingError", `Could not find type descriptor '${typeName}'.`);
}
else {
log.warn(`Could not find type descriptor '${typeName}'.`);
}
}
return ctorOrTypeDescriptor;
}
createEmptyObject(ctor, rawValue) {
if (!ctor) {
(0, index_js_1.throwError)("InvalidArgumentException", "ctor argument must not be null or undefined.");
}
return Object.assign(new ctor(), rawValue);
}
_makeObjectLiteral(obj, objPathPrefix, typeInfoCallback, knownTypes, skipTypes = false) {
if (TypeUtil_js_1.TypeUtil.isDate(obj)) {
if (!skipTypes) {
typeInfoCallback({
[objPathPrefix]: "date"
});
}
return this._conventions.dateUtil.stringify(obj);
}
if (TypeUtil_js_1.TypeUtil.isSet(obj)) {
if (!skipTypes) {
typeInfoCallback({
[objPathPrefix]: "Set"
});
}
const newObjPathPrefix = `${objPathPrefix}$SET`;
return Array.from(obj)
.map(x => this._makeObjectLiteral(x, newObjPathPrefix, typeInfoCallback, knownTypes));
}
if (TypeUtil_js_1.TypeUtil.isMap(obj)) {
if (!skipTypes) {
typeInfoCallback({
[objPathPrefix]: "Map"
});
}
const valuePathPrefix = `${objPathPrefix}$MAP`;
const map = obj;
return Array.from(map.entries()).reduce((result, [name, value]) => {
return [
...result,
[
this._makeObjectLiteral(name, valuePathPrefix + "KEY", typeInfoCallback, knownTypes),
this._makeObjectLiteral(value, valuePathPrefix, typeInfoCallback, knownTypes)
]
];
}, []);
}
if (Array.isArray(obj)) {
return obj.map((x, index) => this._makeObjectLiteral(x, `${objPathPrefix}.${index}`, typeInfoCallback, knownTypes));
}
if (TypeUtil_js_1.TypeUtil.isObject(obj)) {
if (objPathPrefix) { // if it's non-root object
const matchedType = TypeUtil_js_1.TypeUtil.findType(obj, knownTypes);
if (!skipTypes
&& matchedType
&& matchedType.name !== "Function") {
typeInfoCallback({ [objPathPrefix]: matchedType.name });
}
}
return Object.keys(obj)
.reduce((result, key) => {
let nestedTypeInfoKey = key;
if (this._conventions.localToServerFieldNameConverter) {
nestedTypeInfoKey = this._conventions.localToServerFieldNameConverter(key);
}
let innerSkipTypes = skipTypes;
if (!skipTypes) {
innerSkipTypes = key === Constants_js_1.CONSTANTS.Documents.Metadata.KEY;
}
const fullPath = objPathPrefix ? `${objPathPrefix}.${nestedTypeInfoKey}` : nestedTypeInfoKey;
result[key] = this._makeObjectLiteral(obj[key], fullPath, typeInfoCallback, knownTypes, innerSkipTypes);
return result;
}, {});
}
return obj;
}
}
exports.TypesAwareObjectMapper = TypesAwareObjectMapper;
//# sourceMappingURL=ObjectMapper.js.map