Add basic sqlite-driver

This commit is contained in:
Pablo Baleztena 2024-09-17 17:23:57 -03:00
parent 61a92033f3
commit b5045ed4c8
35 changed files with 1486 additions and 109 deletions

View File

@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TaggedVariant } from "../../variant/variant.js";
import { UUID } from "../types/uuid.js";
/**
* An event is a tagged variant with a payload and a timestamp.
*/
export interface Event<TTag extends string, TPayload>
export interface Event<TTag extends string = string, TPayload = any>
extends TaggedVariant<TTag> {
streamId: UUID;
payload: TPayload;
timestamp: number;
}

View File

@ -1,4 +1,5 @@
export * from "./entity/index.js";
export * from "./models/index.js";
export * from "./security/index.js";
export * from "./types/index.js";
export * from "./use-case/index.js";

View File

@ -1,7 +1,8 @@
import { createStringField, StringField } from "./string-field.js";
import { createUUIDField } from "./uuid-field.js";
import { createUUIDField, UUIDField } from "./uuid-field.js";
export * from "./base-field.js";
export type FieldDefinition = StringField;
export type FieldDefinition = StringField | UUIDField;
export namespace Field {
export const string = createStringField;

View File

@ -11,7 +11,7 @@ export interface StringField
StringFieldOptions {}
export function createStringField<T extends StringFieldOptions>(
opts: T,
opts: T = {} as T,
): StringField & T {
return {
[VariantTag]: "StringField",

View File

@ -5,11 +5,11 @@ export interface UUIDOptions extends BaseField {
isPrimaryKey?: boolean;
}
export interface UUIDField extends TaggedVariant<"UUID_FIELD">, UUIDOptions {}
export interface UUIDField extends TaggedVariant<"UUIDField">, UUIDOptions {}
export function createUUIDField(opts: UUIDOptions): UUIDField {
return {
[VariantTag]: "UUID_FIELD",
[VariantTag]: "UUIDField",
...opts,
};
}

View File

@ -0,0 +1,4 @@
export * from "./create-model.js";
export * from "./fields/index.js";
export * from "./model-to-type.js";
export * from "./relations/index.js";

View File

@ -1,2 +1,3 @@
export * from "./is-error.js";
export * from "./tagged-error.js";
export * from "./unexpected-error.js";

View File

@ -8,7 +8,12 @@ import { TaggedError } from "./tagged-error.js";
* we must be prepared to handle.
*/
export class UnexpectedError extends TaggedError<"UnexpectedError"> {
constructor() {
constructor(readonly context: Record<string, unknown> = {}) {
super("UnexpectedError");
this.message = "An unexpected error occurred";
}
toString() {
return `UnexpectedError: ${this.message}\n${JSON.stringify(this.context, null, 2)}`;
}
}

View File

@ -3,6 +3,7 @@ export * from "./domain/index.js";
export * from "./error/index.js";
export * from "./record/index.js";
export * from "./result/index.js";
export * from "./storage/index.js";
export * from "./time/index.js";
export * from "./types/index.js";
export * from "./variant/index.js";

View File

@ -1,54 +0,0 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ModelDefinition } from "../domain/models/create-model.js";
import { TaggedError } from "../error/tagged-error.js";
import { AsyncResult } from "../result/async-result.js";
import { QueryDefinition } from "./query/query.js";
import { CircularDependencyError } from "./utils/sort-by-dependencies.js";
export interface StorageDriver {
/**
* Insert data into the store
*/
insert(collectionName: string, record: any): AsyncResult<void, QueryError>;
/**
* Run a select query against the store.
*/
select(query: QueryDefinition): AsyncResult<any[], QueryError>;
/**
* Run a select query against the store.
*/
selectOne(query: QueryDefinition): AsyncResult<any, QueryError>;
/**
* Sincronice the store with the schema.
*/
sync(
schema: ModelDefinition[],
): AsyncResult<void, QueryError | CircularDependencyError>;
/**
* Drop the store. This is a destructive operation.
*/
drop(): AsyncResult<void, QueryError>;
/**
* Update a record in the store.
*/
update(
collectionName: string,
id: string,
record: Record<string, any>,
): AsyncResult<void, QueryError>;
}
export class QueryError extends TaggedError<"QueryError"> {
constructor(
public message: string,
public context: any,
) {
super("QueryError");
}
}

View File

@ -0,0 +1,9 @@
import { TaggedError } from "../../error/tagged-error.js";
export class CircularDependencyError extends TaggedError<"CircularDependencyError"> {
context: { key: string; dep: string };
constructor(key: string, dep: string) {
super("CircularDependencyError");
this.context = { key, dep };
}
}

View File

@ -0,0 +1,2 @@
export * from "./circular-dependency-error.js";
export * from "./query-error.js";

View File

@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { TaggedError } from "../../error/tagged-error.js";
export class StoreQueryError extends TaggedError<"StoreQueryError"> {
constructor(
public message: string,
public context: any,
) {
super("StoreQueryError");
}
}

View File

@ -0,0 +1,43 @@
import { Event } from "../domain/events/event.js";
import { UUID } from "../domain/index.js";
import { AsyncResult } from "../result/async-result.js";
import { PosixDate } from "../time/posix-date.js";
import { MaybePromise } from "../types/maybe-promise.js";
import { StoreQueryError } from "./errors/query-error.js";
export interface EventStore<TEvent extends Event = Event> {
getStream<TEventStreamEvent extends TEvent>(
streamId: UUID,
): AsyncResult<EventStream<TEventStreamEvent>, StoreQueryError>;
appendToStream<TEvent extends Event>(
streamId: UUID,
events: TEvent,
): AsyncResult<void, StoreQueryError>;
}
export interface EventStream<TEvent extends Event = Event> {
getCurrentVersion(): bigint;
append(events: TEvent): AsyncResult<StoredEvent<TEvent>, StoreQueryError>;
subscribe(callback: (event: StoredEvent<TEvent>) => MaybePromise<void>): void;
getEvents(
opts?: EventFilterOptions,
): AsyncResult<StoredEvent<TEvent>[], StoreQueryError>;
}
export interface EventFilterOptions {
fromDate?: PosixDate;
toDate?: PosixDate;
fromVersion?: number;
toVersion?: number;
limit?: number;
offset?: number;
}
export type StoredEvent<TEvent extends Event = Event> = TEvent & {
version: bigint;
timestamp: number;
};

View File

@ -0,0 +1,5 @@
export * from "./errors/index.js";
export * from "./event-store.js";
export * from "./query/index.js";
export * from "./state-store.js";
export * from "./storage-driver.js";

View File

@ -0,0 +1,4 @@
export * from "./filter-options.js";
export * from "./order-by-options.js";
export * from "./query-builder.js";
export * from "./query.js";

View File

@ -6,7 +6,8 @@ import {
import { ModelToType } from "../../domain/models/model-to-type.js";
import { AsyncResult } from "../../result/async-result.js";
import { Keyof } from "../../types/index.js";
import { QueryError, StorageDriver } from "../driver.js";
import { StoreQueryError } from "../errors/query-error.js";
import { StorageDriver } from "../storage-driver.js";
import { FilterOptions } from "./filter-options.js";
import { OrderByOptions } from "./order-by-options.js";
import {
@ -56,7 +57,7 @@ export class QueryBuilder<
select<K extends Keyof<T>>(
keys?: K[],
): AsyncResult<Pick<T, K>[], QueryError> {
): AsyncResult<Pick<T, K>[], StoreQueryError> {
return this.driver.select({
...this.query,
keys,
@ -65,7 +66,7 @@ export class QueryBuilder<
selectOne<K extends Keyof<T>>(
keys?: K[],
): AsyncResult<Pick<T, K>, QueryError> {
): AsyncResult<Pick<T, K>, StoreQueryError> {
return this.driver.selectOne({
...this.query,
keys,

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { AsyncResult } from "../../result/async-result.js";
import { Keyof } from "../../types/keyof.js";
import { QueryError } from "../driver.js";
import { StoreQueryError } from "../errors/query-error.js";
import { FilterOptions } from "./filter-options.js";
import { OrderByOptions } from "./order-by-options.js";
@ -10,40 +10,56 @@ export interface StoreQuery<T> {
orderBy(opts: OrderByOptions<T>): StoreLimitableQuery<T>;
limit(limit: number, offset?: number): SelectableQuery<T>;
select(): AsyncResult<T[], QueryError>;
select<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>[], QueryError>;
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<T, QueryError>;
selectOne<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>, QueryError>;
selectOne(): AsyncResult<T, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError>;
}
export interface StoreSortableQuery<T> {
orderBy(opts: OrderByOptions<T>): StoreLimitableQuery<T>;
limit(limit: number, offset?: number): SelectableQuery<T>;
select(): AsyncResult<T[], QueryError>;
select<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>[], QueryError>;
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<T, QueryError>;
selectOne<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>, QueryError>;
selectOne(): AsyncResult<T, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError>;
}
export interface StoreLimitableQuery<T> {
limit(limit: number, offset?: number): SelectableQuery<T>;
select(): AsyncResult<T[], QueryError>;
select<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>[], QueryError>;
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<T, QueryError>;
selectOne<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>, QueryError>;
selectOne(): AsyncResult<T, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError>;
}
export interface SelectableQuery<T> {
select(): AsyncResult<T[], QueryError>;
select<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>[], QueryError>;
select(): AsyncResult<T[], StoreQueryError>;
select<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>[], StoreQueryError>;
selectOne(): AsyncResult<T, QueryError>;
selectOne<K extends Keyof<T>>(keys: K[]): AsyncResult<Pick<T, K>, QueryError>;
selectOne(): AsyncResult<T, StoreQueryError>;
selectOne<K extends Keyof<T>>(
keys: K[],
): AsyncResult<Pick<T, K>, StoreQueryError>;
}
export interface QueryDefinition<K extends string = string> {

View File

@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ModelDefinition } from "../domain/models/create-model.js";
import { UnexpectedError } from "../error/unexpected-error.js";
import { AsyncResult } from "../result/async-result.js";
import { CircularDependencyError } from "./errors/circular-dependency-error.js";
import { StoreQueryError } from "./errors/query-error.js";
import { QueryDefinition } from "./query/query.js";
export interface StorageDriver {
/**
* Insert data into the store
*/
insert(
collectionName: string,
record: Record<string, any>,
): AsyncResult<void, StoreQueryError>;
/**
* Run a select query against the store.
*/
select(query: QueryDefinition): AsyncResult<any[], StoreQueryError>;
/**
* Run a select query against the store.
*/
selectOne(query: QueryDefinition): AsyncResult<any, StoreQueryError>;
/**
* Sincronice the store with the schema.
*/
sync(
schema: ModelDefinition[],
): AsyncResult<void, StoreQueryError | CircularDependencyError>;
/**
* Drop the store. This is a destructive operation.
*/
drop(): AsyncResult<void, StoreQueryError>;
/**
* Close the store.
*/
close(): AsyncResult<void, UnexpectedError>;
/**
* Update a record in the store.
*/
update(
collectionName: string,
id: string,
record: Record<string, any>,
): AsyncResult<void, StoreQueryError>;
/**
* Delete a record from the store.
*/
delete(
collectionName: string,
id: string,
): AsyncResult<void, StoreQueryError>;
}

View File

@ -1,9 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, expect, it } from "vitest";
import {
CircularDependencyError,
sortByDependencies,
} from "./sort-by-dependencies.js";
import { CircularDependencyError } from "../errors/circular-dependency-error.js";
import { sortByDependencies } from "./sort-by-dependencies.js";
describe("sortByDependencies", () => {
it("should sort an array of objects by their dependencies", () => {

View File

@ -1,5 +1,5 @@
import { TaggedError } from "../../error/tagged-error.js";
import { Result } from "../../result/result.js";
import { CircularDependencyError } from "../errors/circular-dependency-error.js";
export function sortByDependencies<T>(
array: T[],
@ -45,11 +45,3 @@ export function sortByDependencies<T>(
(key) => array.find((element) => keyGetter(element) === key) as T,
);
}
export class CircularDependencyError extends TaggedError<"CircularDependencyError"> {
context: { key: string; dep: string };
constructor(key: string, dep: string) {
super("CircularDependencyError");
this.context = { key, dep };
}
}

View File

@ -1,4 +1,5 @@
export * from "./enum.js";
export * from "./fn.js";
export * from "./keyof.js";
export * from "./maybe-promise.js";
export * from "./optional.js";

View File

@ -0,0 +1 @@
export type MaybePromise<T> = T | Promise<T>;

View File

@ -0,0 +1 @@
# store-sqlite

View File

@ -0,0 +1,23 @@
{
"name": "@ulthar/store-sqlite",
"type": "module",
"module": "dist/index.js",
"main": "dist/index.js",
"files": [
"dist"
],
"private": true,
"packageManager": "yarn@4.1.1",
"devDependencies": {
"typescript": "^5.6.2",
"vitest": "^2.1.1"
},
"dependencies": {
"@ulthar/fabric-core": "workspace:^",
"sqlite3": "^5.1.7"
},
"scripts": {
"test": "vitest",
"build": "tsc -p tsconfig.build.json"
}
}

View File

@ -0,0 +1,45 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
BaseField,
FieldDefinition,
ModelDefinition,
VariantTag,
} from "@ulthar/fabric-core";
type FieldMap = {
[K in FieldDefinition[VariantTag]]: (
field: Extract<FieldDefinition, { [VariantTag]: K }>,
) => string;
};
const FieldMap: FieldMap = {
StringField: (f) => {
return "TEXT" + modifiersFromOpts(f);
},
UUIDField: (f) => {
return [
"TEXT",
f.isPrimaryKey ? "PRIMARY KEY" : "",
modifiersFromOpts(f),
].join(" ");
},
};
function modifiersFromOpts(options: BaseField) {
return [
!options.isOptional ? "NOT NULL" : "",
options.isUnique ? "UNIQUE" : "",
].join(" ");
}
function fieldDefinitionToSQL(field: FieldDefinition) {
return FieldMap[field[VariantTag]](field as any);
}
export function modelToSql(
model: ModelDefinition<string, Record<string, FieldDefinition>>,
) {
return Object.entries(model.fields)
.map(([name, type]) => `${name} ${fieldDefinitionToSQL(type)}`)
.join(", ");
}

View File

@ -0,0 +1,26 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* Unfold a record into a string of it's keys separated by commas.
*/
export function recordToKeys(record: Record<string, any>, prefix = "") {
return Object.keys(record)
.map((key) => `${prefix}${key}`)
.join(", ");
}
/**
* Unfold a record into a string of it's keys separated by commas.
*/
export function recordToParams(record: Record<string, any>) {
return Object.keys(record).reduce(
(acc, key) => ({ ...acc, [`:${key}`]: record[key] }),
{},
);
}
export function recordToSQLSet(record: Record<string, any>) {
return Object.keys(record)
.map((key) => `${key} = :${key}`)
.join(", ");
}

View File

@ -0,0 +1,93 @@
import { createModel, Field, isError } from "@ulthar/fabric-core";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { SQLiteStorageDriver } from "./sqlite-driver.js";
describe("SQLite Store Driver", () => {
const model = createModel({
name: "test",
fields: {
id: Field.uuid({}),
name: Field.string(),
},
});
let store: SQLiteStorageDriver;
beforeEach(() => {
store = new SQLiteStorageDriver(":memory:");
});
afterEach(async () => {
const result = await store.close();
if (isError(result)) throw result;
});
test("should be able to synchronize the store and insert a record", async () => {
const result = await store.sync([model]);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
const records = await store.select({ from: "test" });
expect(records).toEqual([{ id: "1", name: "test" }]);
});
test("should be able to update a record", async () => {
const result = await store.sync([model]);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.update("test", "1", { name: "updated" });
const records = await store.select({ from: "test" });
expect(records).toEqual([{ id: "1", name: "updated" }]);
});
test("should be able to delete a record", async () => {
const result = await store.sync([model]);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.delete("test", "1");
const records = await store.select({ from: "test" });
expect(records).toEqual([]);
});
test("should be able to select records", async () => {
const result = await store.sync([model]);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("test", { id: "2", name: "test" });
const records = await store.select({ from: "test" });
expect(records).toEqual([
{ id: "1", name: "test" },
{ id: "2", name: "test" },
]);
});
test("should be able to select one record", async () => {
const result = await store.sync([model]);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("test", { id: "2", name: "test" });
const record = await store.selectOne({ from: "test" });
expect(record).toEqual({ id: "1", name: "test" });
});
});

View File

@ -0,0 +1,207 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
AsyncResult,
CircularDependencyError,
ModelDefinition,
QueryDefinition,
StorageDriver,
StoreQueryError,
UnexpectedError,
} from "@ulthar/fabric-core";
import { unlink } from "fs/promises";
import { Database, Statement } from "sqlite3";
import { modelToSql } from "./model-to-sql.js";
import {
recordToKeys,
recordToParams,
recordToSQLSet,
} from "./record-utils.js";
import {
dbClose,
dbRun,
finalize,
getAll,
getOne,
prepare,
run,
} from "./sqlite-wrapper.js";
export class SQLiteStorageDriver implements StorageDriver {
private db: Database;
private cachedStatements = new Map<string, Statement>();
constructor(private path: string) {
this.db = new Database(path);
// Enable Write-Ahead Logging, which is faster and more reliable.
this.db.run("PRAGMA journal_mode= WAL;");
}
/**
* Get a statement from the cache or prepare a new one.
*/
private async getOrCreatePreparedStatement(sql: string): Promise<Statement> {
if (this.cachedStatements.has(sql)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- We know it's there.
return this.cachedStatements.get(sql)!;
}
const stmt = await prepare(this.db, sql);
this.cachedStatements.set(sql, stmt);
return stmt;
}
/**
* Insert data into the store
*/
async insert(
collectionName: string,
record: Record<string, any>,
): AsyncResult<void, StoreQueryError> {
try {
const sql = `INSERT INTO ${collectionName} (${recordToKeys(record)}) VALUES (${recordToKeys(record, ":")})`;
const stmt = await this.getOrCreatePreparedStatement(sql);
return await run(stmt, recordToParams(record));
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
collectionName,
record,
});
}
}
/**
* Run a select query against the store.
*/
async select(query: QueryDefinition): AsyncResult<any[], StoreQueryError> {
try {
const sql = `SELECT * FROM ${query.from}`;
const stmt = await this.getOrCreatePreparedStatement(sql);
return await getAll(stmt);
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
query,
});
}
}
/**
* Run a select query against the store.
*/
async selectOne(query: QueryDefinition): AsyncResult<any, StoreQueryError> {
try {
const sql = `SELECT * FROM ${query.from}`;
const stmt = await this.getOrCreatePreparedStatement(sql);
return await getOne(stmt);
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
query,
});
}
}
/**
* Sincronice the store with the schema.
*/
async sync(
schema: ModelDefinition[],
): AsyncResult<void, StoreQueryError | CircularDependencyError> {
try {
await dbRun(this.db, "BEGIN TRANSACTION;");
for (const model of schema) {
const query = `CREATE TABLE ${model.name} (${modelToSql(model)});`;
await dbRun(this.db, query);
}
await dbRun(this.db, "COMMIT;");
} catch (error: any) {
await dbRun(this.db, "ROLLBACK;");
return new StoreQueryError(error.message, {
error,
schema,
});
}
}
/**
* Drop the store. This is a destructive operation.
*/
async drop(): AsyncResult<void, StoreQueryError> {
try {
if (this.path === ":memory:") {
return new StoreQueryError("Cannot drop in-memory database", {});
} else {
await unlink(this.path);
}
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
});
}
}
async close(): AsyncResult<void, UnexpectedError> {
try {
for (const stmt of this.cachedStatements.values()) {
await finalize(stmt);
}
await dbClose(this.db);
} catch (error: any) {
return new UnexpectedError({ error });
}
}
/**
* Update a record in the store.
*/
async update(
collectionName: string,
id: string,
record: Record<string, any>,
): AsyncResult<void, StoreQueryError> {
try {
const sql = `UPDATE ${collectionName} SET ${recordToSQLSet(record)} WHERE id = :id`;
const stmt = await this.getOrCreatePreparedStatement(sql);
return await run(
stmt,
recordToParams({
...record,
id,
}),
);
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
collectionName,
record,
});
}
}
/**
* Delete a record from the store.
*/
async delete(
collectionName: string,
id: string,
): AsyncResult<void, StoreQueryError> {
try {
const sql = `DELETE FROM ${collectionName} WHERE id = :id`;
const stmt = await this.getOrCreatePreparedStatement(sql);
return await run(stmt, { ":id": id });
} catch (error: any) {
return new StoreQueryError(error.message, {
error,
collectionName,
id,
});
}
}
}

View File

@ -0,0 +1,89 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Database, Statement } from "sqlite3";
export function dbRun(db: Database, statement: string): Promise<void> {
return new Promise((resolve, reject) => {
db.run(statement, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export function dbClose(db: Database): Promise<void> {
return new Promise((resolve, reject) => {
db.close((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export function prepare(db: Database, statement: string): Promise<Statement> {
return new Promise((resolve, reject) => {
const stmt = db.prepare(statement, (err) => {
if (err) {
reject(err);
} else {
resolve(stmt);
}
});
});
}
export function run(
stmt: Statement,
params: Record<string, any>,
): Promise<void> {
return new Promise((resolve, reject) => {
stmt.run(params, (err: Error | null) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
export function getAll(stmt: Statement): Promise<Record<string, any>[]> {
return new Promise((resolve, reject) => {
stmt.all((err: Error | null, rows: Record<string, any>[]) => {
if (err) {
reject(err);
} else {
resolve(rows);
}
});
});
}
export function getOne(stmt: Statement): Promise<Record<string, any>> {
return new Promise((resolve, reject) => {
stmt.get((err: Error | null, row: Record<string, any>) => {
if (err) {
reject(err);
} else {
resolve(row);
}
});
});
}
export function finalize(stmt: Statement): Promise<void> {
return new Promise((resolve, reject) => {
stmt.finalize((err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}

View File

@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"allowImportingTsExtensions": false,
"outDir": "dist"
},
"exclude": [
"src/**/*.spec.ts",
"dist",
"node_modules",
"coverage",
"vitest.config.ts"
]
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.json",
"exclude": ["dist", "node_modules"]
}

View File

@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
exclude: ["**/index.ts"],
},
passWithNoTests: true,
},
});

788
yarn.lock

File diff suppressed because it is too large Load Diff