[fabric/store-sqlite] Pass all tests for base sqlite-driver implementation

This commit is contained in:
Pablo Baleztena 2024-10-04 11:56:35 -03:00
parent 09f045daf6
commit 27dbd44741
3 changed files with 109 additions and 51 deletions

View File

@ -1,47 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { VariantTag } from "@fabric/core";
import { BaseField, FieldDefinition, Model } from "@fabric/domain";
import { Variant, VariantTag } from "@fabric/core";
import { FieldDefinition, getTargetKey, Model } from "@fabric/domain";
type FieldMap = {
[K in FieldDefinition[VariantTag]]: (
name: string,
field: Extract<FieldDefinition, { [VariantTag]: K }>,
) => string;
};
const FieldMap: FieldMap = {
StringField: (f) => {
return "TEXT" + modifiersFromOpts(f);
StringField: (n, f) => {
return [n, "TEXT", modifiersFromOpts(f)].join(" ");
},
UUIDField: (f) => {
UUIDField: (n, f) => {
return [
n,
"TEXT",
f.isPrimaryKey ? "PRIMARY KEY" : "",
modifiersFromOpts(f),
].join(" ");
},
IntegerField: function (): string {
throw new Error("Function not implemented.");
IntegerField: function (n, f): string {
return [n, "INTEGER", modifiersFromOpts(f)].join(" ");
},
ReferenceField: function (): string {
throw new Error("Function not implemented.");
ReferenceField: function (n, f): string {
return [
n,
"TEXT",
modifiersFromOpts(f),
",",
`FOREIGN KEY (${n}) REFERENCES ${f.targetModel}(${getTargetKey(f)})`,
].join(" ");
},
};
function modifiersFromOpts(options: BaseField) {
function modifiersFromOpts(field: FieldDefinition) {
if (Variant.is(field, "UUIDField") && field.isPrimaryKey) {
return;
}
return [
!options.isOptional ? "NOT NULL" : "",
options.isUnique ? "UNIQUE" : "",
!field.isOptional ? "NOT NULL" : "",
field.isUnique ? "UNIQUE" : "",
].join(" ");
}
function fieldDefinitionToSQL(field: FieldDefinition) {
return FieldMap[field[VariantTag]](field as any);
function fieldDefinitionToSQL(name: string, field: FieldDefinition) {
return FieldMap[field[VariantTag]](name, field as any);
}
export function modelToSql(
model: Model<string, Record<string, FieldDefinition>>,
) {
return Object.entries(model.fields)
.map(([name, type]) => `${name} ${fieldDefinitionToSQL(type)}`)
const fields = Object.entries(model.fields)
.map(([name, type]) => fieldDefinitionToSQL(name, type))
.join(", ");
return `CREATE TABLE ${model.name} (${fields})`;
}

View File

@ -1,15 +1,14 @@
import { createModel, Field, isError } from "@fabric/core";
import { isError } from "@fabric/core";
import { defineModel, Field } from "@fabric/domain";
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({}),
const schema = {
users: defineModel("users", {
name: Field.string(),
},
});
}),
};
let store: SQLiteStorageDriver;
@ -23,71 +22,115 @@ describe("SQLite Store Driver", () => {
});
test("should be able to synchronize the store and insert a record", async () => {
const result = await store.sync([model]);
const result = await store.sync(schema);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("users", {
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
const records = await store.select({ from: "test" });
const records = await store.select({ from: "users" });
expect(records).toEqual([{ id: "1", name: "test" }]);
expect(records).toEqual([
{ id: "1", name: "test", streamId: "1", streamVersion: 1 },
]);
});
test("should be able to update a record", async () => {
const result = await store.sync([model]);
const result = await store.sync(schema);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("users", {
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
await store.update("test", "1", { name: "updated" });
await store.update("users", "1", { name: "updated" });
const records = await store.select({ from: "test" });
const records = await store.select({ from: "users" });
expect(records).toEqual([{ id: "1", name: "updated" }]);
expect(records).toEqual([
{ id: "1", name: "updated", streamId: "1", streamVersion: 1 },
]);
});
test("should be able to delete a record", async () => {
const result = await store.sync([model]);
const result = await store.sync(schema);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("users", {
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
await store.delete("test", "1");
await store.delete("users", "1");
const records = await store.select({ from: "test" });
const records = await store.select({ from: "users" });
expect(records).toEqual([]);
});
test("should be able to select records", async () => {
const result = await store.sync([model]);
const result = await store.sync(schema);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("test", { id: "2", name: "test" });
await store.insert("users", {
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
await store.insert("users", {
id: "2",
name: "test",
streamId: "2",
streamVersion: 1,
});
const records = await store.select({ from: "test" });
const records = await store.select({ from: "users" });
expect(records).toEqual([
{ id: "1", name: "test" },
{ id: "2", name: "test" },
{ id: "1", name: "test", streamId: "1", streamVersion: 1 },
{ id: "2", name: "test", streamId: "2", streamVersion: 1 },
]);
});
test("should be able to select one record", async () => {
const result = await store.sync([model]);
const result = await store.sync(schema);
if (isError(result)) throw result;
await store.insert("test", { id: "1", name: "test" });
await store.insert("test", { id: "2", name: "test" });
await store.insert("users", {
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
await store.insert("users", {
id: "2",
name: "test",
streamId: "2",
streamVersion: 1,
});
const record = await store.selectOne({ from: "test" });
const record = await store.selectOne({ from: "users" });
expect(record).toEqual({ id: "1", name: "test" });
expect(record).toEqual({
id: "1",
name: "test",
streamId: "1",
streamVersion: 1,
});
});
});

View File

@ -4,6 +4,7 @@ import { unlink } from "fs/promises";
import {
CircularDependencyError,
ModelSchema,
QueryDefinition,
StorageDriver,
StoreQueryError,
@ -35,6 +36,7 @@ export class SQLiteStorageDriver implements StorageDriver {
// Enable Write-Ahead Logging, which is faster and more reliable.
this.db.run("PRAGMA journal_mode= WAL;");
this.db.run("PRAGMA foreign_keys = ON;");
}
/**
@ -109,13 +111,13 @@ export class SQLiteStorageDriver implements StorageDriver {
* Sincronice the store with the schema.
*/
async sync(
schema: ModelDefinition[],
schema: ModelSchema,
): 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);
for (const modelKey in schema) {
const model = schema[modelKey];
await dbRun(this.db, modelToSql(model));
}
await dbRun(this.db, "COMMIT;");
} catch (error: any) {