UNPKG

ts-sql-builder

Version:

A straightforward api for SQL query & schema generation

588 lines (571 loc) 18.5 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); // lib/common/normalize-value.ts function normalized(value) { switch (typeof value) { case "string": return `'${value}'`; case "object": return `'${JSON.stringify(value)}'`; case "function": return value(); default: return JSON.stringify(value); } } // lib/query-builder/query-builder.ts import { format } from "sql-formatter"; // lib/query-builder/qb.enums.ts var JoinType = /* @__PURE__ */ ((JoinType2) => { JoinType2["LEFT"] = "LEFT"; JoinType2["RIGHT"] = "RIGHT"; JoinType2["INNER"] = "INNER"; return JoinType2; })(JoinType || {}); var ORDER = /* @__PURE__ */ ((ORDER2) => { ORDER2["DESC"] = "DESC"; ORDER2["ASC"] = "ASC"; return ORDER2; })(ORDER || {}); // lib/query-builder/query-builder.ts var QueryBuilder = class _QueryBuilder { constructor(table, alias) { this._queryType = "READ"; this._insertColumns = []; this._values = []; this._updatedColumns = {}; this._fields = []; this._table = { name: "" }; this._where = []; this._groupBy = []; this._having = []; this._order = []; this._offset = -1; this._limit = -1; this._joins = []; this._rawFront = ""; this._rawEnd = ""; this._query = ""; this.addSelect = this.select; this.andWhere = this.where; this.andHaving = this.having; if (table) this.from(table, alias); } insertInto(table, ...columns) { this._queryType = "CREATE"; this._insertColumns = columns; return this.from(table); } columns(...columns) { this._insertColumns = columns; return this; } values(...values) { this._values.push(...values); return this; } update(table, data = {}) { this._queryType = "UPDATE"; this.from(table); return this.set(data); } set(data, value) { if (typeof data === "string") { this._updatedColumns[data] = value; } else { this._updatedColumns = __spreadValues(__spreadValues({}, this._updatedColumns), data); } return this; } delete(table) { this._queryType = "DELETE"; return this.from(table); } from(table, alias) { this._table = { name: table, alias: alias != null ? alias : table }; return this; } select(selection, fromTable) { if (Array.isArray(selection)) { selection.forEach((column) => this.select(column, fromTable)); } else if (typeof selection === "string") { this._fields.push({ name: fromTable ? `${fromTable}.${selection}` : selection }); } else { Object.entries(selection).forEach(([name, alias]) => { this._fields.push({ name: fromTable ? `${fromTable}.${name}` : name, alias }); }); } return this; } agg(func, column, alias) { const name = `${func}(${column})`; return this.select(alias ? { [name]: alias } : name); } count(column = "*", alias) { return this.agg("COUNT", column, alias); } countDistinct(column, alias) { const name = `COUNT(DISTINCT ${column})`; return this.select(alias ? { [name]: alias } : name); } sum(column, alias) { return this.agg("SUM", column, alias); } avg(column, alias) { return this.agg("AVG", column, alias); } min(column, alias) { return this.agg("MIN", column, alias); } max(column, alias) { return this.agg("MAX", column, alias); } where(...conditions) { this._where.push(...conditions); return this; } groupBy(...columns) { this._groupBy.push(...columns); return this; } having(...conditions) { this._having.push(...conditions); return this; } orderBy(order) { if (typeof order === "string") { this._order.push([order, "ASC" /* ASC */]); } else if (Array.isArray(order)) { this._order.push(...order.map((o) => [o, "ASC" /* ASC */])); } else { this._order.push(...Object.entries(order)); } return this; } offset(n) { this._offset = n; return this; } limit(n) { this._limit = n; return this; } join(joinTable) { var _a, _b; (_a = joinTable.condition) != null ? _a : joinTable.condition = "TRUE"; (_b = joinTable.alias) != null ? _b : joinTable.alias = joinTable.name; if (joinTable.select) { if (joinTable.select === true) joinTable.select = "*"; this.select(joinTable.select, joinTable.alias); } this._joins.push(joinTable); return this; } innerJoin(joinTable) { return this.join(__spreadProps(__spreadValues({}, joinTable), { type: "INNER" /* INNER */ })); } leftJoin(joinTable) { return this.join(__spreadProps(__spreadValues({}, joinTable), { type: "LEFT" /* LEFT */ })); } rightJoin(joinTable) { return this.join(__spreadProps(__spreadValues({}, joinTable), { type: "RIGHT" /* RIGHT */ })); } subQuery(cb) { return cb ? `(${cb(new _QueryBuilder()).build().getSql()})` : new _QueryBuilder(); } rawFront(rawSql) { this._rawFront += rawSql; return this; } rawEnd(rawSql) { this._rawEnd += rawSql; return this; } handleWhereConditions() { if (this._where.length) { this._query += ` WHERE ${this._where.join(" AND ")}`; } } build() { if (this._rawFront) { this._query += `${this._rawFront} `; } switch (this._queryType) { case "CREATE": { this._query += `INSERT INTO ${this._table.name}`; this._query += ` (${this._insertColumns.map((c) => `"${c}"`).join(", ")})`; this._query += ` VALUES `; const listOfValues = this._values.map((values) => { return `(${values.map((value) => normalized(value)).join(", ")})`; }); this._query += `${listOfValues.join(", ")}`; break; } case "UPDATE": { this._query += `UPDATE ${this._table.name} SET `; const data = Object.entries(this._updatedColumns).map( ([column, value]) => `"${column}" = ${normalized(value)}` ); this._query += data.join(", "); this.handleWhereConditions(); break; } case "DELETE": { this._query += `DELETE FROM ${this._table.name}`; this.handleWhereConditions(); break; } default: this._query += "SELECT "; const selection = this._fields.map((field) => { return field.name + (field.alias ? ` AS ${field.alias}` : ""); }); this._query += selection.join(", "); if (this._table.name) { this._query += ` FROM "${this._table.name}" ${this._table.alias}`; } this._joins.forEach(({ name, alias, type, condition }) => { alias != null ? alias : alias = name; condition != null ? condition : condition = "TRUE"; this._query += ` ${type} JOIN "${name}" ${alias} ON (${condition})`; }); this.handleWhereConditions(); if (this._groupBy.length) { this._query += ` GROUP BY ${this._groupBy.join(", ")}`; } if (this._having.length) { this._query += ` HAVING ${this._having.join(" AND ")}`; } if (this._order.length) { this._query += ` ORDER BY `; this._query += this._order.map(([col, order]) => `${col} ${order}`).join(", "); } if (this._limit !== -1) { this._query += ` LIMIT ${this._limit}`; } if (this._offset !== -1) { this._query += ` OFFSET ${this._offset}`; } } if (this._rawEnd) { this._query += ` ${this._rawEnd}`; } return this; } format(formatOptions) { if (!(formatOptions == null ? void 0 : formatOptions.language)) { formatOptions = __spreadProps(__spreadValues({}, formatOptions), { language: "postgresql" }); } this._query = format(this._query, formatOptions); return this; } getSql() { return this._query; } clear() { this._queryType = "READ"; this._insertColumns = []; this._values = []; this._updatedColumns = {}; this._fields = []; this._table = { name: "" }; this._where = []; this._groupBy = []; this._having = []; this._order = []; this._offset = -1; this._limit = -1; this._joins = []; this._rawFront = ""; this._rawEnd = ""; this._query = ""; return this; } }; // lib/query-builder/qb.functions.ts function $contain(column, sub) { return `${column} LIKE '%${sub}%'`; } function $concat(...strings) { return `CONCAT(${strings.join(", ")})`; } function createQueryBuilder(table, alias) { return new QueryBuilder(table, alias); } // lib/query-builder/qb.operators.ts function $AND(...conditions) { return `(${conditions.join(" AND ")})`; } function $OR(...conditions) { return `(${conditions.join(" OR ")})`; } function $NOT(expr) { return `NOT ${expr}`; } function $IN(elem, list) { const _list = typeof list === "string" ? list : Array.isArray(list) ? `(${list.join(", ")})` : list instanceof QueryBuilder ? `(${list.build().getSql()})` : `(${list(new QueryBuilder()).build().getSql()})`; return `${elem} IN ${list}`; } function $BETWEEN(value, l, r) { return `BETWEEN ${l} AND ${r}`; } function $ALL(value, operator, subQuery) { const sub = typeof subQuery === "string" ? subQuery : subQuery instanceof QueryBuilder ? subQuery.build().getSql() : subQuery(new QueryBuilder()).build().getSql(); return `${value} ${operator} ALL (${sub})`; } function $ANY(value, operator, subQuery) { const sub = typeof subQuery === "string" ? subQuery : subQuery instanceof QueryBuilder ? subQuery.getSql() : subQuery(new QueryBuilder()).build().getSql(); return `${value} ${operator} ANY (${sub})`; } function $isNull(value) { return `(${value} IS NULL)`; } function $notNull(value) { return `(${value} IS NOT NULL)`; } // lib/schema-builder/decorators/table.decorator.ts import "reflect-metadata"; // lib/schema-builder/sb.constants.ts var TABLE_METADATA_KEY = "schema_builder:table_metadata"; var COLUMNS_METADATA_KEY = "schema_builder:table_columns"; var PK_METADATA_KEY = "schema_builder:table_primary_key"; var FKS_METADATA_KEY = "schema_builder:table_foreign_keys"; var INDEXES_METADATA_KEY = "schema_builder:table_indexes"; var TABLE_REGISTRY = []; // lib/schema-builder/decorators/table.decorator.ts function Table(name) { return (target) => { const table = name != null ? name : target.name.toLowerCase(); TABLE_REGISTRY.push(target); Reflect.defineMetadata(TABLE_METADATA_KEY, table, target); }; } // lib/schema-builder/decorators/column.decorator.ts import "reflect-metadata"; function Column(columnOptions) { columnOptions = normalizeColumnOptions(columnOptions); return (target, propertyKey) => { var _a, _b, _c, _d; (_a = columnOptions.name) != null ? _a : columnOptions.name = propertyKey.toString(); const columns = (_b = Reflect.getMetadata(COLUMNS_METADATA_KEY, target.constructor)) != null ? _b : []; columns.push(columnOptions); Reflect.defineMetadata(COLUMNS_METADATA_KEY, columns, target.constructor); if (columnOptions.primary) { const primaryKey = (_c = Reflect.getMetadata(PK_METADATA_KEY, target.constructor)) != null ? _c : []; primaryKey.push(columnOptions.name); Reflect.defineMetadata(PK_METADATA_KEY, primaryKey, target.constructor); } if (columnOptions.foreignKey) { const fkOptions = __spreadValues({ column: columnOptions.name }, columnOptions.foreignKey); const foreignKeys = (_d = Reflect.getMetadata(FKS_METADATA_KEY, target.constructor)) != null ? _d : []; foreignKeys.push(fkOptions); Reflect.defineMetadata(FKS_METADATA_KEY, foreignKeys, target.constructor); } }; } function normalizeColumnOptions(columnOptions) { return __spreadValues({ nullable: true, unique: false, primary: false }, columnOptions); } // lib/schema-builder/decorators/index.decorator.ts import "reflect-metadata"; function Index(indexOptions) { return function(target) { var _a; const indexes = (_a = Reflect.getMetadata(INDEXES_METADATA_KEY, target)) != null ? _a : []; indexes.push(indexOptions); Reflect.defineMetadata(INDEXES_METADATA_KEY, indexes, target); }; } // lib/schema-builder/decorators/foreign-key.decorator.ts import "reflect-metadata"; function ForeignKey(fkOptions) { return function(classOrObj, propertyKey) { var _a, _b; const target = typeof classOrObj === "object" ? classOrObj.constructor : classOrObj; if (typeof classOrObj === "function" && !fkOptions.column) { throw new Error( `schema-builder: Property 'column' is required when @ForeignKey is used as a class decorator.` ); } (_a = fkOptions.column) != null ? _a : fkOptions.column = propertyKey == null ? void 0 : propertyKey.toString(); if (!fkOptions.column) { throw new Error(`schema-builder: Ambiguous column for ForeignKey.'`); } const foreignKeys = (_b = Reflect.getMetadata(FKS_METADATA_KEY, target)) != null ? _b : []; foreignKeys.push(fkOptions); Reflect.defineMetadata(FKS_METADATA_KEY, foreignKeys, target); }; } // lib/schema-builder/decorators/primary-key.decorator.ts import "reflect-metadata"; function PrimaryKey(columnOrColumns) { if (!columnOrColumns || typeof columnOrColumns === "string") { return (target, propertyKey) => { var _a; columnOrColumns != null ? columnOrColumns : columnOrColumns = propertyKey.toString(); const primaryKey = (_a = Reflect.getMetadata(PK_METADATA_KEY, target.constructor)) != null ? _a : []; primaryKey.push(columnOrColumns); Reflect.defineMetadata(PK_METADATA_KEY, primaryKey, target.constructor); }; } else { return (target) => { var _a; const primaryKey = (_a = Reflect.getMetadata(PK_METADATA_KEY, target)) != null ? _a : []; primaryKey.push(...columnOrColumns); Reflect.defineMetadata(PK_METADATA_KEY, primaryKey, target); }; } } // lib/schema-builder/functions/db-schema.ts import fs from "fs"; import Path from "path"; // lib/schema-builder/functions/table-schema.ts import "reflect-metadata"; import { format as format2 } from "sql-formatter"; function tableSchema(table, formatOptions) { const { name, indexes, primaryKey, foreignKeys, columns } = getTableMetadata(table); let sql = `CREATE TABLE ${name} ( `; columns.forEach((column, index) => { const { name: name2, type, nullable, unique, default: _default, check } = column; sql += `${name2} ${type}`; sql += `${nullable ? "" : "NOT NULL"} ${unique ? "UNIQUE" : ""}`; if (_default !== void 0) sql += ` DEFAULT ${normalized(_default)}`; sql += ` ${check ? `CHECK (${check})` : ""}`; if (index !== columns.length - 1) sql += ", "; }); if (primaryKey == null ? void 0 : primaryKey.length) { sql += `, PRIMARY KEY (${primaryKey.join(", ")})`; } foreignKeys.forEach(({ column, reference, onDelete, onUpdate }) => { sql += `, FOREIGN KEY (${column}) REFERENCES ${reference}`; if (onDelete) sql += ` ON DELETE ${onDelete}`; if (onUpdate) sql += ` ON UPDATE ${onUpdate}`; }); sql += "\n);\n"; indexes.forEach(({ name: indexName, columns: columns2, unique }) => { sql += `CREATE ${unique ? " UNIQUE" : ""} INDEX ${indexName} ON ${name} `; sql += `(${columns2.join(", ")}); `; }); if (!(formatOptions == null ? void 0 : formatOptions.language)) { formatOptions = __spreadProps(__spreadValues({}, formatOptions), { language: "postgresql" }); } return format2(sql, formatOptions); } function getTableMetadata(table) { var _a, _b, _c, _d; const name = Reflect.getMetadata( TABLE_METADATA_KEY, table ); if (!name) { throw new Error( `${table.name} is either not a class or not decorated with @Table` ); } const indexes = (_a = Reflect.getMetadata(INDEXES_METADATA_KEY, table)) != null ? _a : []; const primaryKey = (_b = Reflect.getMetadata(PK_METADATA_KEY, table)) != null ? _b : []; const foreignKeys = (_c = Reflect.getMetadata(FKS_METADATA_KEY, table)) != null ? _c : []; const columns = (_d = Reflect.getMetadata(COLUMNS_METADATA_KEY, table)) != null ? _d : []; return { name, indexes, primaryKey, foreignKeys, columns }; } // lib/schema-builder/functions/db-schema.ts function buildSchema(options, formatOptions) { if ("path" in options) { const { path } = options; const dirname = Path.dirname(path); if (!fs.existsSync(dirname)) fs.mkdirSync(dirname, { recursive: true }); let schema = ""; TABLE_REGISTRY.forEach((table, index) => { const tableName = Reflect.getMetadata(TABLE_METADATA_KEY, table); if (formatOptions == null ? void 0 : formatOptions.comments) schema += `-- ${tableName} `; schema += tableSchema(table, formatOptions); if (index !== TABLE_REGISTRY.length - 1) schema += "\n\n\n"; }); fs.writeFileSync(path, schema); } else { const { dirname } = options; if (!fs.existsSync(dirname)) fs.mkdirSync(dirname, { recursive: true }); TABLE_REGISTRY.forEach((table) => { const tableName = Reflect.getMetadata(TABLE_METADATA_KEY, table); fs.writeFileSync( `${dirname}/${tableName}.schema.sql`, tableSchema(table, formatOptions) ); }); } } export { $ALL, $AND, $ANY, $BETWEEN, $IN, $NOT, $OR, $concat, $contain, $isNull, $notNull, COLUMNS_METADATA_KEY, Column, FKS_METADATA_KEY, ForeignKey, INDEXES_METADATA_KEY, Index, JoinType, ORDER, PK_METADATA_KEY, PrimaryKey, QueryBuilder, TABLE_METADATA_KEY, Table, buildSchema, createQueryBuilder, getTableMetadata, normalizeColumnOptions, normalized, tableSchema };