import { Source } from '.' import { Expression, expression } from '../expressions' import { sql, Template } from '../template' import { eident } from '../template/eident' import { Type } from '../types' export type Table = any> = TableSource & { [K in keyof T]: T[K]['expression'] } export function source> ( name: string, types: { [K in keyof T]: T[K] }, alias?: string, columns?: string[] ): Table { return new TableSource(name, types, alias, columns) as Table } export class TableSource = Record> implements Source<{ [K in keyof T]: T[K]['expression'] }> { $: TableIdentifier constructor (name: string, types: T, alias?: string, columns?: string[]) { const ident = new TableIdentifier(name.split('.'), types, alias, columns) Object.entries(types).forEach(([key, type]) => { Object.defineProperty(this, key, { enumerable: true, get: () => expression`${ident.$}.${sql.ident(key)}` }) }) try { Object.defineProperty(this, '$', { enumerable: false, value: ident }) } catch (error) { throw new Error(`Column name '${error.message.slice(26)}' is reserved in source definition`) } } } export class TableIdentifier = Record> extends Template implements Source<{ [K in keyof T]: T[K]['expression'] }> { $ = this expr: { [K in keyof T]: T[K]['expression'] } type: { [K in keyof T]: T[K]['primitive'] } constructor (public name: string[], private typings: T, public alias?: string, public columns?: string[]) { super([], []) } toQuery (start = 1) { return [this.alias ? eident(this.alias) : this.name.map(eident).join('.'), []] as [string, string[]] } toSource () { const columns = this.columns && this.columns.length ? sql` ( ${sql.join(this.columns.map(el => sql.ident(el)))} )` : sql`` const AS_ALIAS = this.alias ? sql` AS ${sql.ident(this.alias)}${columns}` : sql`` return sql`${sql.ident(...this.name)}${AS_ALIAS}` } as (alias: string, columns?: string[]): Table { return source(this.name.join('.'), this.types, alias, columns) as Table } get types (): T { return Object.entries(this.typings) .map(([key, val]) => { const type = new Type(val.strings, val.literals) type.constraints = val.constraints return [key, type] }) .reduce((tmp, [key, val]: [string, Type]) => ({ ...tmp, [key]: val }), {}) as any } all (): { [K in keyof T]: Expression } { return expression`${this}.${sql.keyword('*')}` } }