UNPKG

@sqb/connect

Version:

Multi-dialect database connection framework written with TypeScript

340 lines (339 loc) 12.6 kB
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 = {}));