UNPKG

workers-qb

Version:

Zero dependencies Query Builder for Cloudflare Workers

909 lines (900 loc) 25.3 kB
// src/enums.ts var OrderTypes = /* @__PURE__ */ ((OrderTypes3) => { OrderTypes3["ASC"] = "ASC"; OrderTypes3["DESC"] = "DESC"; return OrderTypes3; })(OrderTypes || {}); var FetchTypes = /* @__PURE__ */ ((FetchTypes2) => { FetchTypes2["ONE"] = "ONE"; FetchTypes2["ALL"] = "ALL"; return FetchTypes2; })(FetchTypes || {}); var ConflictTypes = /* @__PURE__ */ ((ConflictTypes3) => { ConflictTypes3["ROLLBACK"] = "ROLLBACK"; ConflictTypes3["ABORT"] = "ABORT"; ConflictTypes3["FAIL"] = "FAIL"; ConflictTypes3["IGNORE"] = "IGNORE"; ConflictTypes3["REPLACE"] = "REPLACE"; return ConflictTypes3; })(ConflictTypes || {}); var JoinTypes = /* @__PURE__ */ ((JoinTypes2) => { JoinTypes2["INNER"] = "INNER"; JoinTypes2["LEFT"] = "LEFT"; JoinTypes2["CROSS"] = "CROSS"; return JoinTypes2; })(JoinTypes || {}); // src/logger.ts function defaultLogger(query, meta) { console.log(`[workers-qb][${meta.duration}ms] ${JSON.stringify(query)}`); } async function asyncLoggerWrapper(query, loggerFunction, innerFunction) { const start = Date.now(); try { return await innerFunction(); } catch (e) { throw e; } finally { if (loggerFunction) { if (Array.isArray(query)) { for (const q of query) { await loggerFunction(q.toObject(), { duration: Date.now() - start }); } } else { await loggerFunction(query.toObject(), { duration: Date.now() - start }); } } } } function syncLoggerWrapper(query, loggerFunction, innerFunction) { const start = Date.now(); try { return innerFunction(); } catch (e) { throw e; } finally { if (loggerFunction) { if (Array.isArray(query)) { for (const q of query) { loggerFunction(q.toObject(), { duration: Date.now() - start }); } } else { loggerFunction(query.toObject(), { duration: Date.now() - start }); } } } } // src/modularBuilder.ts var SelectBuilder = class _SelectBuilder { _debugger = false; _options = {}; _fetchAll; _fetchOne; constructor(options, fetchAll, fetchOne) { this._options = options; this._fetchAll = fetchAll; this._fetchOne = fetchOne; } setDebugger(state) { this._debugger = state; } tableName(tableName) { return new _SelectBuilder( { ...this._options, tableName }, this._fetchAll, this._fetchOne ); } fields(fields) { return this._parseArray("fields", this._options.fields, fields); } where(conditions, params) { if (!Array.isArray(conditions)) { conditions = [conditions]; } if (params === void 0) params = []; if (!Array.isArray(params)) { params = [params]; } if (this._options.where?.conditions) { conditions = this._options.where.conditions.concat(conditions); } if (this._options.where?.params) { params = this._options.where.params.concat(params); } return new _SelectBuilder( { ...this._options, where: { conditions, params } }, this._fetchAll, this._fetchOne ); } whereIn(fields, values) { let whereInCondition; let whereInParams; const seperateWithComma = (prev, next) => prev + ", " + next; if (values.length === 0) { return new _SelectBuilder( { ...this._options }, this._fetchAll, this._fetchOne ); } if (!Array.isArray(fields)) { whereInCondition = `(${fields}) IN (VALUES `; whereInCondition += values.map(() => "(?)").reduce(seperateWithComma); whereInCondition += ")"; whereInParams = values; } else { const fieldLength = fields.length; whereInCondition = `(${fields.map((val) => val).reduce(seperateWithComma)}) IN (VALUES `; const valuesString = `(${[...new Array(fieldLength).keys()].map(() => "?").reduce(seperateWithComma)})`; whereInCondition += [...new Array(fieldLength).keys()].map(() => valuesString).reduce(seperateWithComma); whereInCondition += ")"; whereInParams = values.flat(); } let conditions = [whereInCondition]; let params = whereInParams; if (this._options.where?.conditions) { conditions = this._options.where?.conditions.concat(conditions); } if (this._options.where?.params) { params = this._options.where?.params.concat(params); } return new _SelectBuilder( { ...this._options, where: { conditions, params } }, this._fetchAll, this._fetchOne ); } join(join) { return this._parseArray("join", this._options.join, join); } groupBy(groupBy) { return this._parseArray("groupBy", this._options.groupBy, groupBy); } having(having) { return this._parseArray("having", this._options.having, having); } orderBy(orderBy) { return this._parseArray("orderBy", this._options.orderBy, orderBy); } offset(offset) { return new _SelectBuilder( { ...this._options, offset }, this._fetchAll, this._fetchOne ); } limit(limit) { return new _SelectBuilder( { ...this._options, limit }, this._fetchAll, this._fetchOne ); } _parseArray(fieldName, option, value) { let val = []; if (!Array.isArray(value)) { val.push(value); } else { val = value; } if (option && Array.isArray(option)) { val = [...option, ...val]; } return new _SelectBuilder( { ...this._options, [fieldName]: val }, this._fetchAll, this._fetchOne ); } getQueryAll(options) { return this._fetchAll({ ...this._options, ...options }); } getQueryOne() { return this._fetchOne(this._options); } execute(options) { return this._fetchAll({ ...this._options, ...options }).execute(); } all(options) { return this._fetchAll({ ...this._options, ...options }).execute(); } one() { return this._fetchOne(this._options).execute(); } count() { return this._fetchOne(this._options).count(); } }; // src/tools.ts var Raw = class { isRaw = true; content; constructor(content) { this.content = content; } }; var Query = class { executeMethod; query; arguments; fetchType; constructor(executeMethod, query, args, fetchType) { this.executeMethod = executeMethod; this.query = trimQuery(query); this.arguments = args; this.fetchType = fetchType; } execute() { return this.executeMethod(this); } toObject() { return { query: this.query, args: this.arguments, fetchType: this.fetchType }; } }; var QueryWithExtra = class extends Query { countQuery; constructor(executeMethod, query, countQuery, args, fetchType) { super(executeMethod, query, args, fetchType); this.countQuery = countQuery; } count() { return this.executeMethod( new Query(this.executeMethod, this.countQuery, this.arguments, "ONE" /* ONE */) ); } }; function trimQuery(query) { return query.replace(/\s\s+/g, " "); } // src/builder.ts var QueryBuilder = class { options; loggerWrapper = asyncLoggerWrapper; constructor(options) { this.options = options || {}; } setDebugger(state) { if (state === true) { if (this.options.logger) { return; } this.options.logger = defaultLogger; } else { this.options.logger = void 0; } } execute(query) { throw new Error("Execute method not implemented"); } batchExecute(queryArray) { throw new Error("Batch execute method not implemented"); } lazyExecute(query) { throw new Error("Execute lazyExecute not implemented"); } createTable(params) { return new Query( (q) => { return this.execute(q); }, `CREATE TABLE ${params.ifNotExists ? "IF NOT EXISTS" : ""} ${params.tableName} ( ${params.schema})` ); } dropTable(params) { return new Query( (q) => { return this.execute(q); }, `DROP TABLE ${params.ifExists ? "IF EXISTS" : ""} ${params.tableName}` ); } select(tableName) { return new SelectBuilder( { tableName }, (params) => { return this.fetchAll(params); }, (params) => { return this.fetchOne(params); } ); } fetchOne(params) { return new QueryWithExtra( (q) => { return this.execute(q); }, this._select({ ...params, limit: 1 }), this._select({ ...params, fields: "count(*) as total", offset: void 0, groupBy: void 0, limit: 1 }), typeof params.where === "object" && !Array.isArray(params.where) && params.where?.params ? Array.isArray(params.where?.params) ? params.where?.params : [params.where?.params] : void 0, "ONE" /* ONE */ ); } fetchAll(params) { return new QueryWithExtra( (q) => { return params.lazy ? this.lazyExecute(q) : this.execute(q); }, this._select({ ...params, lazy: void 0 }), this._select({ ...params, fields: "count(*) as total", offset: void 0, groupBy: void 0, limit: 1, lazy: void 0 }), typeof params.where === "object" && !Array.isArray(params.where) && params.where?.params ? Array.isArray(params.where?.params) ? params.where?.params : [params.where?.params] : void 0, "ALL" /* ALL */ ); } raw(params) { return new Query( (q) => { return this.execute(q); }, params.query, params.args, params.fetchType ); } insert(params) { let args = []; if (typeof params.onConflict === "object") { if (typeof params.onConflict?.where === "object" && !Array.isArray(params.onConflict?.where) && params.onConflict?.where?.params) { args = args.concat(params.onConflict.where?.params); } if (params.onConflict.data) { args = args.concat(this._parse_arguments(params.onConflict.data)); } } if (Array.isArray(params.data)) { for (const row of params.data) { args = args.concat(this._parse_arguments(row)); } } else { args = args.concat(this._parse_arguments(params.data)); } const fetchType = Array.isArray(params.data) ? "ALL" /* ALL */ : "ONE" /* ONE */; return new Query( (q) => { return this.execute(q); }, this._insert(params), args, fetchType ); } update(params) { let args = this._parse_arguments(params.data); if (typeof params.where === "object" && !Array.isArray(params.where) && params.where?.params) { if (Array.isArray(params.where?.params)) { args = params.where?.params.concat(args); } else { args = [params.where?.params].concat(args); } } return new Query( (q) => { return this.execute(q); }, this._update(params), args, "ALL" /* ALL */ ); } delete(params) { return new Query( (q) => { return this.execute(q); }, this._delete(params), typeof params.where === "object" && !Array.isArray(params.where) && params.where?.params ? Array.isArray(params.where?.params) ? params.where?.params : [params.where?.params] : void 0, "ALL" /* ALL */ ); } _parse_arguments(row) { return Object.values(row).filter((value) => { return !(value instanceof Raw); }); } _onConflict(resolution) { if (resolution) { if (typeof resolution === "object") { if (!Array.isArray(resolution.column)) { resolution.column = [resolution.column]; } const _update_query = this.update({ tableName: "_REPLACE_", data: resolution.data, where: resolution.where }).query.replace(" _REPLACE_", ""); return ` ON CONFLICT (${resolution.column.join(", ")}) DO ${_update_query}`; } return `OR ${resolution} `; } return ""; } _insert(params) { const rows = []; let data; if (!Array.isArray(params.data)) { data = [params.data]; } else { data = params.data; } if (!data || !data[0] || data.length === 0) { throw new Error("Insert data is undefined"); } const columns = Object.keys(data[0]).join(", "); let index = 1; let orConflict = "", onConflict = ""; if (params.onConflict && typeof params.onConflict === "object") { onConflict = this._onConflict(params.onConflict); if (typeof params.onConflict?.where === "object" && !Array.isArray(params.onConflict?.where) && params.onConflict?.where?.params) { if (Array.isArray(params.onConflict.where?.params)) { index += (params.onConflict.where?.params).length; } else { index += 1; } } if (params.onConflict.data) { index += this._parse_arguments(params.onConflict.data).length; } } else { orConflict = this._onConflict(params.onConflict); } for (const row of data) { const values = []; Object.values(row).forEach((value) => { if (value instanceof Raw) { values.push(value.content); } else { values.push(`?${index}`); index += 1; } }); rows.push(`(${values.join(", ")})`); } return `INSERT ${orConflict} INTO ${params.tableName} (${columns}) VALUES ${rows.join(", ")}` + onConflict + this._returning(params.returning); } _update(params) { const whereParamsLength = typeof params.where === "object" && !Array.isArray(params.where) && params.where?.params ? Array.isArray(params.where?.params) ? Object.keys(params.where?.params).length : 1 : 0; let whereString = this._where(params.where); let parameterIndex = 1; if (whereString && whereString.match(/(?<!\d)\?(?!\d)/)) { whereString = whereString.replace(/\?/g, () => `?${parameterIndex++}`); } const set = []; let index = 1; for (const [key, value] of Object.entries(params.data)) { if (value instanceof Raw) { set.push(`${key} = ${value.content}`); } else { set.push(`${key} = ?${whereParamsLength + index}`); index += 1; } } return `UPDATE ${this._onConflict(params.onConflict)}${params.tableName} SET ${set.join(", ")}` + whereString + this._returning(params.returning); } _delete(params) { return `DELETE FROM ${params.tableName}` + this._where(params.where) + this._returning(params.returning) + this._orderBy(params.orderBy) + this._limit(params.limit) + this._offset(params.offset); } _select(params) { return `SELECT ${this._fields(params.fields)} FROM ${params.tableName}` + this._join(params.join) + this._where(params.where) + this._groupBy(params.groupBy) + this._having(params.having) + this._orderBy(params.orderBy) + this._limit(params.limit) + this._offset(params.offset); } _fields(value) { if (!value) return "*"; if (typeof value === "string") return value; return value.join(", "); } _where(value) { if (!value) return ""; let conditions = value; if (typeof value === "object" && !Array.isArray(value)) { conditions = value.conditions; } if (typeof conditions === "string") return ` WHERE ${conditions.toString()}`; if (conditions.length === 1) return ` WHERE ${conditions[0].toString()}`; if (conditions.length > 1) { return ` WHERE (${conditions.join(") AND (")})`; } return ""; } _join(value) { if (!value) return ""; if (!Array.isArray(value)) { value = [value]; } const joinQuery = []; value.forEach((item) => { const type = item.type ? `${item.type} ` : ""; joinQuery.push( `${type}JOIN ${typeof item.table === "string" ? item.table : `(${this._select(item.table)})`}${item.alias ? ` AS ${item.alias}` : ""} ON ${item.on}` ); }); return " " + joinQuery.join(" "); } _groupBy(value) { if (!value) return ""; if (typeof value === "string") return ` GROUP BY ${value}`; return ` GROUP BY ${value.join(", ")}`; } _having(value) { if (!value) return ""; if (typeof value === "string") return ` HAVING ${value}`; return ` HAVING ${value.join(" AND ")}`; } _orderBy(value) { if (!value) return ""; if (typeof value === "string") return ` ORDER BY ${value}`; const order = []; if (Array.isArray(value)) { for (const val of value) { order.push(val); } } else { order.push(value); } const result = order.map((obj) => { if (typeof obj === "object") { const objs = []; Object.entries(obj).forEach(([key, item]) => { objs.push(`${key} ${item}`); }); return objs.join(", "); } else { return obj; } }); return ` ORDER BY ${result.join(", ")}`; } _limit(value) { if (!value) return ""; return ` LIMIT ${value}`; } _offset(value) { if (!value) return ""; return ` OFFSET ${value}`; } _returning(value) { if (!value) return ""; if (typeof value === "string") return ` RETURNING ${value}`; return ` RETURNING ${value.join(", ")}`; } }; // src/migrations.ts var syncMigrationsBuilder = class { _builder; _migrations; _tableName; constructor(options, builder) { this._tableName = options.tableName || "migrations"; this._migrations = options.migrations; this._builder = builder; } initialize() { this._builder.createTable({ tableName: this._tableName, schema: `id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL`, ifNotExists: true }).execute(); return; } getApplied() { this.initialize(); const result = this._builder.fetchAll({ tableName: this._tableName, orderBy: "id" }).execute(); return result.results || []; } getUnapplied() { const appliedMigrations = this.getApplied().map((migration) => { return migration.name; }); const unappliedMigrations = []; for (const migration of this._migrations) { if (!appliedMigrations.includes(migration.name)) { unappliedMigrations.push(migration); } } return unappliedMigrations; } apply() { const appliedMigrations = []; for (const migration of this.getUnapplied()) { this._builder.raw({ query: ` ${migration.sql} INSERT INTO ${this._tableName} (name) values ('${migration.name}');` }).execute(); appliedMigrations.push(migration); } return appliedMigrations; } }; var asyncMigrationsBuilder = class { _builder; _migrations; _tableName; constructor(options, builder) { this._tableName = options.tableName || "migrations"; this._migrations = options.migrations; this._builder = builder; } async initialize() { await this._builder.createTable({ tableName: this._tableName, schema: `id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL`, ifNotExists: true }).execute(); return; } async getApplied() { await this.initialize(); const result = await this._builder.fetchAll({ tableName: this._tableName, orderBy: "id" }).execute(); return result.results || []; } async getUnapplied() { const appliedMigrations = (await this.getApplied()).map((migration) => { return migration.name; }); const unappliedMigrations = []; for (const migration of this._migrations) { if (!appliedMigrations.includes(migration.name)) { unappliedMigrations.push(migration); } } return unappliedMigrations; } async apply() { const appliedMigrations = []; for (const migration of await this.getUnapplied()) { await this._builder.raw({ query: ` ${migration.sql} INSERT INTO ${this._tableName} (name) values ('${migration.name}');` }).execute(); appliedMigrations.push(migration); } return appliedMigrations; } }; // src/databases/d1.ts var D1QB = class extends QueryBuilder { db; constructor(db, options) { super(options); this.db = db; } migrations(options) { return new asyncMigrationsBuilder(options, this); } async execute(query) { return await this.loggerWrapper(query, this.options.logger, async () => { let stmt = this.db.prepare(query.query); if (query.arguments) { stmt = stmt.bind(...query.arguments); } if (query.fetchType === "ONE" /* ONE */ || query.fetchType === "ALL" /* ALL */) { const resp = await stmt.all(); return { changes: resp.meta?.changes, duration: resp.meta?.duration, last_row_id: resp.meta?.last_row_id, served_by: resp.meta?.served_by, meta: resp.meta, success: resp.success, results: query.fetchType === "ONE" /* ONE */ ? resp.results[0] : resp.results }; } return stmt.run(); }); } async batchExecute(queryArray) { return await this.loggerWrapper(queryArray, this.options.logger, async () => { const statements = queryArray.map((query) => { let stmt = this.db.prepare(query.query); if (query.arguments) { stmt = stmt.bind(...query.arguments); } return stmt; }); const responses = await this.db.batch(statements); return responses.map( (resp, i) => { if (queryArray && queryArray[i] !== void 0 && queryArray[i]?.fetchType) { return { changes: resp.meta?.changes, duration: resp.meta?.duration, last_row_id: resp.meta?.last_row_id, served_by: resp.meta?.served_by, meta: resp.meta, success: resp.success, results: queryArray[i]?.fetchType === "ONE" /* ONE */ ? resp.results?.[0] : resp.results }; } else { return { changes: resp.meta?.changes, duration: resp.meta?.duration, last_row_id: resp.meta?.last_row_id, served_by: resp.meta?.served_by, meta: resp.meta, success: resp.success }; } } ); }); } }; // src/databases/pg.ts var PGQB = class extends QueryBuilder { db; _migrationsBuilder = asyncMigrationsBuilder; constructor(db, options) { super(options); this.db = db; } migrations(options) { return new asyncMigrationsBuilder(options, this); } async connect() { await this.db.connect(); } async close() { await this.db.end(); } async execute(query) { return await this.loggerWrapper(query, this.options.logger, async () => { const queryString = query.query.replaceAll("?", "$"); let result; if (query.arguments) { result = await this.db.query({ values: query.arguments, text: queryString }); } else { result = await this.db.query({ text: queryString }); } if (query.fetchType === "ONE" /* ONE */ || query.fetchType === "ALL" /* ALL */) { return { command: result.command, lastRowId: result.oid, rowCount: result.rowCount, results: query.fetchType === "ONE" /* ONE */ ? result.rows[0] : result.rows }; } return { command: result.command, lastRowId: result.oid, rowCount: result.rowCount }; }); } }; // src/databases/do.ts var DOQB = class extends QueryBuilder { db; loggerWrapper = syncLoggerWrapper; constructor(db, options) { super(options); this.db = db; } migrations(options) { return new syncMigrationsBuilder(options, this); } execute(query) { return this.loggerWrapper(query, this.options.logger, () => { let cursor; if (query.arguments) { cursor = this.db.exec(query.query, ...query.arguments); } else { cursor = this.db.exec(query.query); } const result = cursor.toArray(); if (query.fetchType == "ONE" /* ONE */) { return { results: result.length > 0 ? result[0] : void 0 }; } return { results: result }; }); } lazyExecute(query) { return this.loggerWrapper(query, this.options.logger, () => { let cursor; if (query.arguments) { cursor = this.db.exec(query.query, ...query.arguments); } else { cursor = this.db.exec(query.query); } return cursor; }); } }; export { ConflictTypes, D1QB, DOQB, FetchTypes, JoinTypes, OrderTypes, PGQB, Query, QueryBuilder, QueryWithExtra, Raw, asyncLoggerWrapper, asyncMigrationsBuilder, defaultLogger, syncLoggerWrapper, syncMigrationsBuilder, trimQuery };