Add biome

This commit is contained in:
Endeavorance 2025-04-06 19:28:42 -04:00
parent 09d25c926c
commit 6479d09196
9 changed files with 586 additions and 538 deletions

34
biome.json Normal file
View file

@ -0,0 +1,34 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false,
"ignore": ["dist"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": {
"noArrayIndexKey": "off"
}
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
}
}

BIN
bun.lockb

Binary file not shown.

View file

@ -5,14 +5,13 @@
"types": "./dist/index.d.ts",
"scripts": {
"build": "bun run ./build.ts",
"clean": "rm -rf dist"
"clean": "rm -rf dist",
"fmt": "biome check --fix"
},
"keywords": [],
"author": "Endeavorance <hello@endeavorance.camp> (https://endeavorance.camp)",
"license": "CC BY-NC-SA 4.0",
"files": [
"dist"
],
"files": ["dist"],
"type": "module",
"devDependencies": {
"@types/bun": "latest",
@ -20,5 +19,8 @@
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@biomejs/biome": "^1.9.4"
}
}

View file

@ -28,7 +28,7 @@ export const ColumnOf = {
type: otherTable.primaryColumnType(),
references: otherTable.reference(),
nullable: false,
cascade
cascade,
};
},
@ -36,7 +36,6 @@ export const ColumnOf = {
Text: {
type: "TEXT",
nullable: true,
},
Int: {
@ -94,7 +93,7 @@ export const ColumnOf = {
return {
type: "TEXT",
default: defaultValue,
}
};
},
Int(defaultValue: number): ColumnShorthand {
@ -109,7 +108,6 @@ export const ColumnOf = {
type: "REAL",
default: defaultValue,
} as const;
}
},
},
} as const;

View file

@ -1,6 +1,13 @@
import { ColumnOf } from "./column-types";
import type { Column, ColumnShorthand, DataType } from "./columns";
import { Prequel } from "./prequel";
import { Table } from "./table";
import { ColumnOf } from "./column-types";
export { Prequel, Table, ColumnOf, type DataType, type Column, type ColumnShorthand };
export {
Prequel,
Table,
ColumnOf,
type DataType,
type Column,
type ColumnShorthand,
};

View file

