Feature: Basic Events, Models and Projections #2
@ -1,4 +1,4 @@
|
|||||||
import { isError } from "@fabric/core";
|
import { Run } from "@fabric/core";
|
||||||
import { defineModel, Field, isLike } from "@fabric/domain";
|
import { defineModel, Field, isLike } from "@fabric/domain";
|
||||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { SQLiteStorageDriver } from "./sqlite-driver.js";
|
import { SQLiteStorageDriver } from "./sqlite-driver.js";
|
||||||
@ -21,27 +21,24 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
const result = await driver.close();
|
await Run.UNSAFE(() => driver.close());
|
||||||
if (isError(result)) throw result;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should synchronize the store and insert a record", async () => {
|
it("should synchronize the store and insert a record", async () => {
|
||||||
const result = await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
if (isError(result)) throw result;
|
await Run.UNSAFE(() =>
|
||||||
|
driver.insert(schema.users, {
|
||||||
|
id: "1",
|
||||||
|
name: "test",
|
||||||
|
streamId: "1",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const insertResult = await driver.insert(schema.users, {
|
const records = await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.select(schema, { from: "users" }),
|
||||||
name: "test",
|
);
|
||||||
streamId: "1",
|
|
||||||
streamVersion: 1n,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isError(insertResult)) throw insertResult;
|
|
||||||
|
|
||||||
const records = (
|
|
||||||
await driver.select(schema, { from: "users" })
|
|
||||||
).unwrapOrThrow();
|
|
||||||
|
|
||||||
expect(records).toEqual([
|
expect(records).toEqual([
|
||||||
{ id: "1", name: "test", streamId: "1", streamVersion: 1n },
|
{ id: "1", name: "test", streamId: "1", streamVersion: 1n },
|
||||||
@ -49,21 +46,24 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be update a record", async () => {
|
it("should be update a record", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const err = await driver.update(schema.users, "1", { name: "updated" });
|
await Run.UNSAFE(() =>
|
||||||
if (isError(err)) throw err;
|
driver.update(schema.users, "1", { name: "updated" }),
|
||||||
|
);
|
||||||
|
|
||||||
const records = (
|
const records = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, { from: "users" })
|
driver.select(schema, { from: "users" }),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(records).toEqual([
|
expect(records).toEqual([
|
||||||
{ id: "1", name: "updated", streamId: "1", streamVersion: 1n },
|
{ id: "1", name: "updated", streamId: "1", streamVersion: 1n },
|
||||||
@ -71,43 +71,49 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to delete a record", async () => {
|
it("should be able to delete a record", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
await driver.delete(schema.users, "1");
|
await Run.UNSAFE(() => driver.delete(schema.users, "1"));
|
||||||
|
|
||||||
const records = (
|
const records = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, { from: "users" })
|
driver.select(schema, { from: "users" }),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(records).toEqual([]);
|
expect(records).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to select records", async () => {
|
it("should be able to select records", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
await driver.insert(schema.users, {
|
}),
|
||||||
id: "2",
|
);
|
||||||
name: "test",
|
await Run.UNSAFE(() =>
|
||||||
streamId: "2",
|
driver.insert(schema.users, {
|
||||||
streamVersion: 1n,
|
id: "2",
|
||||||
});
|
name: "test",
|
||||||
|
streamId: "2",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const records = (
|
const records = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, { from: "users" })
|
driver.select(schema, { from: "users" }),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(records).toEqual([
|
expect(records).toEqual([
|
||||||
{ id: "1", name: "test", streamId: "1", streamVersion: 1n },
|
{ id: "1", name: "test", streamId: "1", streamVersion: 1n },
|
||||||
@ -116,24 +122,28 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to select one record", async () => {
|
it("should be able to select one record", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
await driver.insert(schema.users, {
|
}),
|
||||||
id: "2",
|
);
|
||||||
name: "test",
|
await Run.UNSAFE(() =>
|
||||||
streamId: "2",
|
driver.insert(schema.users, {
|
||||||
streamVersion: 1n,
|
id: "2",
|
||||||
});
|
name: "test",
|
||||||
|
streamId: "2",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const record = (
|
const record = await Run.UNSAFE(() =>
|
||||||
await driver.selectOne(schema, { from: "users" })
|
driver.selectOne(schema, { from: "users" }),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(record).toEqual({
|
expect(record).toEqual({
|
||||||
id: "1",
|
id: "1",
|
||||||
@ -144,27 +154,31 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should select a record with a where clause", async () => {
|
it("should select a record with a where clause", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
await driver.insert(schema.users, {
|
}),
|
||||||
id: "2",
|
);
|
||||||
name: "jamón",
|
await Run.UNSAFE(() =>
|
||||||
streamId: "2",
|
driver.insert(schema.users, {
|
||||||
streamVersion: 1n,
|
id: "2",
|
||||||
});
|
name: "jamón",
|
||||||
|
streamId: "2",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const result = (
|
const result = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, {
|
driver.select(schema, {
|
||||||
from: "users",
|
from: "users",
|
||||||
where: { name: isLike("te%") },
|
where: { name: isLike("te%") },
|
||||||
})
|
}),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
@ -177,27 +191,31 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should select a record with a where clause of a specific type", async () => {
|
it("should select a record with a where clause of a specific type", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
await driver.insert(schema.users, {
|
}),
|
||||||
id: "2",
|
);
|
||||||
name: "jamón",
|
await Run.UNSAFE(() =>
|
||||||
streamId: "2",
|
driver.insert(schema.users, {
|
||||||
streamVersion: 1n,
|
id: "2",
|
||||||
});
|
name: "jamón",
|
||||||
|
streamId: "2",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const result = (
|
const result = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, {
|
driver.select(schema, {
|
||||||
from: "users",
|
from: "users",
|
||||||
where: { streamVersion: 1n },
|
where: { streamVersion: 1n },
|
||||||
})
|
}),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
@ -216,28 +234,32 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should select with a limit and offset", async () => {
|
it("should select with a limit and offset", async () => {
|
||||||
await driver.sync(schema);
|
await Run.UNSAFE(() => driver.sync(schema));
|
||||||
|
|
||||||
await driver.insert(schema.users, {
|
await Run.UNSAFE(() =>
|
||||||
id: "1",
|
driver.insert(schema.users, {
|
||||||
name: "test",
|
id: "1",
|
||||||
streamId: "1",
|
name: "test",
|
||||||
streamVersion: 1n,
|
streamId: "1",
|
||||||
});
|
streamVersion: 1n,
|
||||||
await driver.insert(schema.users, {
|
}),
|
||||||
id: "2",
|
);
|
||||||
name: "jamón",
|
await Run.UNSAFE(() =>
|
||||||
streamId: "2",
|
driver.insert(schema.users, {
|
||||||
streamVersion: 1n,
|
id: "2",
|
||||||
});
|
name: "jamón",
|
||||||
|
streamId: "2",
|
||||||
|
streamVersion: 1n,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const result = (
|
const result = await Run.UNSAFE(() =>
|
||||||
await driver.select(schema, {
|
driver.select(schema, {
|
||||||
from: "users",
|
from: "users",
|
||||||
limit: 1,
|
limit: 1,
|
||||||
offset: 1,
|
offset: 1,
|
||||||
})
|
}),
|
||||||
).unwrapOrThrow();
|
);
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
StorageDriver,
|
StorageDriver,
|
||||||
StoreQueryError,
|
StoreQueryError,
|
||||||
} from "@fabric/domain";
|
} from "@fabric/domain";
|
||||||
import { Database, Statement } from "sqlite3";
|
|
||||||
import { filterToParams, filterToSQL } from "./filter-to-sql.js";
|
import { filterToParams, filterToSQL } from "./filter-to-sql.js";
|
||||||
import { modelToSql } from "./model-to-sql.js";
|
import { modelToSql } from "./model-to-sql.js";
|
||||||
import {
|
import {
|
||||||
@ -22,44 +21,19 @@ import {
|
|||||||
recordToSQLSet,
|
recordToSQLSet,
|
||||||
} from "./record-utils.js";
|
} from "./record-utils.js";
|
||||||
import { transformRow } from "./sql-to-value.js";
|
import { transformRow } from "./sql-to-value.js";
|
||||||
import {
|
import { SQLiteDatabase } from "./sqlite/sqlite-wrapper.js";
|
||||||
dbClose,
|
|
||||||
dbRun,
|
|
||||||
finalize,
|
|
||||||
getAll,
|
|
||||||
getOne,
|
|
||||||
prepare,
|
|
||||||
run,
|
|
||||||
} from "./sqlite-wrapper.js";
|
|
||||||
|
|
||||||
export class SQLiteStorageDriver implements StorageDriver {
|
export class SQLiteStorageDriver implements StorageDriver {
|
||||||
private db: Database;
|
private db: SQLiteDatabase;
|
||||||
|
|
||||||
private cachedStatements = new Map<string, Statement>();
|
|
||||||
|
|
||||||
constructor(private path: string) {
|
constructor(private path: string) {
|
||||||
this.db = new Database(path);
|
this.db = new SQLiteDatabase(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private getSelectStatement(
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getSelectStatement(
|
|
||||||
collection: Collection,
|
collection: Collection,
|
||||||
query: QueryDefinition,
|
query: QueryDefinition,
|
||||||
): Promise<[Statement, Record<string, any>]> {
|
): [string, Record<string, any>] {
|
||||||
const selectFields = query.keys ? query.keys.join(", ") : "*";
|
const selectFields = query.keys ? query.keys.join(", ") : "*";
|
||||||
|
|
||||||
const queryFilter = filterToSQL(query.where);
|
const queryFilter = filterToSQL(query.where);
|
||||||
@ -75,7 +49,7 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
].join(" ");
|
].join(" ");
|
||||||
|
|
||||||
return [
|
return [
|
||||||
await this.getOrCreatePreparedStatement(sql),
|
sql,
|
||||||
{
|
{
|
||||||
...filterToParams(collection, query.where),
|
...filterToParams(collection, query.where),
|
||||||
},
|
},
|
||||||
@ -91,9 +65,10 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
): AsyncResult<void, StoreQueryError> {
|
): AsyncResult<void, StoreQueryError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
const sql = `INSERT INTO ${model.name} (${recordToSQLKeys(record)}) VALUES (${recordToSQLKeyParams(record)})`;
|
await this.db.runPrepared(
|
||||||
const stmt = await this.getOrCreatePreparedStatement(sql);
|
`INSERT INTO ${model.name} (${recordToSQLKeys(record)}) VALUES (${recordToSQLKeyParams(record)})`,
|
||||||
return await run(stmt, recordToSQLParams(model, record));
|
recordToSQLParams(model, record),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
(error) =>
|
(error) =>
|
||||||
new StoreQueryError(error.message, {
|
new StoreQueryError(error.message, {
|
||||||
@ -113,11 +88,15 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
): AsyncResult<any[], StoreQueryError> {
|
): AsyncResult<any[], StoreQueryError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
const [stmt, params] = await this.getSelectStatement(
|
const [sql, params] = this.getSelectStatement(
|
||||||
schema[query.from],
|
schema[query.from],
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
return await getAll(stmt, params, transformRow(schema[query.from]));
|
return this.db.allPrepared(
|
||||||
|
sql,
|
||||||
|
params,
|
||||||
|
transformRow(schema[query.from]),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
(err) =>
|
(err) =>
|
||||||
new StoreQueryError(err.message, {
|
new StoreQueryError(err.message, {
|
||||||
@ -136,11 +115,15 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
): AsyncResult<any, StoreQueryError> {
|
): AsyncResult<any, StoreQueryError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
const [stmt, params] = await this.getSelectStatement(
|
const [stmt, params] = this.getSelectStatement(
|
||||||
schema[query.from],
|
schema[query.from],
|
||||||
query,
|
query,
|
||||||
);
|
);
|
||||||
return await getOne(stmt, params, transformRow(schema[query.from]));
|
return await this.db.onePrepared(
|
||||||
|
stmt,
|
||||||
|
params,
|
||||||
|
transformRow(schema[query.from]),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
(err) =>
|
(err) =>
|
||||||
new StoreQueryError(err.message, {
|
new StoreQueryError(err.message, {
|
||||||
@ -158,19 +141,12 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
): AsyncResult<void, StoreQueryError | CircularDependencyError> {
|
): AsyncResult<void, StoreQueryError | CircularDependencyError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
// Enable Write-Ahead Logging, which is faster and more reliable.
|
await this.db.withTransaction(async () => {
|
||||||
await dbRun(this.db, "PRAGMA journal_mode = WAL;");
|
for (const modelKey in schema) {
|
||||||
|
const model = schema[modelKey];
|
||||||
// Enable foreign key constraints.
|
await this.db.runPrepared(modelToSql(model));
|
||||||
await dbRun(this.db, "PRAGMA foreign_keys = ON;");
|
}
|
||||||
|
});
|
||||||
// Begin a transaction to create the schema.
|
|
||||||
await dbRun(this.db, "BEGIN TRANSACTION;");
|
|
||||||
for (const modelKey in schema) {
|
|
||||||
const model = schema[modelKey];
|
|
||||||
await dbRun(this.db, modelToSql(model));
|
|
||||||
}
|
|
||||||
await dbRun(this.db, "COMMIT;");
|
|
||||||
},
|
},
|
||||||
(error) =>
|
(error) =>
|
||||||
new StoreQueryError(error.message, {
|
new StoreQueryError(error.message, {
|
||||||
@ -201,10 +177,7 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
|
|
||||||
async close(): AsyncResult<void, UnexpectedError> {
|
async close(): AsyncResult<void, UnexpectedError> {
|
||||||
return AsyncResult.from(async () => {
|
return AsyncResult.from(async () => {
|
||||||
for (const stmt of this.cachedStatements.values()) {
|
this.db.close();
|
||||||
await finalize(stmt);
|
|
||||||
}
|
|
||||||
await dbClose(this.db);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,13 +191,14 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
): AsyncResult<void, StoreQueryError> {
|
): AsyncResult<void, StoreQueryError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
const sql = `UPDATE ${model.name} SET ${recordToSQLSet(record)} WHERE id = ${keyToParam("id")}`;
|
|
||||||
const stmt = await this.getOrCreatePreparedStatement(sql);
|
|
||||||
const params = recordToSQLParams(model, {
|
const params = recordToSQLParams(model, {
|
||||||
...record,
|
...record,
|
||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
return await run(stmt, params);
|
await this.db.runPrepared(
|
||||||
|
`UPDATE ${model.name} SET ${recordToSQLSet(record)} WHERE id = ${keyToParam("id")}`,
|
||||||
|
params,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
(error) =>
|
(error) =>
|
||||||
new StoreQueryError(error.message, {
|
new StoreQueryError(error.message, {
|
||||||
@ -238,13 +212,13 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
/**
|
/**
|
||||||
* Delete a record from the store.
|
* Delete a record from the store.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async delete(model: Model, id: string): AsyncResult<void, StoreQueryError> {
|
async delete(model: Model, id: string): AsyncResult<void, StoreQueryError> {
|
||||||
return AsyncResult.tryFrom(
|
return AsyncResult.tryFrom(
|
||||||
async () => {
|
async () => {
|
||||||
const sql = `DELETE FROM ${model.name} WHERE id = ${keyToParam("id")}`;
|
await this.db.runPrepared(
|
||||||
const stmt = await this.getOrCreatePreparedStatement(sql);
|
`DELETE FROM ${model.name} WHERE id = ${keyToParam("id")}`,
|
||||||
return await run(stmt, { [keyToParam("id")]: id });
|
{ $id: id },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
(error) =>
|
(error) =>
|
||||||
new StoreQueryError(error.message, {
|
new StoreQueryError(error.message, {
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
||||||
import { Database, Statement } from "sqlite3";
|
|
||||||
|
|
||||||
export function dbRun(db: Database, statement: string): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
db.all(statement, (err, result) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
params: Record<string, any>,
|
|
||||||
transformer: (row: any) => any,
|
|
||||||
): Promise<Record<string, any>[]> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.all(params, (err: Error | null, rows: Record<string, any>[]) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(rows.map(transformer));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOne(
|
|
||||||
stmt: Statement,
|
|
||||||
params: Record<string, any>,
|
|
||||||
transformer: (row: any) => any,
|
|
||||||
): Promise<Record<string, any>> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.all(params, (err: Error | null, rows: Record<string, any>[]) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(rows.map(transformer)[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function finalize(stmt: Statement): Promise<void> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
stmt.finalize((err) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
134
packages/fabric/store-sqlite/src/sqlite/sqlite-wrapper.ts
Normal file
134
packages/fabric/store-sqlite/src/sqlite/sqlite-wrapper.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
import { MaybePromise } from "@fabric/core";
|
||||||
|
import SQLite from "sqlite3";
|
||||||
|
|
||||||
|
export class SQLiteDatabase {
|
||||||
|
db: SQLite.Database;
|
||||||
|
|
||||||
|
private cachedStatements = new Map<string, SQLite.Statement>();
|
||||||
|
|
||||||
|
constructor(private readonly path: string) {
|
||||||
|
this.db = new SQLite.Database(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.run("PRAGMA journal_mode = WAL");
|
||||||
|
await this.run("PRAGMA foreign_keys = ON");
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this.finalizeStatements();
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
this.db.close((err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async withTransaction(fn: () => MaybePromise<void>) {
|
||||||
|
try {
|
||||||
|
await this.run("BEGIN TRANSACTION");
|
||||||
|
await fn();
|
||||||
|
await this.run("COMMIT");
|
||||||
|
} catch {
|
||||||
|
await this.run("ROLLBACK");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run(sql: string, params?: Record<string, any>) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
this.db.run(sql, params, (err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
runPrepared(sql: string, params?: Record<string, any>) {
|
||||||
|
const cachedStmt = this.getCachedStatement(sql);
|
||||||
|
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
cachedStmt.run(params, (err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
allPrepared(
|
||||||
|
sql: string,
|
||||||
|
params?: Record<string, any>,
|
||||||
|
transformer?: (row: any) => any,
|
||||||
|
) {
|
||||||
|
const cachedStmt = this.getCachedStatement(sql);
|
||||||
|
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
cachedStmt.all(
|
||||||
|
params,
|
||||||
|
(err: Error | null, rows: Record<string, any>[]) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(transformer ? rows.map(transformer) : rows);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onePrepared(
|
||||||
|
sql: string,
|
||||||
|
params?: Record<string, any>,
|
||||||
|
transformer?: (row: any) => any,
|
||||||
|
) {
|
||||||
|
const cachedStmt = this.getCachedStatement(sql);
|
||||||
|
|
||||||
|
return new Promise<any>((resolve, reject) => {
|
||||||
|
cachedStmt.all(
|
||||||
|
params,
|
||||||
|
(err: Error | null, rows: Record<string, any>[]) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(transformer ? rows.map(transformer)[0] : rows[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCachedStatement(sql: string) {
|
||||||
|
let cached = this.cachedStatements.get(sql);
|
||||||
|
|
||||||
|
if (!cached) {
|
||||||
|
const stmt = this.db.prepare(sql);
|
||||||
|
this.cachedStatements.set(sql, stmt);
|
||||||
|
cached = stmt;
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async finalizeStatements() {
|
||||||
|
for (const stmt of this.cachedStatements.values()) {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
stmt.finalize((err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user