From 9092b032b350dac91d3f1dc5c851f0b948ce8750 Mon Sep 17 00:00:00 2001 From: Pablo Baleztena Date: Fri, 4 Oct 2024 11:10:29 -0300 Subject: [PATCH] [fabric/domain] improve structure --- .../src/{storage => events}/event-store.ts | 2 +- packages/fabric/domain/src/events/index.ts | 1 + packages/fabric/domain/src/index.ts | 1 - .../src/models/fields/reference-field.spec.ts | 61 +++++++------------ .../src/models/fields/reference-field.ts | 35 ++++------- packages/fabric/domain/src/models/index.ts | 3 +- .../fabric/domain/src/models/model.spec.ts | 5 +- packages/fabric/domain/src/models/model.ts | 36 ++++++++--- .../{ => models}/query/aggregate-options.ts | 0 .../src/{ => models}/query/filter-options.ts | 6 +- .../domain/src/{ => models}/query/index.ts | 0 .../{ => models}/query/order-by-options.ts | 0 .../src/{ => models}/query/query-builder.ts | 35 +++-------- .../domain/src/{ => models}/query/query.ts | 5 +- .../fabric/domain/src/models/state-store.ts | 5 ++ .../fabric/domain/src/models/types/index.ts | 2 - .../src/models/types/model-field-names.ts | 3 - .../domain/src/models/types/model-to-type.ts | 6 -- packages/fabric/domain/src/storage/index.ts | 2 - .../fabric/domain/src/storage/state-store.ts | 18 ------ .../domain/src/storage/storage-driver.ts | 2 +- 21 files changed, 88 insertions(+), 140 deletions(-) rename packages/fabric/domain/src/{storage => events}/event-store.ts (94%) rename packages/fabric/domain/src/{ => models}/query/aggregate-options.ts (100%) rename packages/fabric/domain/src/{ => models}/query/filter-options.ts (92%) rename packages/fabric/domain/src/{ => models}/query/index.ts (100%) rename packages/fabric/domain/src/{ => models}/query/order-by-options.ts (100%) rename packages/fabric/domain/src/{ => models}/query/query-builder.ts (57%) rename packages/fabric/domain/src/{ => models}/query/query.ts (91%) create mode 100644 packages/fabric/domain/src/models/state-store.ts delete mode 100644 packages/fabric/domain/src/models/types/index.ts delete mode 100644 packages/fabric/domain/src/models/types/model-field-names.ts delete mode 100644 packages/fabric/domain/src/models/types/model-to-type.ts delete mode 100644 packages/fabric/domain/src/storage/state-store.ts diff --git a/packages/fabric/domain/src/storage/event-store.ts b/packages/fabric/domain/src/events/event-store.ts similarity index 94% rename from packages/fabric/domain/src/storage/event-store.ts rename to packages/fabric/domain/src/events/event-store.ts index 6918397..c58102b 100644 --- a/packages/fabric/domain/src/storage/event-store.ts +++ b/packages/fabric/domain/src/events/event-store.ts @@ -1,7 +1,7 @@ import { AsyncResult, MaybePromise, PosixDate } from "@fabric/core"; import { StoreQueryError } from "../errors/query-error.js"; -import { Event, StoredEvent } from "../events/event.js"; import { UUID } from "../types/uuid.js"; +import { Event, StoredEvent } from "./event.js"; export interface EventStore { getStream( diff --git a/packages/fabric/domain/src/events/index.ts b/packages/fabric/domain/src/events/index.ts index 8aad408..a28ecf2 100644 --- a/packages/fabric/domain/src/events/index.ts +++ b/packages/fabric/domain/src/events/index.ts @@ -1 +1,2 @@ +export * from "./event-store.js"; export * from "./event.js"; diff --git a/packages/fabric/domain/src/index.ts b/packages/fabric/domain/src/index.ts index 8d7b7da..552b447 100644 --- a/packages/fabric/domain/src/index.ts +++ b/packages/fabric/domain/src/index.ts @@ -2,7 +2,6 @@ export * from "./errors/index.js"; export * from "./events/index.js"; export * from "./files/index.js"; export * from "./models/index.js"; -export * from "./query/index.js"; export * from "./security/index.js"; export * from "./storage/index.js"; export * from "./types/index.js"; diff --git a/packages/fabric/domain/src/models/fields/reference-field.spec.ts b/packages/fabric/domain/src/models/fields/reference-field.spec.ts index cbd58f7..d5e75b4 100644 --- a/packages/fabric/domain/src/models/fields/reference-field.spec.ts +++ b/packages/fabric/domain/src/models/fields/reference-field.spec.ts @@ -9,22 +9,22 @@ import { describe("Validate Reference Field", () => { const schema = { - user: defineModel({ + User: defineModel("User", { name: Field.string(), password: Field.string(), - phone: Field.string({ isOptional: true }), otherUnique: Field.integer({ isUnique: true }), otherNotUnique: Field.uuid(), + otherUser: Field.reference({ + targetModel: "User", + }), }), }; it("should return an error when the target model is not in the schema", () => { const result = validateReferenceField( schema, - "post", - "authorId", Field.reference({ - model: "foo", + targetModel: "foo", }), ); @@ -33,33 +33,26 @@ describe("Validate Reference Field", () => { } expect(result).toBeInstanceOf(InvalidReferenceField); - expect(result.toString()).toBe( - "InvalidReferenceField: post.authorId. The target model 'foo' is not in the schema.", - ); }); it("should not return an error if the target model is in the schema", () => { const result = validateReferenceField( schema, - "post", - "authorId", Field.reference({ - model: "user", + targetModel: "User", }), ); if (isError(result)) { - throw result.toString(); + throw result.reason; } }); it("should return an error if the target key is not in the target model", () => { const result = validateReferenceField( schema, - "post", - "authorId", Field.reference({ - model: "user", + targetModel: "User", targetKey: "foo", }), ); @@ -69,34 +62,13 @@ describe("Validate Reference Field", () => { } expect(result).toBeInstanceOf(InvalidReferenceField); - expect(result.toString()).toBe( - "InvalidReferenceField: post.authorId. The target key 'foo' is not in the target model 'user'.", - ); - }); - - it("should not return an error if the target key is in the target model", () => { - const result = validateReferenceField( - schema, - "post", - "authorId", - Field.reference({ - model: "user", - targetKey: "otherUnique", - }), - ); - - if (isError(result)) { - throw result.toString(); - } }); it("should return error if the target key is not unique", () => { const result = validateReferenceField( schema, - "post", - "authorId", Field.reference({ - model: "user", + targetModel: "User", targetKey: "otherNotUnique", }), ); @@ -106,8 +78,19 @@ describe("Validate Reference Field", () => { } expect(result).toBeInstanceOf(InvalidReferenceField); - expect(result.toString()).toBe( - "InvalidReferenceField: post.authorId. The target key 'user'.'otherNotUnique' is not unique.", + }); + + it("should not return an error if the target key is in the target model and is unique", () => { + const result = validateReferenceField( + schema, + Field.reference({ + targetModel: "User", + targetKey: "otherUnique", + }), ); + + if (isError(result)) { + throw result.toString(); + } }); }); diff --git a/packages/fabric/domain/src/models/fields/reference-field.ts b/packages/fabric/domain/src/models/fields/reference-field.ts index 3d07b90..9dbcfc9 100644 --- a/packages/fabric/domain/src/models/fields/reference-field.ts +++ b/packages/fabric/domain/src/models/fields/reference-field.ts @@ -3,7 +3,7 @@ import { ModelSchema } from "../model-schema.js"; import { BaseField } from "./base-field.js"; export interface ReferenceFieldOptions extends BaseField { - model: string; + targetModel: string; targetKey?: string; } @@ -22,45 +22,32 @@ export function createReferenceField( export function validateReferenceField( schema: ModelSchema, - modelName: string, - fieldName: string, field: ReferenceField, ): Result { - if (!schema[field.model]) { + if (!schema[field.targetModel]) { return new InvalidReferenceField( - modelName, - fieldName, - `The target model '${field.model}' is not in the schema.`, + `The target model '${field.targetModel}' is not in the schema.`, ); } - if (field.targetKey && !schema[field.model][field.targetKey]) { + if (field.targetKey && !schema[field.targetModel].fields[field.targetKey]) { return new InvalidReferenceField( - modelName, - fieldName, - `The target key '${field.targetKey}' is not in the target model '${field.model}'.`, + `The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`, ); } - if (field.targetKey && !schema[field.model][field.targetKey].isUnique) { + if ( + field.targetKey && + !schema[field.targetModel].fields[field.targetKey].isUnique + ) { return new InvalidReferenceField( - modelName, - fieldName, - `The target key '${field.model}'.'${field.targetKey}' is not unique.`, + `The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`, ); } } export class InvalidReferenceField extends TaggedError<"InvalidReferenceField"> { - constructor( - readonly modelName: string, - readonly fieldName: string, - readonly reason: string, - ) { + constructor(readonly reason: string) { super("InvalidReferenceField"); } - - toString() { - return `InvalidReferenceField: ${this.modelName}.${this.fieldName}. ${this.reason}`; - } } diff --git a/packages/fabric/domain/src/models/index.ts b/packages/fabric/domain/src/models/index.ts index d91f9bc..bb4c9d7 100644 --- a/packages/fabric/domain/src/models/index.ts +++ b/packages/fabric/domain/src/models/index.ts @@ -1,4 +1,5 @@ export * from "./fields/index.js"; export * from "./model-schema.js"; export * from "./model.js"; -export * from "./types/index.js"; +export * from "./query/index.js"; +export * from "./state-store.js"; diff --git a/packages/fabric/domain/src/models/model.spec.ts b/packages/fabric/domain/src/models/model.spec.ts index 77d5add..f3b2dd2 100644 --- a/packages/fabric/domain/src/models/model.spec.ts +++ b/packages/fabric/domain/src/models/model.spec.ts @@ -1,12 +1,11 @@ import { describe, expectTypeOf, it } from "vitest"; import { UUID } from "../types/uuid.js"; import { Field } from "./fields/index.js"; -import { defineModel } from "./model.js"; -import { ModelToType } from "./types/model-to-type.js"; +import { defineModel, ModelToType } from "./model.js"; describe("CreateModel", () => { it("should create a model and it's interface type", () => { - const User = defineModel({ + const User = defineModel("User", { name: Field.string(), password: Field.string(), phone: Field.string({ isOptional: true }), diff --git a/packages/fabric/domain/src/models/model.ts b/packages/fabric/domain/src/models/model.ts index 38f31af..34520bb 100644 --- a/packages/fabric/domain/src/models/model.ts +++ b/packages/fabric/domain/src/models/model.ts @@ -1,3 +1,5 @@ +import { Keyof } from "@fabric/core"; +import { FieldToType } from "./fields/field-to-type.js"; import { Field, FieldDefinition } from "./fields/index.js"; export type CustomModelFields = Record; @@ -10,14 +12,34 @@ export const DefaultModelFields = { hasArbitraryPrecision: true, }), }; -export type Model = - typeof DefaultModelFields & TFields; +export interface Model< + TName extends string = string, + TFields extends CustomModelFields = CustomModelFields, +> { + name: TName; + fields: typeof DefaultModelFields & TFields; +} -export function defineModel( - fields: TFields, -): Model { +export function defineModel< + TName extends string, + TFields extends CustomModelFields, +>(name: TName, fields: TFields): Model { return { - ...fields, - ...DefaultModelFields, + name, + fields: { ...DefaultModelFields, ...fields }, } as const; } + +export type ModelToType = { + [K in Keyof]: FieldToType; +}; + +export type ModelFieldNames = Keyof< + TModel["fields"] +>; + +export type ModelAddressableFields = { + [K in Keyof]: TModel["fields"][K] extends { isUnique: true } + ? K + : never; +}[Keyof]; diff --git a/packages/fabric/domain/src/query/aggregate-options.ts b/packages/fabric/domain/src/models/query/aggregate-options.ts similarity index 100% rename from packages/fabric/domain/src/query/aggregate-options.ts rename to packages/fabric/domain/src/models/query/aggregate-options.ts diff --git a/packages/fabric/domain/src/query/filter-options.ts b/packages/fabric/domain/src/models/query/filter-options.ts similarity index 92% rename from packages/fabric/domain/src/query/filter-options.ts rename to packages/fabric/domain/src/models/query/filter-options.ts index 7a6630e..4a18f7c 100644 --- a/packages/fabric/domain/src/query/filter-options.ts +++ b/packages/fabric/domain/src/models/query/filter-options.ts @@ -14,9 +14,9 @@ export type SingleFilterOption = { export type MultiFilterOption = SingleFilterOption[]; -export const FILTER_OPTION_TYPE_SYMBOL = Symbol("$type"); -export const FILTER_OPTION_VALUE_SYMBOL = Symbol("$value"); -export const FILTER_OPTION_OPERATOR_SYMBOL = Symbol("$operator"); +export const FILTER_OPTION_TYPE_SYMBOL = Symbol("filter_type"); +export const FILTER_OPTION_VALUE_SYMBOL = Symbol("filter_value"); +export const FILTER_OPTION_OPERATOR_SYMBOL = Symbol("filter_operator"); export type LikeFilterOption = T extends string ? { diff --git a/packages/fabric/domain/src/query/index.ts b/packages/fabric/domain/src/models/query/index.ts similarity index 100% rename from packages/fabric/domain/src/query/index.ts rename to packages/fabric/domain/src/models/query/index.ts diff --git a/packages/fabric/domain/src/query/order-by-options.ts b/packages/fabric/domain/src/models/query/order-by-options.ts similarity index 100% rename from packages/fabric/domain/src/query/order-by-options.ts rename to packages/fabric/domain/src/models/query/order-by-options.ts diff --git a/packages/fabric/domain/src/query/query-builder.ts b/packages/fabric/domain/src/models/query/query-builder.ts similarity index 57% rename from packages/fabric/domain/src/query/query-builder.ts rename to packages/fabric/domain/src/models/query/query-builder.ts index 94df580..d3f5a95 100644 --- a/packages/fabric/domain/src/query/query-builder.ts +++ b/packages/fabric/domain/src/models/query/query-builder.ts @@ -1,9 +1,6 @@ import { AsyncResult, Keyof } from "@fabric/core"; -import { StoreQueryError } from "../errors/query-error.js"; -import { ModelToType } from "../models/index.js"; -import { ModelSchema } from "../models/model-schema.js"; -import { StorageDriver } from "../storage/storage-driver.js"; -import { AggregateOptions } from "./aggregate-options.js"; +import { StoreQueryError } from "../../errors/query-error.js"; +import { StorageDriver } from "../../storage/storage-driver.js"; import { FilterOptions } from "./filter-options.js"; import { OrderByOptions } from "./order-by-options.js"; import { @@ -14,44 +11,32 @@ import { StoreSortableQuery, } from "./query.js"; -export class QueryBuilder< - TModels extends ModelSchema, - TEntityName extends Keyof, - T = ModelToType, -> implements StoreQuery -{ +export class QueryBuilder implements StoreQuery { constructor( private driver: StorageDriver, - private query: QueryDefinition, + private query: QueryDefinition, ) {} - aggregate>(): SelectableQuery { - throw new Error("Method not implemented."); - } where(where: FilterOptions): StoreSortableQuery { - this.query = { + return new QueryBuilder(this.driver, { ...this.query, where, - }; - return this; + }); } orderBy(opts: OrderByOptions): StoreLimitableQuery { - this.query = { + return new QueryBuilder(this.driver, { ...this.query, orderBy: opts, - }; - return this; + }); } limit(limit: number, offset?: number | undefined): SelectableQuery { - this.query = { + return new QueryBuilder(this.driver, { ...this.query, limit, offset, - }; - - return this; + }); } select>( diff --git a/packages/fabric/domain/src/query/query.ts b/packages/fabric/domain/src/models/query/query.ts similarity index 91% rename from packages/fabric/domain/src/query/query.ts rename to packages/fabric/domain/src/models/query/query.ts index e55b0dd..febc5d8 100644 --- a/packages/fabric/domain/src/query/query.ts +++ b/packages/fabric/domain/src/models/query/query.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { AsyncResult, Keyof } from "@fabric/core"; -import { StoreQueryError } from "../errors/query-error.js"; -import { AggregateOptions } from "./aggregate-options.js"; +import { StoreQueryError } from "../../errors/query-error.js"; import { FilterOptions } from "./filter-options.js"; import { OrderByOptions } from "./order-by-options.js"; @@ -10,8 +9,6 @@ export interface StoreQuery { orderBy(opts: OrderByOptions): StoreLimitableQuery; limit(limit: number, offset?: number): SelectableQuery; - aggregate>(opts: K): SelectableQuery; - select(): AsyncResult; select>( keys: K[], diff --git a/packages/fabric/domain/src/models/state-store.ts b/packages/fabric/domain/src/models/state-store.ts new file mode 100644 index 0000000..d72f93b --- /dev/null +++ b/packages/fabric/domain/src/models/state-store.ts @@ -0,0 +1,5 @@ +import { StorageDriver } from "../storage/storage-driver.js"; + +export class StateStore { + constructor(private driver: StorageDriver) {} +} diff --git a/packages/fabric/domain/src/models/types/index.ts b/packages/fabric/domain/src/models/types/index.ts deleted file mode 100644 index 5d07363..0000000 --- a/packages/fabric/domain/src/models/types/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./model-field-names.js"; -export * from "./model-to-type.js"; diff --git a/packages/fabric/domain/src/models/types/model-field-names.ts b/packages/fabric/domain/src/models/types/model-field-names.ts deleted file mode 100644 index c9d1b9c..0000000 --- a/packages/fabric/domain/src/models/types/model-field-names.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { CustomModelFields } from "../model.js"; - -export type ModelFieldNames = keyof TModel; diff --git a/packages/fabric/domain/src/models/types/model-to-type.ts b/packages/fabric/domain/src/models/types/model-to-type.ts deleted file mode 100644 index 5f633cf..0000000 --- a/packages/fabric/domain/src/models/types/model-to-type.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { FieldToType } from "../fields/field-to-type.js"; -import { Model } from "../model.js"; - -export type ModelToType = { - [K in keyof TModel]: FieldToType; -}; diff --git a/packages/fabric/domain/src/storage/index.ts b/packages/fabric/domain/src/storage/index.ts index cab4965..1d74826 100644 --- a/packages/fabric/domain/src/storage/index.ts +++ b/packages/fabric/domain/src/storage/index.ts @@ -1,3 +1 @@ -export * from "./event-store.js"; -export * from "./state-store.js"; export * from "./storage-driver.js"; diff --git a/packages/fabric/domain/src/storage/state-store.ts b/packages/fabric/domain/src/storage/state-store.ts deleted file mode 100644 index 9df3acc..0000000 --- a/packages/fabric/domain/src/storage/state-store.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Keyof } from "@fabric/core"; -import { ModelToType } from "../models/index.js"; -import { ModelSchema } from "../models/model-schema.js"; -import { QueryBuilder } from "../query/query-builder.js"; -import { StoreQuery } from "../query/query.js"; -import { StorageDriver } from "./storage-driver.js"; - -export class StateStore { - constructor(private driver: StorageDriver) {} - - from>( - entityName: TEntityName, - ): StoreQuery> { - return new QueryBuilder(this.driver, { - from: entityName, - }) as StoreQuery>; - } -} diff --git a/packages/fabric/domain/src/storage/storage-driver.ts b/packages/fabric/domain/src/storage/storage-driver.ts index 02269da..0ba690b 100644 --- a/packages/fabric/domain/src/storage/storage-driver.ts +++ b/packages/fabric/domain/src/storage/storage-driver.ts @@ -4,7 +4,7 @@ import { AsyncResult, UnexpectedError } from "@fabric/core"; import { CircularDependencyError } from "../errors/circular-dependency-error.js"; import { StoreQueryError } from "../errors/query-error.js"; import { ModelSchema } from "../models/model-schema.js"; -import { QueryDefinition } from "../query/query.js"; +import { QueryDefinition } from "../models/query/query.js"; export interface StorageDriver { /**