Compare commits

..

No commits in common. "ab41ff028d246c45aa2dd8114a185f8979edb0b9" and "3b0533b0a93efef50c21379192dc728bb6e6a5b0" have entirely different histories.

24 changed files with 160 additions and 146 deletions

View File

@ -110,35 +110,21 @@ export class AsyncResult<
/** /**
* Map a function over the error of the result. * Map a function over the error of the result.
*/ */
errorMap<TMappedError extends TaggedError>( mapError<TMappedError extends TaggedError>(
fn: (error: TError) => TMappedError, fn: (error: TError) => TMappedError,
): AsyncResult<TValue, TMappedError> { ): AsyncResult<TValue, TMappedError> {
return new AsyncResult( return new AsyncResult(
this.r.then((result) => result.errorMap(fn)), this.r.then((result) => result.mapError(fn)),
); );
} }
/** /**
* Execute a function if the result is not an error. * Taps a function if the result is a success.
* The function does not affect the result. * This is useful for side effects that do not modify the result.
*/ */
tap(fn: (value: TValue) => void): AsyncResult<TValue, TError> { tap(fn: (value: TValue) => void): AsyncResult<TValue, TError> {
return new AsyncResult( return new AsyncResult(
this.r.then((result) => result.tap(fn)), this.r.then((result) => result.tap(fn)),
); );
} }
assert<TResultValue, TResultError extends TaggedError>(
fn: (value: TValue) => AsyncResult<TResultValue, TResultError>,
): AsyncResult<TValue, TError | TResultError> {
return new AsyncResult(
this.r.then((result) => {
if (result.isError()) {
return result as any;
}
return (fn(result.unwrapOrThrow())).promise();
}),
);
}
} }

View File

