workers-qb
Version:
Zero dependencies Query Builder for Cloudflare Workers
909 lines (900 loc) • 25.3 kB
JavaScript
// 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
};