@ -4,194 +4,194 @@ import { SchemaError } from "./error";
import { Table } from "./table";
interface KVRow {
kv_id: number;
key: string;
value: string;
kv_id: number;
key: string;
value: string;
}
/**
* Represents a key-value store using Prequel.
*/
export class PrequelKVStore {
private table: Table<KVRow>;
private table: Table<KVRow>;
/**
* Creates a new instance of the PrequelKVStore class.
* @param pq The Prequel instance.
* @param tableName The name of the table.
*/
constructor(pq: Prequel, tableName: string) {
this.table = pq.table<KVRow>(tableName, {
kv_id: {
type: "INTEGER",
primary: true,
autoincrement: true,
},
key: "TEXT",
value: "TEXT",
});
}
/**
* Creates a new instance of the PrequelKVStore class.
* @param pq The Prequel instance.
* @param tableName The name of the table.
*/
constructor(pq: Prequel, tableName: string) {
this.table = pq.table<KVRow>(tableName, {
kv_id: {
type: "INTEGER",
primary: true,
autoincrement: true,
},
key: "TEXT",
value: "TEXT",
});
}
/**
* Inserts or updates a key-value pair in the store.
* @param key The key.
* @param value The value.
*/
put<T>(key: string, value: T): void {
const lookup = this.table.findOneWhere({
key,
});
/**
* Inserts or updates a key-value pair in the store.
* @param key The key.
* @param value The value.
*/
put<T>(key: string, value: T): void {
const lookup = this.table.findOneWhere({
key,
});
if (lookup === null) {
this.table.insertPartial({
key,
value: JSON.stringify(value),
});
} else {
lookup.value = JSON.stringify(value);
this.table.update(lookup);
}
}
if (lookup === null) {
this.table.insertPartial({
key,
value: JSON.stringify(value),
});
} else {
lookup.value = JSON.stringify(value);
this.table.update(lookup);
}
}
/**
* Retrieves the value associated with the specified key from the store.
* @param key The key.
* @returns The value associated with the key, or null if the key is not found.
*/
get<T>(key: string): T | null {
const lookup = this.table.findOneWhere({ key });
/**
* Retrieves the value associated with the specified key from the store.
* @param key The key.
* @returns The value associated with the key, or null if the key is not found.
*/
get<T>(key: string): T | null {
const lookup = this.table.findOneWhere({ key });
if (lookup === null) {
return null;
}
if (lookup === null) {
return null;
}
return JSON.parse(lookup.value) as T;
}
return JSON.parse(lookup.value) as T;
}
/**
* Checks if a key exists in the table.
* @param key - The key to check.
* @returns `true` if the key exists, `false` otherwise.
*/
has(key: string): boolean {
return (
this.table.findOneWhere({
key,
}) !== null
);
}
/**
* Checks if a key exists in the table.
* @param key - The key to check.
* @returns `true` if the key exists, `false` otherwise.
*/
has(key: string): boolean {
return (
this.table.findOneWhere({
key,
}) !== null
);
}
/**
* Get an array of all of the keys in the KV store
* @returns The keys in the store.
*/
keys(): string[] {
return this.table.findAll().map((row) => row.key);
}
/**
* Get an array of all of the keys in the KV store
* @returns The keys in the store.
*/
keys(): string[] {
return this.table.findAll().map((row) => row.key);
}
/**
* Get an array of all of the values in the KV store
* @returns The values in the store.
*/
values(): unknown[] {
return this.table.findAll().map((row) => JSON.parse(row.value));
}
/**
* Get an array of all of the values in the KV store
* @returns The values in the store.
*/
values(): unknown[] {
return this.table.findAll().map((row) => JSON.parse(row.value));
}
}
/**
* Represents a Prequel database instance.
*/
export class Prequel {
public db: Database;
public kv: PrequelKVStore;
public tables: Record<string, Table<unknown>> = {};
public query: typeof Database.prototype.query;
public uncachedQuery: typeof Database.prototype.prepare;
public db: Database;
public kv: PrequelKVStore;
public tables: Record<string, Table<unknown>> = {};
public query: typeof Database.prototype.query;
public uncachedQuery: typeof Database.prototype.prepare;
/**
* Creates a new Prequel database instance.
* @param filename The filename of the database. Defaults to ":memory:" if not provided.
*/
constructor(filename = ":memory:") {
this.db = new Database(filename);
this.query = this.db.query.bind(this.db);
this.uncachedQuery = this.db.prepare.bind(this.db);
this.db.exec("PRAGMA foreign_keys=ON");
this.kv = new PrequelKVStore(this, "PrequelManagedKVStore");
}
/**
* Creates a new Prequel database instance.
* @param filename The filename of the database. Defaults to ":memory:" if not provided.
*/
constructor(filename = ":memory:") {
this.db = new Database(filename);
this.query = this.db.query.bind(this.db);
this.uncachedQuery = this.db.prepare.bind(this.db);
this.db.exec("PRAGMA foreign_keys=ON");
this.kv = new PrequelKVStore(this, "PrequelManagedKVStore");
}
/**
* Enables Write-Ahead Logging (WAL) mode for the database.
*/
enableWAL() {
this.db.exec("PRAGMA journal_mode=WAL;");
}
/**
* Enables Write-Ahead Logging (WAL) mode for the database.
*/
enableWAL() {
this.db.exec("PRAGMA journal_mode=WAL;");
}
/**
* Closes the database connection.
*/
close() {
this.db.close();
}
/**
* Closes the database connection.
*/
close() {
this.db.close();
}
/**
* Checks if a table with the given name exists in the database.
* @param name The name of the table.
* @returns `true` if the table exists, `false` otherwise.
*/
hasTable(name: string): boolean {
return Object.keys(this.tables).includes(name);
}
/**
* Checks if a table with the given name exists in the database.
* @param name The name of the table.
* @returns `true` if the table exists, `false` otherwise.
*/
hasTable(name: string): boolean {
return Object.keys(this.tables).includes(name);
}
/**
* Creates a new table in the database.
* @param name The name of the table.
* @param cols The column definitions of the table.
* @returns The created table.
* @throws {SchemaError} If a table with the same name already exists.
*/
table<RowShape>(
name: string,
cols: ColumnShorthandMap<RowShape>,
): Table<RowShape> {
if (this.hasTable(name)) {
throw new SchemaError("Duplicate table name", name);
}
/**
* Creates a new table in the database.
* @param name The name of the table.
* @param cols The column definitions of the table.
* @returns The created table.
* @throws {SchemaError} If a table with the same name already exists.
*/
table<RowShape>(
name: string,
cols: ColumnShorthandMap<RowShape>,
): Table<RowShape> {
if (this.hasTable(name)) {
throw new SchemaError("Duplicate table name", name);
}
const newTable = new Table(this, name, cols);
this.tables[name] = newTable;
return newTable;
}
const newTable = new Table(this, name, cols);
this.tables[name] = newTable;
return newTable;
}
/**
* Attaches an existing table to the database.
* @param table The table to attach.
* @throws {SchemaError} If a table with the same name already exists.
*/
attachTable(table: Table<unknown>) {
if (this.hasTable(table.name)) {
throw new SchemaError("Duplicate table name", table.name);
}
/**
* Attaches an existing table to the database.
* @param table The table to attach.
* @throws {SchemaError} If a table with the same name already exists.
*/
attachTable(table: Table<unknown>) {
if (this.hasTable(table.name)) {
throw new SchemaError("Duplicate table name", table.name);
}
this.tables[table.name] = table;
}
/**
* Executes a function within a database transaction.
* If the function throws an error, the transaction is rolled back.
* Otherwise, the transaction is committed.
* @param transactionFn The function to execute within the transaction.
* @returns `true` if the transaction was committed successfully, otherwise `false`.
* @throws The error thrown by the transaction function if the transaction fails.
*/
transaction<T>(transactionFn: () => T): T {
try {
this.db.exec("BEGIN TRANSACTION;");
const result: T = transactionFn();
this.db.exec("COMMIT;");
return result;
} catch (error) {
this.db.exec("ROLLBACK;");
throw error;
}
}
this.tables[table.name] = table;
}
/**
* Executes a function within a database transaction.
* If the function throws an error, the transaction is rolled back.
* Otherwise, the transaction is committed.
* @param transactionFn The function to execute within the transaction.
* @returns `true` if the transaction was committed successfully, otherwise `false`.
* @throws The error thrown by the transaction function if the transaction fails.
*/
transaction<T>(transactionFn: () => T): T {
try {
this.db.exec("BEGIN TRANSACTION;");
const result: T = transactionFn();
this.db.exec("COMMIT;");
return result;
} catch (error) {
this.db.exec("ROLLBACK;");
throw error;
}
}
}

