d1-orm
Version:
A simple strictly typed ORM for Cloudflare's D1 product
266 lines • 13.3 kB
JavaScript
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Model_D1Orm, _Model_autoIncrementColumn, _Model_withRowId;
/* eslint-disable @typescript-eslint/no-duplicate-enum-values */
import { D1Orm, isDatabase } from "./database.js";
import { QueryType, GenerateQuery } from "./queryBuilder.js";
/**
* @typeParam T - The type of the model, which will be returned when using methods such as First() or All()
*/
export class Model {
/**
* @param options - The options for the model. All parameters except autoIncrement, withRowId, and uniqueKeys are required.
* @param options.tableName - The name of the table to use.
* @param options.D1Orm - The D1Orm instance to use - optional. If not set initially, you must use SetOrm() to set before querying.
* @param options.primaryKeys - The primary key or keys of the table.
* @param options.autoIncrement - The column to use for auto incrementing. If specified, only one primary key is allowed, and must be of type INTEGER.
* @param options.uniqueKeys - The unique keys of the table. For example `[ ['id'], ['username', 'discriminator'] ]` would cause ID to be unique, as well as the combination of username and discriminator.
* @param options.withRowId - Whether or not D1 should generate a `rowid` column automatically. Defaults to false.
* @param columns - The columns for the model. The keys are the column names, and the values are the column options. See {@link ModelColumn}
*/
constructor(options, columns) {
_Model_D1Orm.set(this, void 0);
_Model_autoIncrementColumn.set(this, void 0);
_Model_withRowId.set(this, void 0);
__classPrivateFieldSet(this, _Model_D1Orm, options.D1Orm ?? null, "f");
this.tableName = options.tableName;
this.columns = columns;
this.primaryKeys = Array.isArray(options.primaryKeys)
? options.primaryKeys
: [options.primaryKeys].filter(Boolean);
__classPrivateFieldSet(this, _Model_autoIncrementColumn, options.autoIncrement, "f");
this.uniqueKeys = options.uniqueKeys || [];
__classPrivateFieldSet(this, _Model_withRowId, options.withRowId ?? false, "f");
if (__classPrivateFieldGet(this, _Model_D1Orm, "f") &&
(!(__classPrivateFieldGet(this, _Model_D1Orm, "f") instanceof D1Orm) || !isDatabase(__classPrivateFieldGet(this, _Model_D1Orm, "f")))) {
throw new Error("Options.D1Orm is not an instance of D1Orm");
}
if (typeof this.tableName !== "string" || !this.tableName.length) {
throw new Error("Options.tableName must be a string");
}
if (!this.primaryKeys.length ||
this.primaryKeys.find((x) => typeof x !== "string" || !x.length)) {
throw new Error("Options.primaryKeys must be a string or an array of strings");
}
if (__classPrivateFieldGet(this, _Model_withRowId, "f") && __classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")) {
throw new Error("Options.autoIncrement and Options.withRowId cannot both be set");
}
if (!columns) {
throw new Error("Model columns must be defined");
}
const columnEntries = Object.entries(columns);
if (!columnEntries.length) {
throw new Error("Model columns cannot be empty");
}
if (this.primaryKeys.find((x) => !(x in columns))) {
throw new Error("Options.primaryKeys includes a column that does not exist");
}
if (__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")) {
if (typeof __classPrivateFieldGet(this, _Model_autoIncrementColumn, "f") !== "string") {
throw new Error("Options.autoIncrement was provided, but was not a string");
}
if (!this.primaryKeys.includes(__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f"))) {
throw new Error("Options.autoIncrement was provided, but was not a primary key");
}
if (this.primaryKeys.length > 1) {
throw new Error("Options.autoIncrement was provided, but there are multiple primary keys");
}
if (!this.columns[__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")]) {
throw new Error("Options.autoIncrement was provided, but is not a column");
}
if (this.columns[__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")].type !== DataTypes.INTEGER) {
throw new Error("Options.autoIncrement was provided, but is not an integer column");
}
}
}
/**
* @returns The ORM instance that this model is using.
*/
get D1Orm() {
if (!__classPrivateFieldGet(this, _Model_D1Orm, "f")) {
throw new Error("D1Orm has not been set");
}
return __classPrivateFieldGet(this, _Model_D1Orm, "f");
}
/**
* @param orm The ORM instance to associate this model with.
*/
SetOrm(orm) {
if (!(orm instanceof D1Orm) || !isDatabase(orm)) {
throw new Error("Options.D1Orm is not an instance of D1Orm");
}
__classPrivateFieldSet(this, _Model_D1Orm, orm, "f");
return this;
}
/**
* @returns A CreateTable definition for the model, which can be used in a CREATE TABLE statement.
*/
get createTableDefinition() {
const columnEntries = Object.entries(this.columns);
const columnDefinition = columnEntries.map(([columnName, column]) => {
let definition = `${columnName} ${column.type}`;
if (columnName === __classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")) {
definition += " PRIMARY KEY AUTOINCREMENT";
}
if (column.notNull) {
definition += " NOT NULL";
}
if (column.defaultValue !== undefined) {
let defaultStr = `${column.defaultValue}`;
if (typeof column.defaultValue === "string") {
defaultStr = `'${column.defaultValue}'`;
}
definition += ` DEFAULT ${defaultStr}`;
}
return definition;
});
if (!__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f")) {
columnDefinition.push(`PRIMARY KEY (${this.primaryKeys.join(", ")})`);
}
for (const i of this.uniqueKeys) {
columnDefinition.push(`UNIQUE (${i.join(", ")})`);
}
return `CREATE TABLE \`${this.tableName}\` (${columnDefinition.join(", ")})${__classPrivateFieldGet(this, _Model_autoIncrementColumn, "f") || __classPrivateFieldGet(this, _Model_withRowId, "f") ? "" : " WITHOUT ROWID"};`;
}
/**
* @param options The options for creating the table. Currently only contains strategy, which is the strategy to use when creating the table.
* - "default" - The default strategy, which will attempt create the table.
* - "force" - Drops the table if it exists, then creates it
* @throws
* - Throws an error if the table already exists and the strategy is not "force".
* - Throws an error if the strategy is "alter", as this is not yet implemented
*/
async CreateTable(options = {
strategy: "default",
}) {
const { strategy } = options;
// @ts-expect-error Alter is not yet implemented
if (strategy === "alter") {
throw new Error("Alter strategy is not implemented");
}
let statement = this.createTableDefinition;
if (strategy === "force") {
statement = `DROP TABLE IF EXISTS \`${this.tableName}\`\n${statement}`;
}
return this.D1Orm.exec(statement);
}
/**
* @param silent If true, will ignore the table not existing. If false, will throw an error if the table does not exist.
*/
async DropTable(silent) {
if (silent) {
return this.D1Orm.exec(`DROP TABLE IF EXISTS ${this.tableName};`);
}
return this.D1Orm.exec(`DROP TABLE ${this.tableName};`);
}
/**
* @param data The data to insert into the table, as an object with the column names as keys and the values as values.
*/
async InsertOne(data, orReplace = false) {
const qt = orReplace ? QueryType.INSERT_OR_REPLACE : QueryType.INSERT;
const statement = GenerateQuery(qt, this.tableName, { data });
return this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.run();
}
/**
* @param data The data to insert into the table, as an array of objects with the column names as keys and the values as values.
*/
async InsertMany(data, orReplace = false) {
const qt = orReplace ? QueryType.INSERT_OR_REPLACE : QueryType.INSERT;
const stmts = [];
for (const row of data) {
const stmt = GenerateQuery(qt, this.tableName, {
data: row,
});
stmts.push(this.D1Orm.prepare(stmt.query).bind(...stmt.bindings));
}
return this.D1Orm.batch(stmts);
}
/**
* @param options The options for the query, see {@link GenerateQueryOptions}
* @returns Returns the first row that matches the where clause, or null if no rows match.
*/
async First(options) {
const statement = GenerateQuery(QueryType.SELECT, this.tableName, Object.assign(options, { limit: 1 }));
try {
return await this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.first();
}
catch (e) {
if (e.message === "D1_NORESULTS") {
return null;
}
throw e;
}
}
/**
* @param options The options for the query, see {@link GenerateQueryOptions}
* @returns Returns all rows that match the where clause.
*/
async All(options) {
const statement = GenerateQuery(QueryType.SELECT, this.tableName, options);
return this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.all();
}
/**
* @param options The options for the query, see {@link GenerateQueryOptions}
*/
async Delete(options) {
const statement = GenerateQuery(QueryType.DELETE, this.tableName, options);
return this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.run();
}
/**
* @param options The options for the query, see {@link GenerateQueryOptions}
* @throws Throws an error if the data clause is empty.
*/
async Update(options) {
const statement = GenerateQuery(QueryType.UPDATE, this.tableName, options);
return this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.run();
}
/**
* Upserting is a way to insert a row into the table, or update it if it already exists.
* This is done by using SQLITE's ON CONFLICT clause. As a result, this method should control the primary key for the insert & where clauses, and should not be used with auto incrementing keys.
* @param options The options for the query, see {@link GenerateQueryOptions}
*/
async Upsert(options) {
const statement = GenerateQuery(QueryType.UPSERT, this.tableName, options, this.primaryKeys);
return this.D1Orm.prepare(statement.query)
.bind(...statement.bindings)
.run();
}
}
_Model_D1Orm = new WeakMap(), _Model_autoIncrementColumn = new WeakMap(), _Model_withRowId = new WeakMap();
/**
* @enum {string} Aliases for DataTypes used in a {@link ModelColumn} definition.
*/
export var DataTypes;
(function (DataTypes) {
DataTypes["INTEGER"] = "integer";
DataTypes["INT"] = "integer";
DataTypes["TEXT"] = "text";
DataTypes["STRING"] = "text";
DataTypes["VARCHAR"] = "text";
DataTypes["CHAR"] = "text";
DataTypes["NUMBER"] = "real";
DataTypes["NUMERIC"] = "real";
DataTypes["REAL"] = "real";
DataTypes["BLOB"] = "blob";
DataTypes["BOOLEAN"] = "boolean";
})(DataTypes || (DataTypes = {}));
//# sourceMappingURL=model.js.map