orange-orm
Version:
Object Relational Mapper
251 lines (219 loc) • 7.02 kB
JavaScript
let updateField = require('../updateField');
let newEmitEvent = require('../../emitEvent');
let extractStrategy = require('./toDto/extractStrategy');
let extractDeleteStrategy = require('../extractDeleteStrategy');
let newCascadeDeleteStrategy = require('../newCascadeDeleteStrategy');
let _delete = require('./delete');
let newObject = require('../../newObject');
let toDto = require('./toDto');
let createDto = require('./toDto/createDto');
let onChange = require('@lroal/on-change');
let flags = require('../../flags');
let tryGetSessionContext = require('../tryGetSessionContext');
let purifyStrategy = require('../purifyStrategy');
function newDecodeDbRow(table, dbRow, filteredAliases, shouldValidate, isInsert) {
let aliases = filteredAliases || table._aliases;
let columns = table._columns;
let numberOfColumns = columns.length;
if (dbRow.offset === undefined) {
dbRow.offset = 0;
}
let offset = dbRow.offset;
let keys = Object.keys(dbRow);
for (let i = 0; i < numberOfColumns; i++) {
defineColumnProperty(i);
}
dbRow.offset += numberOfColumns;
function defineColumnProperty(i) {
let column = columns[i];
let purify = column.purify;
let name = column.alias;
let intName = '__' + name;
i = offset + i;
let key = keys[i];
Object.defineProperty(Row.prototype, intName, {
get: function() {
return this._dbRow[key];
},
set: function(value) {
let oldValue = this[name];
value = purify(value);
this._dbRow[key] = value;
if (column.validate)
column.validate(value, this._dbRow);
updateField(this._context, table, column, this);
let emit = this._emitColumnChanged[name];
if (emit)
emit(this, column, value, oldValue);
this._emitChanged(this, column, value, oldValue);
}
});
Object.defineProperty(Row.prototype, name, {
enumerable: true,
configurable: false,
get: function() {
if (column.onChange && flags.useProxy && (this[intName] !== null && this[intName] !== undefined) && typeof this[intName] === 'object') {
if (!(name in this._proxies)) {
let value = this[intName];
this._proxies[name] = column.onChange(this._dbRow[key], () => {
if (this[intName] !== onChange.target(value)) {
return;
}
this[intName] = this._dbRow[key];
});
}
return this._proxies[name];
}
return negotiateNull(this[intName]);
},
set: function(value) {
if (column.onChange && (this[intName] !== null && this[intName] !== undefined) && value && typeof value === 'object') {
if (this[intName] === onChange.target(value))
return;
this._proxies[name] = column.onChange(value, () => {
if (this[intName] !== onChange.target(value))
return;
this[intName] = this._dbRow[key];
});
}
this[intName] = value;
}
});
}
setRelated();
function setRelated() {
let relations = table._relations;
for (let relationName in relations) {
setSingleRelated(relationName);
}
}
function setSingleRelated(name) {
Object.defineProperty(Row.prototype, name, {
get: function() {
return createGetRelated(this, name)();
}
});
}
function createGetRelated(row, alias) {
let get = row._related[alias];
if (!get) {
let relation = table._relations[alias];
get = relation.toGetRelated(row._context, row);
row._relationCacheMap.set(relation, relation.getInnerCache(row._context));
row._related[alias] = get;
}
return get;
}
Row.prototype.subscribeChanged = function(onChanged, name) {
let emit;
if (name) {
emit = this._emitColumnChanged[name] || (this._emitColumnChanged[name] = newEmitEvent());
emit.add(onChanged);
return;
}
this._emitChanged.add(onChanged);
};
Row.prototype.unsubscribeChanged = function(onChanged, name) {
if (name) {
this._emitColumnChanged[name].tryRemove(onChanged);
return;
}
this._emitChanged.tryRemove(onChanged);
};
Row.prototype.toJSON = function() {
return toDto(undefined, table, this, new Set());
};
Row.prototype.hydrate = function(context, dbRow) {
const engine = tryGetSessionContext(context)?.engine;
let i = offset;
if (engine === 'sqlite') {
const errorSeparator = '12345678-1234-1234-1234-123456789012';
for (let p in dbRow) {
if (typeof dbRow[p] === 'string' && dbRow[p].indexOf(errorSeparator) === 0)
throw new Error(dbRow[p].split(errorSeparator)[1]);
let key = keys[i];
this._dbRow[key] = columns[i].decode(context, dbRow[p]);
i++;
}
}
else {
for (let p in dbRow) {
let key = keys[i];
this._dbRow[key] = columns[i].decode(context, dbRow[p]);
i++;
}
}
};
Row.prototype.toDto = function(strategy) {
if (strategy === undefined) {
strategy = extractStrategy(table);
}
strategy = purifyStrategy(table, strategy);
if (!tryGetSessionContext(this._context)) {
return toDto(this._context, strategy, table, this, new Set());
}
let p = toDto(this._context, strategy, table, this);
return Promise.resolve().then(() => p);
};
Row.prototype.expand = function(alias) {
let get = createGetRelated(this, alias);
get.expanded = true;
};
Row.prototype.isExpanded = function(alias) {
return this._related[alias] && this._related[alias].expanded;
};
Row.prototype.delete = function(strategy) {
strategy = extractDeleteStrategy(strategy);
_delete(this._context, this, strategy, table);
};
Row.prototype.cascadeDelete = function() {
let strategy = newCascadeDeleteStrategy(newObject(), table);
_delete(this._context, this, strategy, table);
};
Row.prototype.deleteCascade = Row.prototype.cascadeDelete;
function decodeDbRow(context, row) {
for (let i = 0; i < numberOfColumns; i++) {
let index = offset + i;
let key = keys[index];
if (row[key] !== undefined && !isInsert)
row[key] = columns[i].decode(context, row[key]);
if (shouldValidate && columns[i].validate)
columns[i].validate(row[key], row, isInsert);
}
let target = new Row(context, row);
const p = new Proxy(target, {
ownKeys: function() {
return Array.from(aliases).concat(Object.keys(target._related).filter(alias => {
return target._related[alias] && target._related[alias].expanded;
}));
},
getOwnPropertyDescriptor(target, prop) {
if (table._aliases.has(prop) || (target._related[prop]))
return {
enumerable: aliases.has(prop) || (target._related[prop] && target._related[prop].expanded),
configurable: true,
writable: true
};
}
});
return p;
}
function negotiateNull(value) {
if (value === undefined)
return null;
return value;
}
function Row(context, dbRow) {
this._context = context;
this._relationCacheMap = new Map();
this._cache = table._cache.getInnerCache(context);
this._dbRow = dbRow;
this._related = {};
this._emitColumnChanged = {};
this._emitChanged = newEmitEvent();
this._proxies = {};
this._oldValues = JSON.stringify(createDto(table, this));
}
return decodeDbRow;
}
module.exports = newDecodeDbRow;