View file

@ -37,9 +37,15 @@ function shapeForQuery<T>(obj: Partial<T>): ArbitraryRow {
for (const key of keys) {
const val = obj[key as keyof T];
if (typeof val !== "string" && typeof val !== "number" && val !== null) {
if (
typeof val === "string" &&
typeof val === "number" &&
typeof val === "boolean" &&
typeof val === "bigint" &&
val !== null
) {
throw new SchemaError(
`Invalid value in query: ${key}: ${val} (type: ${typeof val})`,
`Invalid value in query: ${key} -> ${val} (type: ${typeof val})`,
);
}
@ -97,7 +103,7 @@ export class Table<RowShape> {
for (const columnName of this._columnNames) {
const columnDefinition =
this._columnDefinitions[
columnName as keyof typeof this._columnDefinitions
columnName as keyof typeof this._columnDefinitions
];
// Identify a primary column besides ROWID if specified
@ -160,7 +166,8 @@ export class Table<RowShape> {
}
public primaryColumnType(): DataType {
const primary = this._primaryColumnName as keyof typeof this._columnDefinitions;
const primary = this
._primaryColumnName as keyof typeof this._columnDefinitions;
return this._columnDefinitions[primary].type;
}

View file

@ -3,143 +3,143 @@ import { SchemaError } from "../src/error";
import { Prequel, Table } from "../src/index";
interface User {
user_id: number;
name: string;
user_id: number;
name: string;
}
test("constructor", () => {
const pq = new Prequel();
const users = pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const pq = new Prequel();
const users = pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
users.insertPartial({ name: "Yondu" });
users.insertPartial({ name: "Yondu" });
expect(users.findAll().length).toBe(1);
expect(users.findAll().length).toBe(1);
});
test(".hasTable() knows if a table exists in the DB", () => {
const pq = new Prequel();
const pq = new Prequel();
expect(pq.hasTable("Users")).toBeFalse();
expect(pq.hasTable("Notfound")).toBeFalse();
expect(pq.hasTable("Users")).toBeFalse();
expect(pq.hasTable("Notfound")).toBeFalse();
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(pq.hasTable("Users")).toBeTrue();
expect(pq.hasTable("Notfound")).toBeFalse();
expect(pq.hasTable("Users")).toBeTrue();
expect(pq.hasTable("Notfound")).toBeFalse();
});
test(".table() creates and attaches a table", () => {
const pq = new Prequel();
const pq = new Prequel();
// Already has one table because of KV
expect(Object.keys(pq.tables)).toHaveLength(1);
// Already has one table because of KV
expect(Object.keys(pq.tables)).toHaveLength(1);
const table = pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const table = pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(Object.keys(pq.tables)).toHaveLength(2);
expect(Object.values(pq.tables)).toContain(table);
expect(Object.keys(pq.tables)).toHaveLength(2);
expect(Object.values(pq.tables)).toContain(table);
});
test(".table() throws when creating a duplicate table", () => {
const pq = new Prequel();
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const pq = new Prequel();
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const oopsie = () => {
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
};
const oopsie = () => {
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
};
expect(oopsie).toThrow(SchemaError);
expect(oopsie).toThrow(SchemaError);
});
test(".attachTable() attaches a table", () => {
const pq = new Prequel();
const table = new Table<User>(pq.db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const pq = new Prequel();
const table = new Table<User>(pq.db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(pq.hasTable("Users")).toBeFalse();
expect(pq.hasTable("Users")).toBeFalse();
pq.attachTable(table);
pq.attachTable(table);
expect(pq.hasTable("Users")).toBeTrue();
expect(Object.values(pq.tables)).toContain(table);
expect(pq.hasTable("Users")).toBeTrue();
expect(Object.values(pq.tables)).toContain(table);
});
test(".attachTable() throws on duplicate table names", () => {
const pq = new Prequel();
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const pq = new Prequel();
pq.table<User>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const secondTable = new Table<User>(pq.db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const secondTable = new Table<User>(pq.db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const doAttachment = () => {
pq.attachTable(secondTable);
};
const doAttachment = () => {
pq.attachTable(secondTable);
};
expect(doAttachment).toThrow(SchemaError);
expect(doAttachment).toThrow(SchemaError);
});
test("kv store can set and retrieve arbitrary values", () => {
const pq = new Prequel();
const pq = new Prequel();
pq.kv.put<number>("answer", 42);
pq.kv.put<{ enabled: boolean }>("config", { enabled: true });
pq.kv.put<string[]>("list", ["a", "b", "c"]);
pq.kv.put<number>("answer", 42);
pq.kv.put<{ enabled: boolean }>("config", { enabled: true });
pq.kv.put<string[]>("list", ["a", "b", "c"]);
expect(pq.kv.get<number>("answer")).toEqual(42);
expect(pq.kv.get<{ enabled: boolean }>("config")).toEqual({ enabled: true });
expect(pq.kv.get<string[]>("list")).toEqual(["a", "b", "c"]);
expect(pq.kv.get<number>("answer")).toEqual(42);
expect(pq.kv.get<{ enabled: boolean }>("config")).toEqual({ enabled: true });
expect(pq.kv.get<string[]>("list")).toEqual(["a", "b", "c"]);
});
test(".query() creates a cached statement", () => {
const pq = new Prequel();
const query = pq.query("SELECT 1");
const pq = new Prequel();
const query = pq.query("SELECT 1");
expect(query).toBeDefined();
expect(query).toBe(pq.query("SELECT 1"));
expect(query).toBeDefined();
expect(query).toBe(pq.query("SELECT 1"));
});

View file

@ -3,414 +3,414 @@ import { expect, test } from "bun:test";
import { Prequel, Table } from "../src/index";
interface UserWithID {
user_id: number;
name: string;
user_id: number;
name: string;
}
interface UserWithBio {
user_id: number;
bio: {
name: string;
age: number;
};
user_id: number;
bio: {
name: string;
age: number;
};
}
interface Post {
post_id: number;
user_id: number;
post_id: number;
user_id: number;
}
interface UserWithNullable extends UserWithID {
bio: string | null;
bio: string | null;
}
function makeTestTable(): Table<UserWithID> {
const db = new Database();
return new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const db = new Database();
return new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
}
test("constructor", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(table.name).toEqual("Users");
expect(table.db).toBe(db);
expect(table.columns).toEqual(["user_id", "name"]);
expect(table.name).toEqual("Users");
expect(table.db).toBe(db);
expect(table.columns).toEqual(["user_id", "name"]);
});
test("constructor -> auto-attach to a prequel isntance", () => {
const db = new Prequel();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const db = new Prequel();
const table = new Table<UserWithID>(db, "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
expect(Object.values(db.tables)).toContain(table);
expect(Object.values(db.tables)).toContain(table);
});
test(".toString() resolves to the table name for embedding in queries", () => {
const table = makeTestTable();
expect(`${table}`).toEqual('"Users"');
const table = makeTestTable();
expect(`${table}`).toEqual('"Users"');
});
test(".reference() creates a default reference to the primary column", () => {
const table = makeTestTable();
expect(table.reference()).toEqual("Users(user_id)");
const table = makeTestTable();
expect(table.reference()).toEqual("Users(user_id)");
});
test(".reference() creates a reference to the specified column", () => {
const table = makeTestTable();
expect(table.reference("name")).toEqual("Users(name)");
const table = makeTestTable();
expect(table.reference("name")).toEqual("Users(name)");
});
test(".insert() inserts a full row", () => {
const table = makeTestTable();
table.insert({
user_id: 100,
name: "Jack",
});
const table = makeTestTable();
table.insert({
user_id: 100,
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(100);
expect(found).toEqual({
user_id: 100,
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(100);
expect(found).toEqual({
user_id: 100,
name: "Jack",
});
});
test(".insert() is able to insert nulls to nullable fields", () => {
const table = new Table<UserWithNullable>(new Database(), "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
bio: {
type: "TEXT",
nullable: true,
},
});
const table = new Table<UserWithNullable>(new Database(), "Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
bio: {
type: "TEXT",
nullable: true,
},
});
table.insert({
user_id: 1,
name: "Jack",
bio: null,
});
table.insert({
user_id: 1,
name: "Jack",
bio: null,
});
const lookup = table.db.prepare<UserWithNullable, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(1);
const lookup = table.db.prepare<UserWithNullable, number>(
`SELECT * FROM ${table} WHERE user_id = ?`,
);
const found = lookup.get(1);
expect(found?.bio).toBeNull();
expect(found?.bio).toBeNull();
});
test(".insertPartial() inserts a partial row", () => {
const table = makeTestTable();
table.insertPartial({
name: "Jack",
});
const table = makeTestTable();
table.insertPartial({
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
});
test(".insertWithout() inserts a partial row with type safety", () => {
const table = makeTestTable();
table.insertWithout<"user_id">({
name: "Jack",
});
const table = makeTestTable();
table.insertWithout<"user_id">({
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
const lookup = table.db.prepare<UserWithID, string>(
`SELECT * FROM ${table} WHERE name = ?`,
);
const found = lookup.get("Jack");
expect(found).toEqual({
user_id: 1,
name: "Jack",
});
});
test(".count() gets the current number of rows in the table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
});
test(".countWhere() gets the current number of rows that match a condition", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.countWhere({ name: "Jack" })).toBe(1);
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.countWhere({ name: "Jack" })).toBe(1);
});
test(".findAll() returns the full table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll();
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const allUsers = table.findAll();
expect(allUsers).toHaveLength(3);
expect(allUsers[0]).toEqual({ user_id: 1, name: "Jack" });
expect(allUsers[1]).toEqual({ user_id: 2, name: "Kate" });
expect(allUsers[2]).toEqual({ user_id: 3, name: "Sayid" });
expect(allUsers).toHaveLength(3);
expect(allUsers[0]).toEqual({ user_id: 1, name: "Jack" });
expect(allUsers[1]).toEqual({ user_id: 2, name: "Kate" });
expect(allUsers[2]).toEqual({ user_id: 3, name: "Sayid" });
});
test(".query() returns a prepared statement for the table", () => {
const table = makeTestTable();
const query = table.query("SELECT * FROM Users WHERE name = ?");
expect(query).toBeDefined();
const table = makeTestTable();
const query = table.query("SELECT * FROM Users WHERE name = ?");
expect(query).toBeDefined();
});
test(".findOneWhere() returns a row based on basic criteria", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneWhere({ name: "Sayid" });
const sayid = table.findOneWhere({ name: "Sayid" });
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneWhereOrFail() throws if it finds nothing", () => {
const table = makeTestTable();
const table = makeTestTable();
expect(() => {
table.findOneWhereOrFail({ name: "Sayid" });
}).toThrow();
expect(() => {
table.findOneWhereOrFail({ name: "Sayid" });
}).toThrow();
});
test(".findAllWhere() returns a row based on basic criteria", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" });
expect(manyJacks).toHaveLength(3);
expect(manyJacks[0]).toEqual({ user_id: 1, name: "Jack" });
expect(manyJacks[1]).toEqual({ user_id: 2, name: "Jack" });
expect(manyJacks[2]).toEqual({ user_id: 3, name: "Jack" });
expect(manyJacks).toHaveLength(3);
expect(manyJacks[0]).toEqual({ user_id: 1, name: "Jack" });
expect(manyJacks[1]).toEqual({ user_id: 2, name: "Jack" });
expect(manyJacks[2]).toEqual({ user_id: 3, name: "Jack" });
});
test(".findOneById() returns a single element by its primary column", () => {
const table = makeTestTable();
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneById() returns null when nothing was found", () => {
const table = makeTestTable();
const nobody = table.findOneById(3);
expect(nobody).toBeNull();
const table = makeTestTable();
const nobody = table.findOneById(3);
expect(nobody).toBeNull();
});
test(".findOneByIdOrFail() returns a single element by its primary column", () => {
const table = makeTestTable();
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
const sayid = table.findOneById(3);
expect(sayid).toEqual({ user_id: 3, name: "Sayid" });
});
test(".findOneByIdOrFail() throws an error when nothing is found", () => {
const table = makeTestTable();
expect(() => {
table.findOneByIdOrFail(3);
}).toThrow();
const table = makeTestTable();
expect(() => {
table.findOneByIdOrFail(3);
}).toThrow();
});
test(".delete() deletes a full record", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.size()).toBe(3);
expect(table.size()).toBe(3);
table.delete(table.findOneByIdOrFail(2));
table.delete(table.findOneByIdOrFail(2));
expect(table.size()).toBe(2);
expect(table.size()).toBe(2);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
});
test(".deleteById() deletes the element with the given primary value", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.size()).toBe(3);
expect(table.size()).toBe(3);
table.deleteById(2);
table.deleteById(2);
expect(table.size()).toBe(2);
expect(table.size()).toBe(2);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
expect(table.findAll()).toEqual([
{ user_id: 1, name: "Jack" },
{ user_id: 3, name: "Sayid" },
]);
});
test(".deleteWhere() deletes all rows that match", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Sayid" });
expect(table.size()).toBe(3);
expect(table.size()).toBe(3);
table.deleteWhere({ name: "Jack" });
table.deleteWhere({ name: "Jack" });
expect(table.size()).toBe(1);
expect(table.size()).toBe(1);
expect(table.findAll()).toEqual([{ user_id: 3, name: "Sayid" }]);
expect(table.findAll()).toEqual([{ user_id: 3, name: "Sayid" }]);
});
test(".deleteById() respects when references are set to cascade", () => {
const pq = new Prequel();
const users = pq.table<UserWithID>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const pq = new Prequel();
const users = pq.table<UserWithID>("Users", {
user_id: {
type: "INTEGER",
primary: true,
},
name: "TEXT",
});
const posts = pq.table<Post>("Posts", {
post_id: {
type: "INTEGER",
primary: true,
},
user_id: {
type: "INTEGER",
references: users.reference(),
cascade: true,
},
});
const posts = pq.table<Post>("Posts", {
post_id: {
type: "INTEGER",
primary: true,
},
user_id: {
type: "INTEGER",
references: users.reference(),
cascade: true,
},
});
const user = users.insertPartial({ name: "Bob" });
posts.insertPartial({
user_id: user.user_id,
});
const user = users.insertPartial({ name: "Bob" });
posts.insertPartial({
user_id: user.user_id,
});
users.deleteById(user.user_id);
expect(posts.size()).toBe(0);
users.deleteById(user.user_id);
expect(posts.size()).toBe(0);
});
test(".deleteAll() deletes all rows in the table", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
expect(table.count()).toBe(3);
expect(table.count()).toBe(3);
table.deleteAll();
table.deleteAll();
expect(table.count()).toBe(0);
expect(table.count()).toBe(0);
});
test(".update() updates a full record", () => {
const table = makeTestTable();
const locke = table.insertPartial({ name: "Locke" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
const table = makeTestTable();
const locke = table.insertPartial({ name: "Locke" });
table.insertPartial({ name: "Kate" });
table.insertPartial({ name: "Sayid" });
locke.name = "Jeremy Bentham";
table.update(locke);
locke.name = "Jeremy Bentham";
table.update(locke);
const newLocke = table.findOneById(1);
expect(newLocke?.name).toEqual("Jeremy Bentham");
const newLocke = table.findOneById(1);
expect(newLocke?.name).toEqual("Jeremy Bentham");
});
test(".update() throws if no primary column is set besides ROWID", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
const user = table.insert({ user_id: 1, name: "User" });
const user = table.insert({ user_id: 1, name: "User" });
const oops = () => {
table.update(user);
};
const oops = () => {
table.update(user);
};
expect(oops).toThrow(/primary column/);
expect(oops).toThrow(/primary column/);
});
test(".updateWhere() updates rows based on the condition", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.updateWhere({ name: "Sawyer" }, { user_id: 2 });
expect(table.findOneById(2)?.name).toEqual("Sawyer");
table.updateWhere({ name: "Sawyer" }, { user_id: 2 });
expect(table.findOneById(2)?.name).toEqual("Sawyer");
});
test(".updateAll() updates all rows in the table", () => {
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
const db = new Database();
const table = new Table<UserWithID>(db, "Users", {
user_id: "INTEGER",
name: "TEXT",
});
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.insert({ user_id: 1, name: "Jack" });
table.insert({ user_id: 2, name: "Kate" });
table.insert({ user_id: 3, name: "Sayid" });
table.updateAll({ name: "Sawyer" });
expect(table.findAll().map((u) => u.name)).toEqual([
"Sawyer",
"Sawyer",
"Sawyer",
]);
table.updateAll({ name: "Sawyer" });
expect(table.findAll().map((u) => u.name)).toEqual([
"Sawyer",
"Sawyer",
"Sawyer",
]);
});