@typeheim/orm-on-fire
Version:
Firestore ORM
298 lines • 14 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MutationTracker = exports.EntityManager = void 0;
const Reference_1 = require("../Model/Reference");
const DocInitializer_1 = require("./DocInitializer");
const DocPersistenceManager_1 = require("./DocPersistenceManager");
const DocReference_1 = require("./DocReference");
const singletons_1 = require("../singletons");
// import FieldValue = types.FieldValue - somehow fails TS
class EntityManager {
constructor(metadata, entityConstructor, collectionReference) {
this.metadata = metadata;
this.entityConstructor = entityConstructor;
this.collectionReference = collectionReference;
}
fromSnapshot(docSnapshot) {
if (!docSnapshot.exists) {
return null;
}
let data = docSnapshot.data();
let entity = new this.entityConstructor();
const fields = this.metadata.fields;
entity['id'] = docSnapshot.id;
this.metadata.fields.forEach(field => {
var _a, _b, _c, _d, _e, _f;
if (data[field.name] === undefined) {
return;
}
else if ((((_a = data[field.name]) === null || _a === void 0 ? void 0 : _a.toDate) !== undefined && typeof ((_b = data[field.name]) === null || _b === void 0 ? void 0 : _b.toDate) === 'function') || (typeof data[field.name] === 'object' && ((_d = (_c = data[field.name]) === null || _c === void 0 ? void 0 : _c.constructor) === null || _d === void 0 ? void 0 : _d.name) === 'Timestamp')) {
entity[field.name] = (_e = data[field.name]) === null || _e === void 0 ? void 0 : _e.toDate();
}
else if ((field === null || field === void 0 ? void 0 : field.isDate) && data[field.name] && ((_f = data[field.name]) === null || _f === void 0 ? void 0 : _f.length) > 0) {
try {
entity[field.name] = new Date(data[field.name]);
}
catch (error) {
console.error(error);
}
}
else if (typeof data[field.name] === 'object' && (field === null || field === void 0 ? void 0 : field.isMap) && (typeof (field === null || field === void 0 ? void 0 : field.constructor) === 'object' || typeof (field === null || field === void 0 ? void 0 : field.constructor) === 'function')) {
if (Array.isArray(data[field.name])) {
entity[field.name] = data[field.name].map(obj => Object.assign(new field.constructor(), obj));
}
else {
entity[field.name] = Object.assign(new field.constructor(), data[field.name]);
}
}
else {
entity[field.name] = data[field.name];
}
});
if (!entity['toJSON']) {
//@todo need to add support for passing additional properties
entity['toJSON'] = () => {
let jsonData = {};
fields.forEach(field => {
if (entity[field.name] !== undefined) {
jsonData[field.name] = entity[field.name];
}
});
return jsonData;
};
}
this.attachOrmMetadataToEntity(entity, docSnapshot.ref);
this.attachSubCollectionsToEntity(entity, docSnapshot.ref);
this.attachRefsToEntity(entity, data);
if (typeof entity.init === 'function') {
// @todo handle promises
entity.init();
}
return entity;
}
createTextIndex(text) {
const arrName = [];
let curName = '';
text.split('').forEach(letter => {
curName += letter.toLowerCase();
arrName.push(curName);
});
return arrName;
}
createReverseTextIndex(text) {
const arrName = [];
let curName = '';
text.split('').reverse().forEach(letter => {
curName += letter.toLowerCase();
arrName.push(curName);
});
return arrName;
}
createEntity() {
let entity = new this.entityConstructor();
this.attachMetadataToNewEntity(entity);
return entity;
}
attachSubCollectionsToEntity(entity, docReference) {
let subCollectionsMetadata = this.metadata.collectionRefs;
if (subCollectionsMetadata.length == 0) {
return;
}
subCollectionsMetadata.forEach(subCollection => {
entity[subCollection.fieldName] = singletons_1.CollectionFactory.createWithRef(subCollection.entity, docReference);
});
}
attachRefsToEntity(entity, data) {
let docRefs = this.metadata.docRefs;
for (let fieldName in docRefs) {
entity[fieldName] = new Reference_1.Reference(docRefs[fieldName].entity, entity);
if (data[fieldName] !== undefined) {
entity[fieldName].___attachDockRef(DocReference_1.DocReference.fromNativeRef(data[fieldName]));
}
}
}
extractDataFromEntity(entity) {
var _a, _b, _c, _d;
const fields = this.metadata.fields;
let dataToSave = {};
let changes = null;
if (!(((_a = entity === null || entity === void 0 ? void 0 : entity.__ormOnFire) === null || _a === void 0 ? void 0 : _a.isNew) || entity.__ormOnFire === undefined)) {
// not new entities require mutation check
changes = (_c = (_b = entity === null || entity === void 0 ? void 0 : entity.__ormOnFire) === null || _b === void 0 ? void 0 : _b.mutation) === null || _c === void 0 ? void 0 : _c.getChanges(entity);
}
fields.forEach(field => {
var _a, _b;
if ((field === null || field === void 0 ? void 0 : field.isDate) && (field === null || field === void 0 ? void 0 : field.updateOnSave)) {
let date = new Date();
entity[field.name] = date;
dataToSave[field.name] = date;
}
else if ((field === null || field === void 0 ? void 0 : field.isDate) && (field === null || field === void 0 ? void 0 : field.generateOnCreate) && (((_a = entity === null || entity === void 0 ? void 0 : entity.__ormOnFire) === null || _a === void 0 ? void 0 : _a.isNew) || entity.__ormOnFire === undefined)) {
let date = new Date();
entity[field.name] = date;
dataToSave[field.name] = date;
}
if (changes && field.name in changes) {
dataToSave[field.name] = changes[field.name];
}
else if ((((_b = entity === null || entity === void 0 ? void 0 : entity.__ormOnFire) === null || _b === void 0 ? void 0 : _b.isNew) || entity.__ormOnFire === undefined) && entity[field.name] !== undefined) {
// this condition required only for new entities
if ((field === null || field === void 0 ? void 0 : field.isMap) && Array.isArray(entity[field.name])) {
dataToSave[field.name] = entity[field.name].map(obj => {
return Object.assign({}, obj);
});
}
else if (field === null || field === void 0 ? void 0 : field.isMap) {
dataToSave[field.name] = Object.assign({}, entity[field.name]);
}
else {
dataToSave[field.name] = entity[field.name];
}
}
});
const docRefs = this.metadata.docRefs;
for (let fieldName in docRefs) {
let docRef = (_d = entity[fieldName]) === null || _d === void 0 ? void 0 : _d.___docReference;
if (docRef) {
dataToSave[fieldName] = docRef.nativeRef;
}
}
return dataToSave;
}
refreshNewEntity(entity, docReference) {
this.attachOrmMetadataToEntity(entity, docReference);
this.attachSubCollectionsToEntity(entity, docReference);
}
attachOrmMetadataToEntity(entity, docReference) {
let persistenceManager = new DocPersistenceManager_1.DocPersistenceManager(docReference);
entity['__ormOnFire'] = {
isNew: false,
docRef: DocReference_1.DocReference.fromNativeRef(docReference),
mutation: this.createMutationTracker(entity),
save: () => {
return persistenceManager.update(this.extractDataFromEntity(entity), entity['__ormOnFire'].mutation);
},
remove: () => {
return persistenceManager.remove();
},
};
}
attachMetadataToNewEntity(entity) {
let docInitializer = new DocInitializer_1.DocInitializer(entity, this);
entity['__ormOnFire'] = {
isNew: true,
docRef: null,
mutation: this.createMutationTracker(entity),
save: () => {
return docInitializer.addTo(this.collectionReference);
},
remove: () => {
// @todo throw exceptions
return false;
},
};
}
createMutationTracker(entity) {
return new MutationTracker(entity, this.metadata.fields);
}
}
exports.EntityManager = EntityManager;
// @todo - change mutation tracker to use snapshot as source of original data
class MutationTracker {
constructor(entity, fields) {
this.copy = {};
this.fields = fields;
this.entity = entity;
this.refreshEntity();
}
refreshEntity() {
let entity = this.entity;
let fields = this.fields;
this.copy = {};
fields.forEach(field => {
if (entity[field.name] === undefined) {
return;
}
if (Array.isArray(entity[field.name])) {
// this.copy[field.name] = entity[field.name]?.slice()
this.copy[field.name] = this.deepCopy(entity[field.name]);
}
else if (field.isMap) {
this.copy[field.name] = Object.assign({}, entity[field.name]);
}
else if (typeof entity[field.name] === 'object' && entity[field.name] instanceof Date) {
this.copy[field.name] = new Date(entity[field.name].getTime());
}
else if (typeof entity[field.name] === 'object') {
this.copy[field.name] = Object.assign({}, entity[field.name]);
}
else {
this.copy[field.name] = entity[field.name];
}
});
}
getChanges(entity) {
let changes = {};
this.fields.forEach(field => {
var _a, _b;
// @todo - half of checks disabled because of instability. Need further tetsing
if (entity[field.name] === undefined) {
if (this.copy[field.name] !== undefined) {
// changes[field.name] = FieldValue.delete() - somehow fails TS
changes[field.name] = undefined;
}
else {
return;
}
}
else if (Array.isArray(entity[field.name])) { // array should be checked explicitly
changes[field.name] = entity[field.name];
// if (JSON.stringify(entity[field.name]) !== JSON.stringify(this.copy[field.name])) {
// changes[field.name] = entity[field.name]
// }
}
else if (typeof entity[field.name] === 'object' && entity[field.name] !== null) {
// dates must be compared by time
// if (entity[field.name] instanceof Date && entity[field.name]?.getTime() !== this.copy[field.name]?.getTime()) {
// changes[field.name] = entity[field.name]
// } else if (JSON.stringify(entity[field.name]) !== JSON.stringify(this.copy[field.name])) {
// // objects need deep equality compare
// changes[field.name] = { ...entity[field.name] }
// }
//dates must be compared by time
if (entity[field.name] instanceof Date) {
if (this.copy[field.name] && ((_a = entity[field.name]) === null || _a === void 0 ? void 0 : _a.getTime()) !== ((_b = this.copy[field.name]) === null || _b === void 0 ? void 0 : _b.getTime())) {
changes[field.name] = entity[field.name];
}
else if (!this.copy[field.name]) {
changes[field.name] = entity[field.name];
}
}
else {
// objects need deep equality compare
changes[field.name] = Object.assign({}, entity[field.name]);
}
}
else if (entity[field.name] !== this.copy[field.name]) {
changes[field.name] = entity[field.name];
}
});
return changes;
}
deepCopy(inObject) {
let outObject, value, key;
if (typeof inObject !== 'object' || inObject === null) {
return inObject; // Return the value if inObject is not an object
}
// Create an array or object to hold the values
outObject = Array.isArray(inObject) ? [] : {};
for (key in inObject) {
value = inObject[key];
// Recursively (deep) copy for nested objects, including arrays
outObject[key] = this.deepCopy(value);
}
return outObject;
}
}
exports.MutationTracker = MutationTracker;
//# sourceMappingURL=EntityManager.js.map