Instance to wrappedrow

This commit is contained in:
Endeavorance 2025-01-01 11:53:02 -05:00
parent 47d0ec687b
commit 13c3d86a10
6 changed files with 59 additions and 73 deletions

View file

@ -1,14 +1,13 @@
import type { Column, ColumnShorthand, DataType } from "./columns"; import type { Column, ColumnShorthand, DataType } from "./columns";
import { type ExtractRowShape, Instance } from "./instance"; import { WrappedRow } from "./wrapped-row";
import { Prequel } from "./prequel"; import { Prequel } from "./prequel";
import { Table } from "./table"; import { Table } from "./table";
export { export {
Prequel, Prequel,
Table, Table,
Instance, WrappedRow,
type DataType, type DataType,
type Column, type Column,
type ColumnShorthand, type ColumnShorthand,
type ExtractRowShape,
}; };

View file

@ -1,49 +0,0 @@
import type { Table } from "./table";
export type ExtractRowShape<T> = T extends Table<infer R> ? R : never;
/**
* Represents an instance of a row in a table of a database.
* Wraps the raw Row data and provides methods for interacting with it.
*/
export abstract class Instance<
TableType extends Table<unknown>,
SerializedInstanceType = void,
> {
protected row: ExtractRowShape<TableType>;
protected table: TableType;
constructor(table: TableType, row: ExtractRowShape<TableType>) {
this.row = row;
this.table = table;
}
/**
* Saves any changes made to the row back to the database.
*/
save() {
this.table.update(this.row);
}
/**
* Serializes the instance into a generic format
*/
serialize(): SerializedInstanceType {
throw new Error("Not implemented");
}
/**
* @returns The row data as a JSON string.
*/
toJSON(): string {
return JSON.stringify(this.row);
}
/**
* Exports the raw row data.
* @returns The raw row data.
*/
export(): ExtractRowShape<TableType> {
return this.row;
}
}

View file

