@sqb/connect
Version:
Multi-dialect database connection framework written with TypeScript
340 lines (339 loc) • 12.6 kB
JavaScript
import { DataType } from '@sqb/builder';
import { ENTITY_METADATA_KEY } from '../orm.const.js';
import { isAssociationField, isColumnField, isEmbeddedField, } from '../util/orm.helper.js';
import { Association } from './association.js';
export var EntityMetadata;
(function (EntityMetadata) {
function define(ctor) {
const own = getOwn(ctor);
if (own)
return own;
const baseMeta = get(ctor);
const meta = {
ctor: ctor,
name: ctor.name,
fields: {},
indexes: [],
foreignKeys: [],
eventListeners: {},
};
Reflect.defineMetadata(ENTITY_METADATA_KEY, meta, ctor);
// Merge base entity columns into this one
if (baseMeta) {
EntityMetadata.mixin(meta, baseMeta);
}
return meta;
}
EntityMetadata.define = define;
function get(ctor) {
return Reflect.getMetadata(ENTITY_METADATA_KEY, ctor);
}
EntityMetadata.get = get;
function getOwn(ctor) {
return Reflect.getOwnMetadata(ENTITY_METADATA_KEY, ctor);
}
EntityMetadata.getOwn = getOwn;
function getField(entity, fieldName) {
return fieldName ? entity.fields[fieldName.toLowerCase()] : undefined;
}
EntityMetadata.getField = getField;
function getColumnField(entity, fieldName) {
const el = getField(entity, fieldName);
if (el && !isColumnField(el))
throw new Error(`"${el.name}" requested as "column" but it is "${el.kind}"`);
return el;
}
EntityMetadata.getColumnField = getColumnField;
function getEmbeddedField(entity, fieldName) {
const el = getField(entity, fieldName);
if (el && !isEmbeddedField(el))
throw new Error(`"${el.name}" requested as "embedded" but it is "${el.kind}"`);
return el;
}
EntityMetadata.getEmbeddedField = getEmbeddedField;
function getAssociationField(entity, fieldName) {
const el = getField(entity, fieldName);
if (el && !isAssociationField(el)) {
throw new Error(`"${el.name}" requested as "association" but it is "${el.kind}"`);
}
return el;
}
EntityMetadata.getAssociationField = getAssociationField;
function findField(entity, predicate) {
return Object.values(entity.fields).find(predicate);
}
EntityMetadata.findField = findField;
function getColumnFieldByFieldName(entity, fieldName) {
if (!fieldName)
return;
fieldName = fieldName.toLowerCase();
for (const prop of Object.values(entity.fields)) {
if (isColumnField(prop) && prop.fieldName.toLowerCase() === fieldName)
return prop;
}
}
EntityMetadata.getColumnFieldByFieldName = getColumnFieldByFieldName;
function getFieldNames(entity, filter) {
if (filter) {
const out = [];
for (const el of Object.values(entity.fields)) {
if (el && (!filter || filter(el)))
out.push(el.name);
}
return out;
}
// Create a cached name array
if (!Object.prototype.hasOwnProperty.call(entity, '_fieldNames')) {
Object.defineProperty(entity, '_fieldNames', {
enumerable: false,
configurable: true,
writable: true,
value: Object.values(entity.fields).map(m => m.name),
});
}
return entity._fieldNames;
}
EntityMetadata.getFieldNames = getFieldNames;
function getColumnFieldNames(entity) {
return getFieldNames(entity, isColumnField);
}
EntityMetadata.getColumnFieldNames = getColumnFieldNames;
function getEmbeddedFieldNames(entity) {
return getFieldNames(entity, isEmbeddedField);
}
EntityMetadata.getEmbeddedFieldNames = getEmbeddedFieldNames;
function getAssociationFieldNames(entity) {
return getFieldNames(entity, isAssociationField);
}
EntityMetadata.getAssociationFieldNames = getAssociationFieldNames;
function getNonAssociationFieldNames(entity) {
return getFieldNames(entity, x => !isAssociationField(x));
}
EntityMetadata.getNonAssociationFieldNames = getNonAssociationFieldNames;
function getInsertColumnNames(entity) {
return getFieldNames(entity, x => isColumnField(x) && !x.noInsert);
}
EntityMetadata.getInsertColumnNames = getInsertColumnNames;
function getUpdateColumnNames(entity) {
return getFieldNames(entity, x => isColumnField(x) && !x.noUpdate);
}
EntityMetadata.getUpdateColumnNames = getUpdateColumnNames;
function getPrimaryIndex(entity) {
return entity.indexes && entity.indexes.find(idx => idx.primary);
}
EntityMetadata.getPrimaryIndex = getPrimaryIndex;
function getPrimaryIndexColumns(entity) {
const idx = getPrimaryIndex(entity);
const out = [];
if (idx) {
for (const k of idx.columns) {
const col = getColumnField(entity, k);
if (!col)
throw new Error(`Data column "${k}" in primary index of ${entity.name} does not exists`);
out.push(col);
}
}
return out;
}
EntityMetadata.getPrimaryIndexColumns = getPrimaryIndexColumns;
async function getForeignKeyFor(src, trg) {
if (!src.foreignKeys)
return;
for (const f of src.foreignKeys) {
if ((await f.resolveTarget()) === trg)
return f;
}
}
EntityMetadata.getForeignKeyFor = getForeignKeyFor;
function addIndex(entity, index) {
entity.indexes = entity.indexes || [];
index = {
...index,
columns: Array.isArray(index.columns) ? index.columns : [index.columns],
};
if (index.primary)
entity.indexes.forEach(idx => delete idx.primary);
entity.indexes.push(index);
}
EntityMetadata.addIndex = addIndex;
function addForeignKey(entity, propertyKey, target, targetKey) {
entity.foreignKeys = entity.foreignKeys || [];
const fk = new Association(entity.name + '.' + propertyKey, {
source: entity.ctor,
sourceKey: propertyKey,
target,
targetKey,
});
entity.foreignKeys.push(fk);
}
EntityMetadata.addForeignKey = addForeignKey;
function addEventListener(entity, event, fn) {
if (typeof fn !== 'function')
throw new Error('Property must be a function');
entity.eventListeners = entity.eventListeners || {};
entity.eventListeners[event] = entity.eventListeners[event] || [];
entity.eventListeners[event].push(fn);
}
EntityMetadata.addEventListener = addEventListener;
function defineColumnField(entity, name, options = {}) {
delete entity._fieldNames;
let prop = EntityMetadata.getField(entity, name);
if (isColumnField(prop))
options = { ...prop, ...options };
if (!options.type) {
switch (options.dataType) {
case DataType.BOOL:
options.type = Boolean;
break;
case DataType.VARCHAR:
case DataType.CHAR:
case DataType.TEXT:
options.type = String;
break;
case DataType.NUMBER:
case DataType.DOUBLE:
case DataType.FLOAT:
case DataType.INTEGER:
case DataType.SMALLINT:
options.type = Number;
break;
case DataType.TIMESTAMP:
case DataType.TIMESTAMPTZ:
options.type = Date;
break;
case DataType.BINARY:
options.type = Buffer;
break;
default:
options.type = String;
}
}
if (!options.dataType) {
switch (options.type) {
case Boolean:
options.dataType = DataType.BOOL;
break;
case Number:
options.dataType = DataType.NUMBER;
break;
case Date:
options.dataType = DataType.TIMESTAMP;
break;
case Array:
options.dataType = DataType.VARCHAR;
options.isArray = true;
break;
case Buffer:
options.dataType = DataType.BINARY;
break;
default:
options.dataType = DataType.VARCHAR;
}
}
prop = {
fieldName: name,
...options,
kind: 'column',
entity,
name,
};
entity.fields[name.toLowerCase()] = prop;
return prop;
}
EntityMetadata.defineColumnField = defineColumnField;
function defineEmbeddedField(entity, name, type, options) {
delete entity._fieldNames;
let prop = EntityMetadata.getField(entity, name);
if (isEmbeddedField(prop))
options = { ...prop, ...options };
prop = {
...options,
kind: 'object',
entity,
name,
type,
};
entity.fields[name.toLowerCase()] = prop;
return prop;
}
EntityMetadata.defineEmbeddedField = defineEmbeddedField;
function defineAssociationField(entity, propertyKey, association, options) {
delete entity._fieldNames;
const prop = {
...options,
kind: 'association',
entity,
name: propertyKey,
association,
};
let l = association;
let i = 1;
while (l) {
l.name = entity.name + '.' + propertyKey + '#' + i++;
l = l.next;
}
entity.fields[propertyKey.toLowerCase()] = prop;
return prop;
}
EntityMetadata.defineAssociationField = defineAssociationField;
function setPrimaryKeys(entity, column, options) {
addIndex(entity, {
...options,
columns: Array.isArray(column) ? column : [column],
unique: true,
primary: true,
});
}
EntityMetadata.setPrimaryKeys = setPrimaryKeys;
function mixin(derived, base, filter) {
const hasField = (k) => !filter || filter(k);
delete derived._fieldNames;
if (!derived.tableName) {
derived.tableName = base.tableName;
derived.schema = base.schema;
derived.comment = base.comment;
}
// Copy indexes
if (base.indexes && base.indexes.length) {
const hasPrimaryIndex = !!getPrimaryIndex(derived);
for (const idx of base.indexes) {
if (!idx.columns.find(x => !hasField(x))) {
if (hasPrimaryIndex)
addIndex(derived, { ...idx, primary: undefined });
else
addIndex(derived, idx);
}
}
}
// Copy foreign indexes
if (base.foreignKeys && base.foreignKeys.length) {
derived.foreignKeys = derived.foreignKeys || [];
for (const fk of base.foreignKeys) {
if (!fk.sourceKey || hasField(fk.sourceKey)) {
const newFk = new Association(fk.name, {
...fk,
source: derived.ctor,
});
derived.foreignKeys.push(newFk);
}
}
}
// Copy event listeners
if (base.eventListeners) {
for (const [event, arr] of Object.entries(base.eventListeners)) {
arr.forEach(fn => EntityMetadata.addEventListener(derived, event, fn));
}
}
// Copy fields
derived.fields = derived.fields || {};
for (const [n, p] of Object.entries(base.fields)) {
if (!hasField(n))
continue;
const o = Object.assign({}, p);
o.entity = derived;
Object.setPrototypeOf(o, Object.getPrototypeOf(p));
derived.fields[n] = o;
}
}
EntityMetadata.mixin = mixin;
})(EntityMetadata || (EntityMetadata = {}));