Feature: Basic Events, Models and Projections #2
@ -1,47 +1,60 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { VariantTag } from "@fabric/core";
|
import { Variant, VariantTag } from "@fabric/core";
|
||||||
import { BaseField, FieldDefinition, Model } from "@fabric/domain";
|
import { FieldDefinition, getTargetKey, Model } from "@fabric/domain";
|
||||||
|
|
||||||
type FieldMap = {
|
type FieldMap = {
|
||||||
[K in FieldDefinition[VariantTag]]: (
|
[K in FieldDefinition[VariantTag]]: (
|
||||||
|
name: string,
|
||||||
field: Extract<FieldDefinition, { [VariantTag]: K }>,
|
field: Extract<FieldDefinition, { [VariantTag]: K }>,
|
||||||
) => string;
|
) => string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FieldMap: FieldMap = {
|
const FieldMap: FieldMap = {
|
||||||
StringField: (f) => {
|
StringField: (n, f) => {
|
||||||
return "TEXT" + modifiersFromOpts(f);
|
return [n, "TEXT", modifiersFromOpts(f)].join(" ");
|
||||||
},
|
},
|
||||||
UUIDField: (f) => {
|
UUIDField: (n, f) => {
|
||||||
return [
|
return [
|
||||||
|
n,
|
||||||
"TEXT",
|
"TEXT",
|
||||||
f.isPrimaryKey ? "PRIMARY KEY" : "",
|
f.isPrimaryKey ? "PRIMARY KEY" : "",
|
||||||
modifiersFromOpts(f),
|
modifiersFromOpts(f),
|
||||||
].join(" ");
|
].join(" ");
|
||||||
},
|
},
|
||||||
IntegerField: function (): string {
|
IntegerField: function (n, f): string {
|
||||||
throw new Error("Function not implemented.");
|
return [n, "INTEGER", modifiersFromOpts(f)].join(" ");
|
||||||
},
|
},
|
||||||
ReferenceField: function (): string {
|
ReferenceField: function (n, f): string {
|
||||||
throw new Error("Function not implemented.");
|
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 [
|
return [
|
||||||
!options.isOptional ? "NOT NULL" : "",
|
!field.isOptional ? "NOT NULL" : "",
|
||||||
options.isUnique ? "UNIQUE" : "",
|
field.isUnique ? "UNIQUE" : "",
|
||||||
].join(" ");
|
].join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldDefinitionToSQL(field: FieldDefinition) {
|
function fieldDefinitionToSQL(name: string, field: FieldDefinition) {
|
||||||
return FieldMap[field[VariantTag]](field as any);
|
return FieldMap[field[VariantTag]](name, field as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modelToSql(
|
export function modelToSql(
|
||||||
model: Model<string, Record<string, FieldDefinition>>,
|
model: Model<string, Record<string, FieldDefinition>>,
|
||||||
) {
|
) {
|
||||||
return Object.entries(model.fields)
|
const fields = Object.entries(model.fields)
|
||||||
.map(([name, type]) => `${name} ${fieldDefinitionToSQL(type)}`)
|
.map(([name, type]) => fieldDefinitionToSQL(name, type))
|
||||||
.join(", ");
|
.join(", ");
|
||||||
|
|
||||||
|
return `CREATE TABLE ${model.name} (${fields})`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { afterEach, beforeEach, describe, expect, test } from "vitest";
|
||||||
import { SQLiteStorageDriver } from "./sqlite-driver.js";
|
import { SQLiteStorageDriver } from "./sqlite-driver.js";
|
||||||
|
|
||||||
describe("SQLite Store Driver", () => {
|
describe("SQLite Store Driver", () => {
|
||||||
const model = createModel({
|
const schema = {
|
||||||
name: "test",
|
users: defineModel("users", {
|
||||||
fields: {
|
|
||||||
id: Field.uuid({}),
|
|
||||||
name: Field.string(),
|
name: Field.string(),
|
||||||
},
|
}),
|
||||||
});
|
};
|
||||||
|
|
||||||
let store: SQLiteStorageDriver;
|
let store: SQLiteStorageDriver;
|
||||||
|
|
||||||
@ -23,71 +22,115 @@ describe("SQLite Store Driver", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test("should be able to synchronize the store and insert a record", async () => {
|
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;
|
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 () => {
|
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;
|
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 () => {
|
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;
|
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([]);
|
expect(records).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should be able to select records", async () => {
|
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;
|
if (isError(result)) throw result;
|
||||||
|
|
||||||
await store.insert("test", { id: "1", name: "test" });
|
await store.insert("users", {
|
||||||
await store.insert("test", { id: "2", name: "test" });
|
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([
|
expect(records).toEqual([
|
||||||
{ id: "1", name: "test" },
|
{ id: "1", name: "test", streamId: "1", streamVersion: 1 },
|
||||||
{ id: "2", name: "test" },
|
{ id: "2", name: "test", streamId: "2", streamVersion: 1 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should be able to select one record", async () => {
|
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;
|
if (isError(result)) throw result;
|
||||||
|
|
||||||
await store.insert("test", { id: "1", name: "test" });
|
await store.insert("users", {
|
||||||
await store.insert("test", { id: "2", name: "test" });
|
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,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { unlink } from "fs/promises";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
CircularDependencyError,
|
CircularDependencyError,
|
||||||
|
ModelSchema,
|
||||||
QueryDefinition,
|
QueryDefinition,
|
||||||
StorageDriver,
|
StorageDriver,
|
||||||
StoreQueryError,
|
StoreQueryError,
|
||||||
@ -35,6 +36,7 @@ export class SQLiteStorageDriver implements StorageDriver {
|
|||||||
|
|
||||||
// Enable Write-Ahead Logging, which is faster and more reliable.
|
// Enable Write-Ahead Logging, which is faster and more reliable.
|
||||||
this.db.run("PRAGMA journal_mode= WAL;");
|
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.
|
* Sincronice the store with the schema.
|
||||||
*/
|
*/
|
||||||
async sync(
|
async sync(
|
||||||
schema: ModelDefinition[],
|
schema: ModelSchema,
|
||||||
): AsyncResult<void, StoreQueryError | CircularDependencyError> {
|
): AsyncResult<void, StoreQueryError | CircularDependencyError> {
|
||||||
try {
|
try {
|
||||||
await dbRun(this.db, "BEGIN TRANSACTION;");
|
await dbRun(this.db, "BEGIN TRANSACTION;");
|
||||||
for (const model of schema) {
|
for (const modelKey in schema) {
|
||||||
const query = `CREATE TABLE ${model.name} (${modelToSql(model)});`;
|
const model = schema[modelKey];
|
||||||
await dbRun(this.db, query);
|
await dbRun(this.db, modelToSql(model));
|
||||||
}
|
}
|
||||||
await dbRun(this.db, "COMMIT;");
|
await dbRun(this.db, "COMMIT;");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user