UNPKG

d1-orm

Version:

A simple strictly typed ORM for Cloudflare's D1 product

266 lines 13.3 kB
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