UNPKG

orange-orm

Version:

Object Relational Mapper

518 lines (462 loc) 16.7 kB
const typeMap = { StringColumn: 'string', BooleanColumn: 'boolean', UUIDColumn: 'string', BinaryColumn: 'string', JSONColumn: 'object', DateColumn: 'Date | string', NumberColumn: 'number', }; function getTSDefinition(tableConfigs, {isNamespace = false, isHttp = false} = {}) { const rootTablesAdded = new Map(); const tableNames = new Set(); const tablesAdded = new Map(); let src = ''; const defs = tableConfigs.map(getTSDefinitionTable).join(''); const tables = tableConfigs.reduce((tables, x) => { tables[x.name] = x.table; return tables; }, {}); src += getPrefixTs(isNamespace); if (isNamespace) src += startNamespace(tables, isHttp); src += defs; src += getRdbClientTs(tables, isHttp); if (isNamespace) src += '}'; return src; function getTSDefinitionTable({table, customFilters, name}) { let Name = name.substr(0, 1).toUpperCase() + name.substr(1); name = name.substr(0, 1).toLowerCase() + name.substr(1); let result = '' + getTable(table, Name, name, customFilters); return result; } function getTable(table, Name, name, customFilters) { const _columns = columns(table); const _tableRelations = tableRelations(table); return ` export interface ${Name}Table { count(filter?: RawFilter): Promise<number>; getAll(): Promise<${Name}Array>; getAll(fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getMany(filter?: RawFilter): Promise<${Name}Array>; getMany(filter: RawFilter, fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getMany(${name}s: Array<${Name}>): Promise<${Name}Array>; getMany(${name}s: Array<${Name}>, fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; getOne(filter?: RawFilter): Promise<${Name}Row>; getOne(filter?: RawFilter, fetchingStrategy?: ${Name}Strategy): Promise<${Name}Row>; getOne(${name}: ${Name}): Promise<${Name}Row>; getOne(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; getById(${getIdArgs(table)}): Promise<${Name}Row>; getById(${getIdArgs(table)}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; replace(${name}s: ${Name}[] | ${Name}): Promise<void>; replace(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; replace(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; update(${name}: ${Name}): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy): Promise<void>; update(${name}: ${Name}, whereStrategy: ${Name}Strategy, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row[]>; updateChanges(${name}s: ${Name}[], old${name}s: ${Name}[]): Promise<${Name}Array>; updateChanges(${name}s: ${Name}[],old${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; updateChanges(${name}: ${Name}, old${name}: ${Name}): Promise<${Name}Row>; updateChanges(${name}: ${Name},old${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; insert(${name}s: ${Name}[]): Promise<${Name}Array>; insert(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): Promise<${Name}Array>; insert(${name}: ${Name}): Promise<${Name}Row>; insert(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): Promise<${Name}Row>; insertAndForget(${name}s: ${Name}[]): Promise<void>; insertAndForget(${name}: ${Name}): Promise<void>; delete(filter?: RawFilter): Promise<void>; delete(${name}s: Array<${Name}>): Promise<void>; deleteCascade(filter?: RawFilter): Promise<void>; deleteCascade(${name}s: Array<${Name}>): Promise<void>; proxify(${name}s: ${Name}[]): ${Name}Array; proxify(${name}s: ${Name}[], fetchingStrategy: ${Name}Strategy): ${Name}Array; proxify(${name}: ${Name}): ${Name}Row; proxify(${name}: ${Name}, fetchingStrategy: ${Name}Strategy): ${Name}Row; patch(patch: JsonPatch): Promise<void>; patch(patch: JsonPatch, concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; customFilters: ${Name}CustomFilters; ${_columns} ${_tableRelations} } export interface ${Name}ExpressConfig { baseFilter?: RawFilter | ((context: ExpressContext) => RawFilter | Promise<RawFilter>); customFilters?: Record<string, (context: ExpressContext,...args: any[]) => RawFilter | Promise<RawFilter>>; concurrency?: ${Name}Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; } export interface ${Name}CustomFilters { ${getCustomFilters(customFilters)} } export interface ${Name}Array extends Array<${Name}> { saveChanges(): Promise<void>; saveChanges(concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; acceptChanges(): void; clearChanges(): void; refresh(): Promise<void>; refresh(fetchingStrategy: ${Name}Strategy): Promise<void>; delete(): Promise<void>; delete(options: ${Name}Concurrency): Promise<void>; } export interface ${Name}Row extends ${Name} { saveChanges(): Promise<void>; saveChanges(concurrency: ${Name}Concurrency, fetchingStrategy?: ${Name}Strategy): Promise<void>; acceptChanges(): void; clearChanges(): void; refresh(): Promise<void>; refresh(fetchingStrategy: ${Name}Strategy): Promise<void>; delete(): Promise<void>; delete(options: ${Name}Concurrency): Promise<void>; } ${Concurrency(table, Name, true)} `; } function getIdArgs(table) { let result = []; for (let i = 0; i < table._primaryColumns.length; i++) { let column = table._primaryColumns[i]; result.push(`${column.alias}: ${typeMap[column.tsType]}`); } return result.join(', '); } function tableRelations(table) { let relations = table._relations; let result = ''; for (let relationName in relations) { const tableName = getTableName(relations[relationName], relationName); result += `${relationName}: ${tableName}RelatedTable;`; } return result; } function columns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}${column.alias} : ${column.tsType};`; separator = ` `; } return result; } function Concurrency(table, name, isRoot) { name = pascalCase(name); if (!isRoot) { if (tablesAdded.has(table)) return ''; else { tablesAdded.set(table, name); } } let otherConcurrency = ''; let concurrencyRelations = ''; let strategyRelations = ''; let regularRelations = ''; let relations = table._relations; let relationName; let separator = ` `; let visitor = {}; visitor.visitJoin = function(relation) { const tableTypeName = getTableName(relation, relationName); otherConcurrency += `${Concurrency(relation.childTable, tableTypeName)}`; concurrencyRelations += `${relationName}?: ${tableTypeName}Concurrency;${separator}`; strategyRelations += `${relationName}?: ${tableTypeName}Strategy | boolean;${separator}`; regularRelations += `${relationName}?: ${tableTypeName} | null;${separator}`; }; visitor.visitOne = visitor.visitJoin; visitor.visitMany = function(relation) { const tableTypeName = getTableName(relation, relationName); otherConcurrency += `${Concurrency(relation.childTable, tableTypeName)}`; concurrencyRelations += `${relationName}?: ${tableTypeName}Concurrency;${separator}`; strategyRelations += `${relationName}?: ${tableTypeName}Strategy | boolean;${separator}`; regularRelations += `${relationName}?: ${tableTypeName}[] | null;${separator}`; }; for (relationName in relations) { var relation = relations[relationName]; relation.accept(visitor); } let row = ''; if (!isRoot) { row = `export interface ${name}RelatedTable { ${columns(table)} ${tableRelations(table)} all: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; any: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; none: (selector: (table: ${name}RelatedTable) => RawFilter) => Filter; exists: () => Filter; }`; } return ` export interface ${name}Concurrency { readonly?: boolean; concurrency?: Concurrency; ${concurrencyColumns(table)} ${concurrencyRelations} } export interface ${name} { ${regularColumns(table)} ${regularRelations} } export interface ${name}TableBase { ${columns(table)} ${tableRelations(table)} } export interface ${name}Strategy { ${strategyColumns(table)} ${strategyRelations} limit?: number; offset?: number; orderBy?: Array<${orderByColumns(table)}> | ${orderByColumns(table)}; where?: (table: ${name}TableBase) => RawFilter; } ${otherConcurrency} ${row}`; } function getTableName(relation, relationName) { let name = rootTablesAdded.get(relation.childTable); if (name) return name; else { let name = pascalCase(relationName); let count = 2; while (tableNames.has(name)) { name = name + 'x' + count; count++; } rootTablesAdded.set(relation.childTable, name); tableNames.add(name); return name; } } } function regularColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; if (column._notNull) result += `${separator}${column.alias} : ${typeMap[column.tsType]};`; else result += `${separator}${column.alias}? : ${typeMap[column.tsType]} | null;`; separator = ` `; } return result; } function orderByColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}'${column.alias}' | '${column.alias} desc'`; separator = '| '; } return result; } function pascalCase(name) { return name[0].toUpperCase() + name.substr(1); } function concurrencyColumns(table) { let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; result += `${separator}${column.alias}? : ColumnConcurrency;`; separator = ` `; } return result; } function strategyColumns(table) { let primarySet = new Set(table._primaryColumns); let result = ''; let separator = ''; for (let i = 0; i < table._columns.length; i++) { let column = table._columns[i]; if (primarySet.has(column)) continue; result += `${separator}${column.alias}? : boolean;`; separator = ` `; } return result; } function getCustomFilters(filters) { return getLeafNames(filters); function getLeafNames(obj, tabs = '\t\t\t\t\t') { let result = ''; for (let p in obj) { if (typeof obj[p] === 'object' && obj[p] !== null) { result += '\n' + tabs + p + ': {' + tabs + getLeafNames(obj[p], tabs + '\t'); result += '\n' + tabs + '}'; } else if (typeof obj[p] === 'function') result += '\n' + tabs + p + ': (' + getParamNames(obj[p]) + ') => import(\'orange-orm\').Filter;'; } return result; } } function getParamNames(func) { let STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; let ARGUMENT_NAMES = /([^\s,]+)/g; let fnStr = func.toString().replace(STRIP_COMMENTS, ''); let result = fnStr.slice(fnStr.indexOf('(') + 1, fnStr.indexOf(')')).match(ARGUMENT_NAMES); if (result === null) return ''; return result.slice(1).join(': unknown, ') + ': unknown'; } function getPrefixTs(isNamespace) { if (isNamespace) return ` /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { AxiosInterceptorManager, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import type { BooleanColumn, JSONColumn, UUIDColumn, DateColumn, NumberColumn, BinaryColumn, StringColumn, Concurrency, Filter, RawFilter, TransactionOptions, Pool, Express, Url, ColumnConcurrency, JsonPatch } from 'orange-orm'; export { RequestHandler } from 'express'; export { Concurrency, Filter, RawFilter, Config, TransactionOptions, Pool } from 'orange-orm'; export = r; declare function r(config: Config): r.RdbClient; `; return ` /* eslint-disable @typescript-eslint/no-empty-interface */ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ import schema from './schema'; import type { AxiosInterceptorManager, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import type { BooleanColumn, JSONColumn, UUIDColumn, DateColumn, NumberColumn, BinaryColumn, StringColumn, Concurrency, Filter, RawFilter, TransactionOptions, Pool, Express, Url, ColumnConcurrency, JsonPatch } from 'orange-orm'; export default schema as RdbClient;`; } function startNamespace(tables, isHttp) { return ` declare namespace r {${getTables(isHttp)} `; function getTables(isHttp) { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` const ${name}: ${Name}Table;`; } if (!isHttp) result += ` function and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function not(): Filter; function transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; function query(filter: RawFilter | string): Promise<unknown[]>; function query<T>(filter: RawFilter | string): Promise<T[]>; function transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; const filter: Filter; function express(): Express; function express(config: ExpressConfig): Express; `; else result += ` const interceptors: { request: AxiosInterceptorManager<InternalAxiosRequestConfig>; response: AxiosInterceptorManager<AxiosResponse>; }; function reactive(proxyMethod: (obj: unknown) => unknown): void; function and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; function not(): Filter; const filter: Filter; `; return result; } } function getRdbClientTs(tables, isHttp) { return ` export interface RdbClient {${getTables(isHttp)} } export interface RdbConfig { db?: Pool | (() => Pool); readonly?: boolean; concurrency?: Concurrency;${getConcurrencyTables()} } export interface MetaData { readonly?: boolean; concurrency?: Concurrency;${getConcurrencyTables()} } export interface ExpressConfig { db?: Pool | (() => Pool); tables?: ExpressTables; concurrency?: Concurrency; readonly?: boolean; disableBulkDeletes?: boolean; } export interface ExpressContext { request: import('express').Request; response: import('express').Response; client: RdbClient; } export interface ExpressTables {${getExpressTables()} } `; function getConcurrencyTables() { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}?: ${Name}Concurrency;`; } return result; } function getTables(isHttp) { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}: ${Name}Table;`; } if (isHttp) result += ` (config: {db: Url}): RdbClient; interceptors: { request: AxiosInterceptorManager<InternalAxiosRequestConfig>; response: AxiosInterceptorManager<AxiosResponse>; }; reactive(proxyMethod: (obj: unknown) => unknown): void; and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; not(): Filter; transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; filter: Filter; createPatch(original: any[], modified: any[]): JsonPatch; createPatch(original: any, modified: any): JsonPatch;`; else result += ` (config: RdbConfig): RdbClient; and(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; or(filter: RawFilter | RawFilter[], ...filters: RawFilter[]): Filter; not(): Filter; query(filter: RawFilter | string): Promise<unknown[]>; query<T>(filter: RawFilter | string): Promise<T[]>; transaction(fn: (transaction: RdbClient) => Promise<unknown>, options?: TransactionOptions): Promise<void>; filter: Filter; createPatch(original: any[], modified: any[]): JsonPatch; createPatch(original: any, modified: any): JsonPatch; express(): Express; express(config: ExpressConfig): Express; readonly metaData: MetaData;`; return result; } function getExpressTables() { let result = ''; for (let name in tables) { let Name = name.substring(0, 1).toUpperCase() + name.substring(1); result += ` ${name}?: boolean | ${Name}ExpressConfig;`; } return result; } } module.exports = getTSDefinition;