@ -7,12 +7,15 @@ import {
} from "./columns"; } from "./columns";
import { SchemaError } from "./error"; import { SchemaError } from "./error";
import { Prequel } from "./prequel"; import { Prequel } from "./prequel";
import type { WrappedRow } from "./wrapped-row";
export type ExtractRowShape<T> = T extends Table<infer R> ? R : never;
/** Types that may appear in a row */ /** Types that may appear in a row */
type Value = string | number | null; type ColumnValue = string | number | null;
export class PrequelIDNotFoundError extends Error { export class PrequelIDNotFoundError extends Error {
constructor(tableName: string, id: Value) { constructor(tableName: string, id: ColumnValue) {
super(`ID ${id} not found in ${tableName}`); super(`ID ${id} not found in ${tableName}`);
} }
} }
@ -25,11 +28,11 @@ export class PrequelEmptyResultsError extends Error {
/** An arbitrarily shaped Row */ /** An arbitrarily shaped Row */
interface ArbitraryRow { interface ArbitraryRow {
[key: string]: Value; [key: string]: ColumnValue;
} }
function shapeForQuery<T>(obj: Partial<T>): Record<string, Value> { function shapeForQuery<T>(obj: Partial<T>): Record<string, ColumnValue> {
const asInsertDataShape: Record<string, Value> = {}; const asInsertDataShape: Record<string, ColumnValue> = {};
const keys = Object.keys(obj); const keys = Object.keys(obj);
@ -43,7 +46,7 @@ function shapeForQuery<T>(obj: Partial<T>): Record<string, Value> {
} }
const asQueryKey = `$${key}`; const asQueryKey = `$${key}`;
asInsertDataShape[asQueryKey] = val as Value; asInsertDataShape[asQueryKey] = val as ColumnValue;
} }
return asInsertDataShape; return asInsertDataShape;
@ -282,7 +285,7 @@ export class Table<RowShape> {
* @param id - The ID of the entry to check for existence. * @param id - The ID of the entry to check for existence.
* @returns `true` if an entry with the specified ID exists, otherwise `false`. * @returns `true` if an entry with the specified ID exists, otherwise `false`.
*/ */
public exists(id: Value): boolean { public exists(id: ColumnValue): boolean {
return this.findOneById(id) !== null; return this.findOneById(id) !== null;
} }
@ -321,7 +324,7 @@ export class Table<RowShape> {
* @param id - The unique identifier of the row to find. * @param id - The unique identifier of the row to find.
* @returns The row shape if found, otherwise `null`. * @returns The row shape if found, otherwise `null`.
*/ */
public findOneById(id: Value): RowShape | null { public findOneById(id: ColumnValue): RowShape | null {
return this._findQuery.get(`${id}`); return this._findQuery.get(`${id}`);
} }
@ -333,7 +336,7 @@ export class Table<RowShape> {
* *
* @throws {Error} If no row is found with the specified ID. * @throws {Error} If no row is found with the specified ID.
*/ */
public findOneByIdOrFail(id: Value): RowShape { public findOneByIdOrFail(id: ColumnValue): RowShape {
const found = this.findOneById(id); const found = this.findOneById(id);
if (!found) { if (!found) {
@ -429,7 +432,7 @@ export class Table<RowShape> {
* @param id - The unique identifier of the record to be deleted. * @param id - The unique identifier of the record to be deleted.
* @returns void * @returns void
*/ */
public deleteById = (id: Value): void => { public deleteById = (id: ColumnValue): void => {
if (this._primaryColumnName === "ROWID") { if (this._primaryColumnName === "ROWID") {
throw new Error( throw new Error(
"Cannot use `Table.deleteById()` without setting a primary column", "Cannot use `Table.deleteById()` without setting a primary column",
@ -489,6 +492,11 @@ export class Table<RowShape> {
return res.count; return res.count;
} }
public save(wrapped: WrappedRow<RowShape>) {
const row = wrapped.unwrap();
this.update(row);
}
public toString() { public toString() {
return `"${this._name}"`; return `"${this._name}"`;
} }

26
src/wrapped-row.ts Normal file
View file

@ -0,0 +1,26 @@
/**
* Represents an instance of a row in a table of a database.
* Wraps the raw Row data and provides methods for interacting with it.
*/
export class WrappedRow<RowShape> {
protected row: RowShape;
constructor(row: RowShape) {
this.row = row;
}
/**
* @returns The row data as a JSON string.
*/
toJSON(): string {
return JSON.stringify(this.row);
}
/**
* Exports the raw row data.
* @returns The raw row data.
*/
unwrap(): RowShape {
return this.row;
}
}

View file

@ -7,6 +7,14 @@ interface UserWithID {
name: string; name: string;
} }
interface UserWithBio {
user_id: number;
bio: {
name: string;
age: number;
};
}
interface Post { interface Post {
post_id: number; post_id: number;
user_id: number; user_id: number;

View file

@ -1,6 +1,6 @@
import { Database } from "bun:sqlite"; import { Database } from "bun:sqlite";
import { expect, test } from "bun:test"; import { expect, test } from "bun:test";
import { Instance, Prequel, Table } from "../src/index"; import { WrappedRow, Table } from "../src/index";
interface User { interface User {
id: number; id: number;
@ -20,7 +20,7 @@ const table = new Table<User>(db, "Users", {
name: "TEXT", name: "TEXT",
}); });
class UserInstance extends Instance<typeof table, SerializedUser> { class UserWrappedRow extends WrappedRow<User> {
get name(): string { get name(): string {
return this.row.name; return this.row.name;
} }
@ -28,25 +28,19 @@ class UserInstance extends Instance<typeof table, SerializedUser> {
set name(val: string) { set name(val: string) {
this.row.name = val; this.row.name = val;
} }
serialize(): SerializedUser {
return {
name: this.row.name,
};
}
} }
test("setting values on an instance", () => { test("setting values on an WrappedRow", () => {
table.insert({ table.insert({
id: 1, id: 1,
name: "Alice", name: "Alice",
}); });
const alice = table.findOneByIdOrFail(1); const alice = table.findOneByIdOrFail(1);
const inst = new UserInstance(table, alice); const inst = new UserWrappedRow(alice);
inst.name = "Bob"; inst.name = "Bob";
inst.save(); table.save(inst);
const bob = table.findOneByIdOrFail(1); const bob = table.findOneByIdOrFail(1);
expect(bob.name).toEqual("Bob"); expect(bob.name).toEqual("Bob");