Improved typings
This commit is contained in:
parent
98a5b9c0e1
commit
45ece62045
2 changed files with 29 additions and 45 deletions
|
@ -2,6 +2,15 @@ type RedefineValueTypes<ShapeToRedefine, NewValueType> = {
|
||||||
[K in keyof ShapeToRedefine]: NewValueType;
|
[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 */
|
/** Types that define the data type of a column in the DB */
|
||||||
export type DataType = "TEXT" | "INTEGER" | "REAL" | "BLOB" | "NULL";
|
export type DataType = "TEXT" | "INTEGER" | "REAL" | "BLOB" | "NULL";
|
||||||
|
|
||||||
|
|
65
src/table.ts
65
src/table.ts
|
@ -1,7 +1,8 @@
|
||||||
import type { Database, Statement } from "bun:sqlite";
|
import type { Database, SQLQueryBindings, Statement } from "bun:sqlite";
|
||||||
import {
|
import {
|
||||||
type ColumnMap,
|
type ColumnMap,
|
||||||
type ColumnShorthandMap,
|
type ColumnShorthandMap,
|
||||||
|
type ColumnValue,
|
||||||
type DataType,
|
type DataType,
|
||||||
generateColumnDefinitionSQL,
|
generateColumnDefinitionSQL,
|
||||||
normalizeColumns,
|
normalizeColumns,
|
||||||
|
@ -11,19 +12,6 @@ import { Prequel } from "./prequel";
|
||||||
|
|
||||||
export type ExtractRowShape<T> = T extends Table<infer R> ? R : never;
|
export type ExtractRowShape<T> = T extends Table<infer R> ? 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 {
|
export class PrequelIDNotFoundError extends Error {
|
||||||
constructor(tableName: string, id: ColumnValue) {
|
constructor(tableName: string, id: ColumnValue) {
|
||||||
super(`ID ${id} not found in ${tableName}`);
|
super(`ID ${id} not found in ${tableName}`);
|
||||||
|
@ -36,23 +24,10 @@ export class PrequelEmptyResultsError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An arbitrarily shaped Row */
|
function shapeForQuery<T>(obj: Partial<T>): SQLQueryBindings {
|
||||||
type ArbitraryRow = Record<string, ColumnValue>;
|
const asInsertDataShape: SQLQueryBindings = {};
|
||||||
|
|
||||||
function shapeForQuery<T>(obj: Partial<T>): 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})`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (const [key, val] of Object.entries(obj)) {
|
||||||
const asQueryKey = `$${key}`;
|
const asQueryKey = `$${key}`;
|
||||||
asInsertDataShape[asQueryKey] = val as ColumnValue;
|
asInsertDataShape[asQueryKey] = val as ColumnValue;
|
||||||
}
|
}
|
||||||
|
@ -73,11 +48,11 @@ export class Table<RowShape> {
|
||||||
private _createTableSQL: string;
|
private _createTableSQL: string;
|
||||||
private _primaryColumnName = "ROWID";
|
private _primaryColumnName = "ROWID";
|
||||||
private _primaryColumnType: DataType | null = null;
|
private _primaryColumnType: DataType | null = null;
|
||||||
private _insertQuery: Statement<RowShape, [ArbitraryRow]>;
|
private _insertQuery: Statement<RowShape, [SQLQueryBindings]>;
|
||||||
private _updateQuery: Statement<RowShape, [ArbitraryRow]>;
|
private _updateQuery: Statement<RowShape, [SQLQueryBindings]>;
|
||||||
private _findQuery: Statement<RowShape, [string]>;
|
private _findQuery: Statement<RowShape, [string]>;
|
||||||
private _deleteQuery: Statement<void, [string]>;
|
private _deleteQuery: Statement<void, [string]>;
|
||||||
private _deleteRowQuery: Statement<void, [ArbitraryRow]>;
|
private _deleteRowQuery: Statement<void, [SQLQueryBindings]>;
|
||||||
private _sizeQuery: Statement<{ count: number }, []>;
|
private _sizeQuery: Statement<{ count: number }, []>;
|
||||||
private _truncateQuery: Statement<void, []>;
|
private _truncateQuery: Statement<void, []>;
|
||||||
|
|
||||||
|
@ -141,11 +116,11 @@ export class Table<RowShape> {
|
||||||
this.db.exec(this._createTableSQL);
|
this.db.exec(this._createTableSQL);
|
||||||
|
|
||||||
// Prepare common queries
|
// Prepare common queries
|
||||||
this._insertQuery = this.db.query<RowShape, ArbitraryRow>(
|
this._insertQuery = this.db.query<RowShape, SQLQueryBindings>(
|
||||||
`INSERT INTO "${name}" (${allCols}) VALUES (${allColVars}) RETURNING *;`,
|
`INSERT INTO "${name}" (${allCols}) VALUES (${allColVars}) RETURNING *;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
this._updateQuery = this.db.query<RowShape, ArbitraryRow>(
|
this._updateQuery = this.db.query<RowShape, SQLQueryBindings>(
|
||||||
`UPDATE "${name}" SET ${updateColumnSQL} WHERE "${this._primaryColumnName}" = $${this._primaryColumnName} RETURNING *;`,
|
`UPDATE "${name}" SET ${updateColumnSQL} WHERE "${this._primaryColumnName}" = $${this._primaryColumnName} RETURNING *;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -161,7 +136,7 @@ export class Table<RowShape> {
|
||||||
`SELECT count(*) as count FROM ${name}`,
|
`SELECT count(*) as count FROM ${name}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
this._deleteRowQuery = this.db.query<void, ArbitraryRow>(
|
this._deleteRowQuery = this.db.query<void, SQLQueryBindings>(
|
||||||
`DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = $${this._primaryColumnName};`,
|
`DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = $${this._primaryColumnName};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -229,7 +204,7 @@ export class Table<RowShape> {
|
||||||
* @typeParam RowShape - The shape of the row in the table.
|
* @typeParam RowShape - The shape of the row in the table.
|
||||||
*/
|
*/
|
||||||
public insertPartial(data: Partial<RowShape>): RowShape {
|
public insertPartial(data: Partial<RowShape>): RowShape {
|
||||||
const asInsertDataShape: ArbitraryRow = shapeForQuery<RowShape>(data);
|
const asInsertDataShape: SQLQueryBindings = shapeForQuery<RowShape>(data);
|
||||||
const keys = Object.keys(data);
|
const keys = Object.keys(data);
|
||||||
const colNames = keys.join(", ");
|
const colNames = keys.join(", ");
|
||||||
const varNames = keys.map((keyName) => `$${keyName}`);
|
const varNames = keys.map((keyName) => `$${keyName}`);
|
||||||
|
@ -261,7 +236,7 @@ export class Table<RowShape> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const asInsertDataShape: ArbitraryRow = shapeForQuery<RowShape>(data);
|
const asInsertDataShape: SQLQueryBindings = shapeForQuery<RowShape>(data);
|
||||||
return this._updateQuery.get(asInsertDataShape);
|
return this._updateQuery.get(asInsertDataShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +259,7 @@ export class Table<RowShape> {
|
||||||
const whereParts = whereKeys.map((key) => `"${key}" = $${key}`);
|
const whereParts = whereKeys.map((key) => `"${key}" = $${key}`);
|
||||||
const whereClause = whereParts.join(" AND ");
|
const whereClause = whereParts.join(" AND ");
|
||||||
|
|
||||||
const query = this._db.prepare<RowShape, ArbitraryRow>(
|
const query = this._db.prepare<RowShape, SQLQueryBindings>(
|
||||||
`UPDATE ${this} SET ${setClause} WHERE ${whereClause} RETURNING *;`,
|
`UPDATE ${this} SET ${setClause} WHERE ${whereClause} RETURNING *;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -308,7 +283,7 @@ export class Table<RowShape> {
|
||||||
const setParts = keys.map((key) => `"${key}" = $${key}`);
|
const setParts = keys.map((key) => `"${key}" = $${key}`);
|
||||||
const setClause = setParts.join(", ");
|
const setClause = setParts.join(", ");
|
||||||
|
|
||||||
const query = this._db.prepare<RowShape, ArbitraryRow>(
|
const query = this._db.prepare<RowShape, SQLQueryBindings>(
|
||||||
`UPDATE ${this} SET ${setClause} RETURNING *;`,
|
`UPDATE ${this} SET ${setClause} RETURNING *;`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -345,7 +320,7 @@ export class Table<RowShape> {
|
||||||
const keys = Object.keys(conditions);
|
const keys = Object.keys(conditions);
|
||||||
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
||||||
const whereClause = whereParts.join(" AND ");
|
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};`,
|
`SELECT count(*) as count FROM ${this} WHERE ${whereClause};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -401,7 +376,7 @@ export class Table<RowShape> {
|
||||||
const keys = Object.keys(conditions);
|
const keys = Object.keys(conditions);
|
||||||
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
||||||
const whereClause = whereParts.join(" AND ");
|
const whereClause = whereParts.join(" AND ");
|
||||||
const query = this._db.prepare<RowShape, ArbitraryRow>(
|
const query = this._db.prepare<RowShape, SQLQueryBindings>(
|
||||||
`SELECT * FROM ${this} WHERE ${whereClause};`,
|
`SELECT * FROM ${this} WHERE ${whereClause};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -459,7 +434,7 @@ export class Table<RowShape> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = this._db.prepare<RowShape, ArbitraryRow>(
|
const query = this._db.prepare<RowShape, SQLQueryBindings>(
|
||||||
`SELECT * FROM ${this} WHERE ${whereClause} ${optionParts.join(" ")};`,
|
`SELECT * FROM ${this} WHERE ${whereClause} ${optionParts.join(" ")};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -480,7 +455,7 @@ export class Table<RowShape> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const asDeleteDataShape: ArbitraryRow = shapeForQuery<RowShape>(data);
|
const asDeleteDataShape: SQLQueryBindings = shapeForQuery<RowShape>(data);
|
||||||
this._deleteRowQuery.run(asDeleteDataShape);
|
this._deleteRowQuery.run(asDeleteDataShape);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,7 +484,7 @@ export class Table<RowShape> {
|
||||||
const keys = Object.keys(conditions);
|
const keys = Object.keys(conditions);
|
||||||
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
const whereParts = keys.map((key) => `"${key}" = $${key}`);
|
||||||
const whereClause = whereParts.join(" AND ");
|
const whereClause = whereParts.join(" AND ");
|
||||||
const query = this._db.prepare<void, ArbitraryRow>(
|
const query = this._db.prepare<void, SQLQueryBindings>(
|
||||||
`DELETE FROM ${this} WHERE ${whereClause};`,
|
`DELETE FROM ${this} WHERE ${whereClause};`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue