diff --git a/src/columns.ts b/src/columns.ts index bf2f502..6c7e6f4 100644 --- a/src/columns.ts +++ b/src/columns.ts @@ -2,6 +2,15 @@ type RedefineValueTypes = { [K in keyof ShapeToRedefine]: NewValueType; }; +/** Types that may appear in a row */ +export type ColumnValue = + | string + | number + | boolean + | bigint + | NodeJS.TypedArray + | null; + /** Types that define the data type of a column in the DB */ export type DataType = "TEXT" | "INTEGER" | "REAL" | "BLOB" | "NULL"; diff --git a/src/table.ts b/src/table.ts index b9f7beb..3c71a89 100644 --- a/src/table.ts +++ b/src/table.ts @@ -1,7 +1,8 @@ -import type { Database, Statement } from "bun:sqlite"; +import type { Database, SQLQueryBindings, Statement } from "bun:sqlite"; import { type ColumnMap, type ColumnShorthandMap, + type ColumnValue, type DataType, generateColumnDefinitionSQL, normalizeColumns, @@ -11,19 +12,6 @@ import { Prequel } from "./prequel"; export type ExtractRowShape = T extends Table ? R : never; -/** Types that may appear in a row */ -type ColumnValue = string | number | boolean | bigint | null; - -function isColumnValue(val: unknown): val is ColumnValue { - return ( - typeof val === "string" || - typeof val === "number" || - typeof val === "boolean" || - typeof val === "bigint" || - val === null - ); -} - export class PrequelIDNotFoundError extends Error { constructor(tableName: string, id: ColumnValue) { super(`ID ${id} not found in ${tableName}`); @@ -36,23 +24,10 @@ export class PrequelEmptyResultsError extends Error { } } -/** An arbitrarily shaped Row */ -type ArbitraryRow = Record; - -function shapeForQuery(obj: Partial): ArbitraryRow { - const asInsertDataShape: ArbitraryRow = {}; - - const keys = Object.keys(obj); - - for (const key of keys) { - const val = obj[key as keyof T]; - - if (val !== undefined && !isColumnValue(val)) { - throw new SchemaError( - `Invalid value in query: ${key} -> ${val} (type: ${typeof val})`, - ); - } +function shapeForQuery(obj: Partial): SQLQueryBindings { + const asInsertDataShape: SQLQueryBindings = {}; + for (const [key, val] of Object.entries(obj)) { const asQueryKey = `$${key}`; asInsertDataShape[asQueryKey] = val as ColumnValue; } @@ -73,11 +48,11 @@ export class Table { private _createTableSQL: string; private _primaryColumnName = "ROWID"; private _primaryColumnType: DataType | null = null; - private _insertQuery: Statement; - private _updateQuery: Statement; + private _insertQuery: Statement; + private _updateQuery: Statement; private _findQuery: Statement; private _deleteQuery: Statement; - private _deleteRowQuery: Statement; + private _deleteRowQuery: Statement; private _sizeQuery: Statement<{ count: number }, []>; private _truncateQuery: Statement; @@ -141,11 +116,11 @@ export class Table { this.db.exec(this._createTableSQL); // Prepare common queries - this._insertQuery = this.db.query( + this._insertQuery = this.db.query( `INSERT INTO "${name}" (${allCols}) VALUES (${allColVars}) RETURNING *;`, ); - this._updateQuery = this.db.query( + this._updateQuery = this.db.query( `UPDATE "${name}" SET ${updateColumnSQL} WHERE "${this._primaryColumnName}" = $${this._primaryColumnName} RETURNING *;`, ); @@ -161,7 +136,7 @@ export class Table { `SELECT count(*) as count FROM ${name}`, ); - this._deleteRowQuery = this.db.query( + this._deleteRowQuery = this.db.query( `DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = $${this._primaryColumnName};`, ); @@ -229,7 +204,7 @@ export class Table { * @typeParam RowShape - The shape of the row in the table. */ public insertPartial(data: Partial): RowShape { - const asInsertDataShape: ArbitraryRow = shapeForQuery(data); + const asInsertDataShape: SQLQueryBindings = shapeForQuery(data); const keys = Object.keys(data); const colNames = keys.join(", "); const varNames = keys.map((keyName) => `$${keyName}`); @@ -261,7 +236,7 @@ export class Table { ); } - const asInsertDataShape: ArbitraryRow = shapeForQuery(data); + const asInsertDataShape: SQLQueryBindings = shapeForQuery(data); return this._updateQuery.get(asInsertDataShape); } @@ -284,7 +259,7 @@ export class Table { const whereParts = whereKeys.map((key) => `"${key}" = $${key}`); const whereClause = whereParts.join(" AND "); - const query = this._db.prepare( + const query = this._db.prepare( `UPDATE ${this} SET ${setClause} WHERE ${whereClause} RETURNING *;`, ); @@ -308,7 +283,7 @@ export class Table { const setParts = keys.map((key) => `"${key}" = $${key}`); const setClause = setParts.join(", "); - const query = this._db.prepare( + const query = this._db.prepare( `UPDATE ${this} SET ${setClause} RETURNING *;`, ); @@ -345,7 +320,7 @@ export class Table { const keys = Object.keys(conditions); const whereParts = keys.map((key) => `"${key}" = $${key}`); const whereClause = whereParts.join(" AND "); - const query = this._db.prepare<{ count: number }, ArbitraryRow>( + const query = this._db.prepare<{ count: number }, SQLQueryBindings>( `SELECT count(*) as count FROM ${this} WHERE ${whereClause};`, ); @@ -401,7 +376,7 @@ export class Table { const keys = Object.keys(conditions); const whereParts = keys.map((key) => `"${key}" = $${key}`); const whereClause = whereParts.join(" AND "); - const query = this._db.prepare( + const query = this._db.prepare( `SELECT * FROM ${this} WHERE ${whereClause};`, ); @@ -459,7 +434,7 @@ export class Table { } } - const query = this._db.prepare( + const query = this._db.prepare( `SELECT * FROM ${this} WHERE ${whereClause} ${optionParts.join(" ")};`, ); @@ -480,7 +455,7 @@ export class Table { ); } - const asDeleteDataShape: ArbitraryRow = shapeForQuery(data); + const asDeleteDataShape: SQLQueryBindings = shapeForQuery(data); this._deleteRowQuery.run(asDeleteDataShape); } @@ -509,7 +484,7 @@ export class Table { const keys = Object.keys(conditions); const whereParts = keys.map((key) => `"${key}" = $${key}`); const whereClause = whereParts.join(" AND "); - const query = this._db.prepare( + const query = this._db.prepare( `DELETE FROM ${this} WHERE ${whereClause};`, );