@ -127,7 +127,7 @@ export class Result<TValue, TError extends TaggedError = never> {
/** /**
* Map a function over the error of the result. * Map a function over the error of the result.
*/ */
errorMap<TMappedError extends TaggedError>( mapError<TMappedError extends TaggedError>(
fn: (error: TError) => TMappedError, fn: (error: TError) => TMappedError,
): Result<TValue, TMappedError> { ): Result<TValue, TMappedError> {
if (isError(this.value)) { if (isError(this.value)) {
@ -143,19 +143,9 @@ export class Result<TValue, TError extends TaggedError = never> {
*/ */
tap(fn: (value: TValue) => void): Result<TValue, TError> { tap(fn: (value: TValue) => void): Result<TValue, TError> {
if (!isError(this.value)) { if (!isError(this.value)) {
try {
fn(this.value as TValue); fn(this.value as TValue);
} catch {
// do nothing
}
} }
return this; return this;
} }
assert<TResultValue, TResultError extends TaggedError>(
fn: (value: TValue) => Result<TResultValue, TResultError>,
): Result<TValue, TError | TResultError> {
return this.flatMap((value) => fn(value).map(() => value));
}
} }

View File

@ -1,14 +0,0 @@
import type { TaggedVariant, VariantTag } from "./variant.ts";
export function variantConstructor<
const T extends TaggedVariant<string>,
>(
tag: T[VariantTag],
) {
return <TOpts extends Omit<T, VariantTag>>(options: TOpts) => {
return {
_tag: tag,
...options,
} as const;
};
}

View File

@ -1,3 +1,2 @@
export * from "./constructor.ts";
export * from "./match.ts"; export * from "./match.ts";
export * from "./variant.ts"; export * from "./variant.ts";

View File

@ -6,3 +6,4 @@ export * from "./security/index.ts";
export * from "./services/index.ts"; export * from "./services/index.ts";
export * from "./use-case/index.ts"; export * from "./use-case/index.ts";
export * from "./utils/index.ts"; export * from "./utils/index.ts";
export * from "./validations/index.ts";

View File

@ -0,0 +1,37 @@
import type { Keyof } from "../../core/index.ts";
import type { CustomModelFields } from "./custom-model-fields.ts";
import { DefaultEntityFields, type EntityModel } from "./entity-model.ts";
import { Field } from "./fields/index.ts";
export const DefaultAggregateFields = {
...DefaultEntityFields,
streamId: Field.uuid({ isIndexed: true }),
streamVersion: Field.integer({
isUnsigned: true,
hasArbitraryPrecision: true,
}),
deletedAt: Field.timestamp({ isOptional: true }),
};
export interface AggregateModel<
TName extends string = string,
TFields extends CustomModelFields = CustomModelFields,
> extends EntityModel<TName, TFields> {
fields: typeof DefaultAggregateFields & TFields;
}
export function defineAggregateModel<
TName extends string,
TFields extends CustomModelFields,
>(name: TName, fields: TFields): AggregateModel<TName, TFields> {
return {
name,
fields: { ...DefaultAggregateFields, ...fields },
} as const;
}
export type ModelAddressableFields<TModel extends AggregateModel> = {
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends { isUnique: true }
? K
: never;
}[Keyof<TModel["fields"]>];

View File

@ -0,0 +1,3 @@
import type { FieldDefinition } from "./fields/index.ts";
export type CustomModelFields = Record<string, FieldDefinition>;

View File

@ -0,0 +1,14 @@
import type { CustomModelFields } from "./custom-model-fields.ts";
import { Field } from "./fields/index.ts";
import type { Model } from "./model.ts";
export const DefaultEntityFields = {
id: Field.uuid({ isPrimaryKey: true }),
};
export interface EntityModel<
TName extends string = string,
TFields extends CustomModelFields = CustomModelFields,
> extends Model<TName, TFields> {
fields: typeof DefaultEntityFields & TFields;
}

View File

@ -1,6 +1,6 @@
import { isError } from "@fabric/core"; import { isError } from "@fabric/core";
import { describe, expect, test } from "@fabric/testing"; import { describe, expect, test } from "@fabric/testing";
import { Model } from "../model.ts"; import { defineAggregateModel } from "../aggregate-model.ts";
import { Field } from "./index.ts"; import { Field } from "./index.ts";
import { import {
InvalidReferenceFieldError, InvalidReferenceFieldError,
@ -9,7 +9,7 @@ import {
describe("Validate Reference Field", () => { describe("Validate Reference Field", () => {
const schema = { const schema = {
User: Model.aggregateFrom("User", { User: defineAggregateModel("User", {
name: Field.string(), name: Field.string(),
password: Field.string(), password: Field.string(),
otherUnique: Field.integer({ isUnique: true }), otherUnique: Field.integer({ isUnique: true }),

View File

@ -1,6 +1,7 @@
export * from "./aggregate-model.ts";
export * from "./custom-model-fields.ts";
export * from "./fields/index.ts"; export * from "./fields/index.ts";
export * from "./model-schema.ts"; export * from "./model-schema.ts";
export * from "./model.ts"; export * from "./model.ts";
export * from "./state-store.ts"; export * from "./state-store.ts";
export * from "./store-query/index.ts"; export * from "./store-query/index.ts";
export * from "./validations/index.ts";

View File

@ -1,12 +1,13 @@
import type { UUID } from "@fabric/core"; import type { UUID } from "@fabric/core";
import { describe, expectTypeOf, test } from "@fabric/testing"; import { describe, expectTypeOf, test } from "@fabric/testing";
import type { PosixDate } from "../../core/index.ts"; import type { PosixDate } from "../../core/index.ts";
import { defineAggregateModel } from "./aggregate-model.ts";
import { Field } from "./fields/index.ts"; import { Field } from "./fields/index.ts";
import { Model, type ModelToType } from "./model.ts"; import type { ModelToType } from "./model.ts";
describe("CreateModel", () => { describe("CreateModel", () => {
test("should create a model and it's interface type", () => { test("should create a model and it's interface type", () => {
const User = Model.aggregateFrom("User", { const User = defineAggregateModel("User", {
name: Field.string(), name: Field.string(),
password: Field.string(), password: Field.string(),
phone: Field.string({ isOptional: true }), phone: Field.string({ isOptional: true }),

View File

@ -1,73 +1,29 @@
import type { Keyof } from "@fabric/core"; import type { Keyof } from "@fabric/core";
import type { CustomModelFields } from "./custom-model-fields.ts";
import type { FieldToType } from "./fields/field-to-type.ts"; import type { FieldToType } from "./fields/field-to-type.ts";
import { Field, type FieldDefinition } from "./fields/index.ts";
/** export interface Model<
* A model is a schema definition for some type of structured data.
*/
export class Model<
TName extends string = string, TName extends string = string,
TFields extends ModelFields = ModelFields, TFields extends CustomModelFields = CustomModelFields,
> { > {
static from<TName extends string, TFields extends ModelFields>( name: TName;
name: TName, fields: TFields;
fields: TFields,
) {
return new Model(name, fields);
} }
static aggregateFrom<TName extends string, TFields extends ModelFields>( export function defineModel<
name: TName, TName extends string,
fields: TFields, TFields extends CustomModelFields,
): Model<TName, TFields & typeof DefaultAggregateFields> { >(name: TName, fields: TFields): Model<TName, TFields> {
return new Model(name, { ...fields, ...DefaultAggregateFields }); return {
name,
fields,
} as const;
} }
static entityFrom<TName extends string, TFields extends ModelFields>(
name: TName,
fields: TFields,
): Model<TName, TFields & typeof DefaultEntityFields> {
return new Model(name, { ...fields, ...DefaultEntityFields });
}
private constructor(readonly name: TName, readonly fields: TFields) {}
}
export type EntityModel = Model<
string,
typeof DefaultEntityFields & ModelFields
>;
export type AggregateModel = Model<
string,
typeof DefaultAggregateFields & ModelFields
>;
export type ModelToType<TModel extends Model> = { export type ModelToType<TModel extends Model> = {
[K in Keyof<TModel["fields"]>]: FieldToType<TModel["fields"][K]>; [K in Keyof<TModel["fields"]>]: FieldToType<TModel["fields"][K]>;
}; };
export type ModelFieldNames<TModel extends ModelFields> = Keyof< export type ModelFieldNames<TModel extends CustomModelFields> = Keyof<
TModel["fields"] TModel["fields"]
>; >;
export type ModelAddressableFields<TModel extends Model> = {
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends { isUnique: true }
? K
: never;
}[Keyof<TModel["fields"]>];
type ModelFields = Record<string, FieldDefinition>;
const DefaultEntityFields = {
id: Field.uuid({ isPrimaryKey: true }),
} as const;
const DefaultAggregateFields = {
...DefaultEntityFields,
streamId: Field.uuid({ isIndexed: true }),
streamVersion: Field.integer({
isUnsigned: true,
hasArbitraryPrecision: true,
}),
deletedAt: Field.timestamp({ isOptional: true }),
} as const;

View File

@ -1,7 +1,8 @@
import type { VariantTag } from "@fabric/core"; import type { VariantTag } from "@fabric/core";
import type { Event } from "../events/event.ts"; import type { Event } from "../events/event.ts";
import type { StoredEvent } from "../events/stored-event.ts"; import type { StoredEvent } from "../events/stored-event.ts";
import type { AggregateModel, ModelToType } from "../models/model.ts"; import type { AggregateModel } from "../models/aggregate-model.ts";
import type { ModelToType } from "../models/model.ts";
export interface Projection< export interface Projection<
TModel extends AggregateModel, TModel extends AggregateModel,

View File

@ -5,7 +5,7 @@ import {
type VariantFromTag, type VariantFromTag,
} from "@fabric/core"; } from "@fabric/core";
import { isUUID, parseAndSanitizeString } from "@fabric/validations"; import { isUUID, parseAndSanitizeString } from "@fabric/validations";
import type { FieldDefinition, FieldToType } from "../index.ts"; import type { FieldDefinition, FieldToType } from "../models/index.ts";
export type FieldParsers = { export type FieldParsers = {
[K in FieldDefinition["_tag"]]: FieldParser< [K in FieldDefinition["_tag"]]: FieldParser<

View File

@ -1,7 +1,7 @@
// deno-lint-ignore-file no-explicit-any // deno-lint-ignore-file no-explicit-any
import { isRecordEmpty, Result, TaggedError } from "@fabric/core"; import { isRecordEmpty, Result, TaggedError } from "@fabric/core";
import type { FieldDefinition, Model, ModelToType } from "../index.ts"; import type { FieldDefinition, Model, ModelToType } from "../models/index.ts";
import { fieldParsers, type FieldParsingError } from "./field-parsers.ts"; import { fieldParsers, type FieldParsingError } from "./field-parsers.ts";
export function parseFromModel< export function parseFromModel<

View File

@ -1,4 +1,5 @@
import { import {
defineModel,
Field, Field,
isGreaterOrEqualTo, isGreaterOrEqualTo,
isGreaterThan, isGreaterThan,
@ -7,13 +8,12 @@ import {
isLessThan, isLessThan,
isLike, isLike,
isNotEqualTo, isNotEqualTo,
Model,
} from "@fabric/domain"; } from "@fabric/domain";
import { describe, expect, test } from "@fabric/testing"; import { describe, expect, test } from "@fabric/testing";
import { filterToParams, filterToSQL } from "./filter-to-sql.ts"; import { filterToParams, filterToSQL } from "./filter-to-sql.ts";
describe("SQL where clause from filter options", () => { describe("SQL where clause from filter options", () => {
const col = Model.from("users", { const col = defineModel("users", {
name: Field.string(), name: Field.string(),
age: Field.integer(), age: Field.integer(),
status: Field.string(), status: Field.string(),

View File

@ -10,7 +10,7 @@ import {
MultiFilterOption, MultiFilterOption,
SingleFilterOption, SingleFilterOption,
} from "@fabric/domain"; } from "@fabric/domain";
import { keyToParamKey } from "./record-utils.ts"; import { keyToParam } from "./record-utils.ts";
import { fieldValueToSQL } from "./value-to-sql.ts"; import { fieldValueToSQL } from "./value-to-sql.ts";
export function filterToSQL(filterOptions?: FilterOptions) { export function filterToSQL(filterOptions?: FilterOptions) {
@ -57,7 +57,7 @@ function getWhereFromSingleOption(
const WHERE_KEY_PREFIX = "where_"; const WHERE_KEY_PREFIX = "where_";
function getWhereParamKey(key: string, opts: { postfix?: string } = {}) { function getWhereParamKey(key: string, opts: { postfix?: string } = {}) {
return keyToParamKey(`${WHERE_KEY_PREFIX}${key}${opts.postfix ?? ""}`); return keyToParam(`${WHERE_KEY_PREFIX}${key}${opts.postfix ?? ""}`);
} }
function getWhereFromKeyValue( function getWhereFromKeyValue(

View File

@ -1,9 +1,9 @@
import { Field, Model } from "@fabric/domain"; import { defineModel, Field } from "@fabric/domain";
import { describe, expect, test } from "@fabric/testing"; import { describe, expect, test } from "@fabric/testing";
import { modelToSql } from "./model-to-sql.ts"; import { modelToSql } from "./model-to-sql.ts";
describe("ModelToSQL", () => { describe("ModelToSQL", () => {
const model = Model.from("something", { const model = defineModel("something", {
id: Field.uuid({ isPrimaryKey: true }), id: Field.uuid({ isPrimaryKey: true }),
name: Field.string(), name: Field.string(),
age: Field.integer(), age: Field.integer(),

View File

@ -14,23 +14,23 @@ export function recordToSQLKeys(record: Record<string, any>) {
/** /**
* Unfold a record into a string of it's keys separated by commas. * Unfold a record into a string of it's keys separated by commas.
*/ */
export function recordToSQLParamKeys(record: Record<string, any>) { export function recordToSQLKeyParams(record: Record<string, any>) {
return Object.keys(record) return Object.keys(record)
.map((key) => keyToParamKey(key)) .map((key) => keyToParam(key))
.join(", "); .join(", ");
} }
/** /**
* Unfold a record into a string of it's keys separated by commas. * Unfold a record into a string of it's keys separated by commas.
*/ */
export function recordToSQLParamRecord( export function recordToSQLParams(
model: Model, model: Model,
record: Record<string, any>, record: Record<string, any>,
) { ) {
return Object.keys(record).reduce( return Object.keys(record).reduce(
(acc, key) => ({ (acc, key) => ({
...acc, ...acc,
[keyToParamKey(key)]: fieldValueToSQL(model.fields[key]!, record[key]), [keyToParam(key)]: fieldValueToSQL(model.fields[key]!, record[key]),
}), }),
{}, {},
); );
@ -38,10 +38,10 @@ export function recordToSQLParamRecord(
export function recordToSQLSet(record: Record<string, any>) { export function recordToSQLSet(record: Record<string, any>) {
return Object.keys(record) return Object.keys(record)
.map((key) => `${key} = ${keyToParamKey(key)}`) .map((key) => `${key} = ${keyToParam(key)}`)
.join(", "); .join(", ");
} }
export function keyToParamKey(key: string) { export function keyToParam(key: string) {
return `$${key}`; return `$${key}`;
} }

View File

@ -26,9 +26,8 @@ export class SQLiteDatabase {
this.run("BEGIN TRANSACTION"); this.run("BEGIN TRANSACTION");
await fn(); await fn();
this.run("COMMIT"); this.run("COMMIT");
} catch (e) { } catch {
this.run("ROLLBACK"); this.run("ROLLBACK");
throw e;
} }
} }

View File

@ -3,8 +3,7 @@ import { AsyncResult, Keyof, Optional } from "@fabric/core";
import { import {
FilterOptions, FilterOptions,
Model, Model,
type ModelSchema, ModelSchema,
NotFoundError,
OrderByOptions, OrderByOptions,
SelectableQuery, SelectableQuery,
StoreLimitableQuery, StoreLimitableQuery,
@ -13,6 +12,7 @@ import {
StoreQueryError, StoreQueryError,
StoreSortableQuery, StoreSortableQuery,
} from "@fabric/domain"; } from "@fabric/domain";
import { NotFoundError } from "../../domain/models/store-query/store-query.ts";
import { filterToParams, filterToSQL } from "../sqlite/filter-to-sql.ts"; import { filterToParams, filterToSQL } from "../sqlite/filter-to-sql.ts";
import { transformRow } from "../sqlite/sql-to-value.ts"; import { transformRow } from "../sqlite/sql-to-value.ts";
import { SQLiteDatabase } from "../sqlite/sqlite-database.ts"; import { SQLiteDatabase } from "../sqlite/sqlite-database.ts";

View File

@ -1,5 +1,5 @@
import { Run, UUID } from "@fabric/core"; import { PosixDate, Run, UUID } from "@fabric/core";
import { Field, isLike, Model } from "@fabric/domain"; import { defineAggregateModel, Field, isLike } from "@fabric/domain";
import { UUIDGeneratorMock } from "@fabric/domain/mocks"; import { UUIDGeneratorMock } from "@fabric/domain/mocks";
import { import {
afterEach, afterEach,
@ -13,11 +13,11 @@ import { SQLiteStateStore } from "./state-store.ts";
describe("State Store", () => { describe("State Store", () => {
const models = [ const models = [
Model.entityFrom("demo", { defineAggregateModel("demo", {
value: Field.float(), value: Field.float(),
owner: Field.reference({ targetModel: "users" }), owner: Field.reference({ targetModel: "users" }),
}), }),
Model.entityFrom("users", { defineAggregateModel("users", {
name: Field.string(), name: Field.string(),
}), }),
]; ];
@ -39,6 +39,9 @@ describe("State Store", () => {
await store.insertInto("users", { await store.insertInto("users", {
id: newUUID, id: newUUID,
name: "test", name: "test",
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
}); });
@ -48,6 +51,9 @@ describe("State Store", () => {
await store.insertInto("users", { await store.insertInto("users", {
name: "test", name: "test",
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
const result = await store.from("users").select().unwrapOrThrow(); const result = await store.from("users").select().unwrapOrThrow();
@ -55,14 +61,20 @@ describe("State Store", () => {
expectTypeOf(result).toEqualTypeOf< expectTypeOf(result).toEqualTypeOf<
{ {
id: UUID; id: UUID;
streamId: UUID;
streamVersion: bigint;
name: string; name: string;
deletedAt: PosixDate | null;
}[] }[]
>(); >();
expect(result).toEqual([ expect(result).toEqual([
{ {
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
name: "test", name: "test",
deletedAt: null,
}, },
]); ]);
}); });
@ -75,16 +87,25 @@ describe("State Store", () => {
store.insertInto("users", { store.insertInto("users", {
name: "test", name: "test",
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}), }),
() => () =>
store.insertInto("users", { store.insertInto("users", {
name: "anotherName", name: "anotherName",
id: UUIDGeneratorMock.generate(), id: UUIDGeneratorMock.generate(),
streamId: UUIDGeneratorMock.generate(),
streamVersion: 1n,
deletedAt: null,
}), }),
() => () =>
store.insertInto("users", { store.insertInto("users", {
name: "anotherName2", name: "anotherName2",
id: UUIDGeneratorMock.generate(), id: UUIDGeneratorMock.generate(),
streamId: UUIDGeneratorMock.generate(),
streamVersion: 1n,
deletedAt: null,
}), }),
); );
@ -98,14 +119,20 @@ describe("State Store", () => {
expectTypeOf(result).toEqualTypeOf< expectTypeOf(result).toEqualTypeOf<
{ {
id: UUID; id: UUID;
streamId: UUID;
streamVersion: bigint;
name: string; name: string;
deletedAt: PosixDate | null;
}[] }[]
>(); >();
expect(result).toEqual([ expect(result).toEqual([
{ {
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
name: "test", name: "test",
deletedAt: null,
}, },
]); ]);
}); });
@ -116,6 +143,9 @@ describe("State Store", () => {
await store.insertInto("users", { await store.insertInto("users", {
name: "test", name: "test",
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
await store.update("users", newUUID, { await store.update("users", newUUID, {
@ -127,7 +157,10 @@ describe("State Store", () => {
expect(result).toEqual({ expect(result).toEqual({
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
name: "updated", name: "updated",
deletedAt: null,
}); });
}); });
@ -137,6 +170,9 @@ describe("State Store", () => {
await store.insertInto("users", { await store.insertInto("users", {
name: "test", name: "test",
id: newUUID, id: newUUID,
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
await store.delete("users", newUUID).orThrow(); await store.delete("users", newUUID).orThrow();
@ -156,12 +192,18 @@ describe("State Store", () => {
await store.insertInto("users", { await store.insertInto("users", {
id: ownerUUID, id: ownerUUID,
name: "test", name: "test",
streamId: ownerUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
await store.insertInto("demo", { await store.insertInto("demo", {
id: newUUID, id: newUUID,
value: 1.0, value: 1.0,
owner: ownerUUID, owner: ownerUUID,
streamId: newUUID,
streamVersion: 1n,
deletedAt: null,
}).orThrow(); }).orThrow();
}); });
}); });

View File

@ -1,6 +1,6 @@
import { AsyncResult, UnexpectedError, UUID } from "@fabric/core"; import { AsyncResult, UnexpectedError, UUID } from "@fabric/core";
import { import {
Model, type AggregateModel,
ModelSchemaFromModels, ModelSchemaFromModels,
ModelToType, ModelToType,
StoreQuery, StoreQuery,
@ -9,16 +9,16 @@ import {
} from "@fabric/domain"; } from "@fabric/domain";
import { modelToSql } from "../sqlite/model-to-sql.ts"; import { modelToSql } from "../sqlite/model-to-sql.ts";
import { import {
keyToParamKey, keyToParam,
recordToSQLKeyParams,
recordToSQLKeys, recordToSQLKeys,
recordToSQLParamKeys, recordToSQLParams,
recordToSQLParamRecord,
recordToSQLSet, recordToSQLSet,
} from "../sqlite/record-utils.ts"; } from "../sqlite/record-utils.ts";
import { SQLiteDatabase } from "../sqlite/sqlite-database.ts"; import { SQLiteDatabase } from "../sqlite/sqlite-database.ts";
import { QueryBuilder } from "./query-builder.ts"; import { QueryBuilder } from "./query-builder.ts";
export class SQLiteStateStore<TModel extends Model> export class SQLiteStateStore<TModel extends AggregateModel>
implements WritableStateStore<TModel> { implements WritableStateStore<TModel> {
private schema: ModelSchemaFromModels<TModel>; private schema: ModelSchemaFromModels<TModel>;
private db: SQLiteDatabase; private db: SQLiteDatabase;
@ -47,8 +47,8 @@ export class SQLiteStateStore<TModel extends Model>
recordToSQLKeys( recordToSQLKeys(
record, record,
) )
}) VALUES (${recordToSQLParamKeys(record)})`, }) VALUES (${recordToSQLKeyParams(record)})`,
recordToSQLParamRecord(model, record), recordToSQLParams(model, record),
); );
}, },
(error) => new StoreQueryError(error.message), (error) => new StoreQueryError(error.message),
@ -72,7 +72,7 @@ export class SQLiteStateStore<TModel extends Model>
return AsyncResult.tryFrom( return AsyncResult.tryFrom(
() => { () => {
const params = recordToSQLParamRecord(model, { const params = recordToSQLParams(model, {
...record, ...record,
id, id,
}); });
@ -81,7 +81,7 @@ export class SQLiteStateStore<TModel extends Model>
recordToSQLSet( recordToSQLSet(
record, record,
) )
} WHERE id = ${keyToParamKey("id")}`, } WHERE id = ${keyToParam("id")}`,
params, params,
); );
}, },
@ -98,10 +98,8 @@ export class SQLiteStateStore<TModel extends Model>
return AsyncResult.tryFrom( return AsyncResult.tryFrom(
() => { () => {
this.db.runPrepared( this.db.runPrepared(
`DELETE FROM ${model.name} WHERE id = ${keyToParamKey("id")}`, `DELETE FROM ${model.name} WHERE id = ${keyToParam("id")}`,
{ { $id: id },
[keyToParamKey("id")]: id,
},
); );
}, },
(error) => new StoreQueryError(error.message), (error) => new StoreQueryError(error.message),
@ -111,7 +109,7 @@ export class SQLiteStateStore<TModel extends Model>
migrate(): AsyncResult<void, StoreQueryError> { migrate(): AsyncResult<void, StoreQueryError> {
return AsyncResult.tryFrom( return AsyncResult.tryFrom(
async () => { async () => {
this.db.init(); await this.db.init();
await this.db.withTransaction(() => { await this.db.withTransaction(() => {
for (const modelKey in this.schema) { for (const modelKey in this.schema) {
const model = const model =