From 63290444156368e3214b404d1f7cdef58a10b3f3 Mon Sep 17 00:00:00 2001 From: Pablo Baleztena Date: Wed, 23 Oct 2024 23:52:12 -0300 Subject: [PATCH] [fabric/domain] Simplify model field definitions --- .../models/{validations => }/field-parsers.ts | 80 ++++++++----- packages/fabric/domain/models/fields.ts | 108 ++++++++++++++++++ .../fabric/domain/models/fields/base-field.ts | 5 - .../domain/models/fields/boolean-field.ts | 16 --- .../fabric/domain/models/fields/decimal.ts | 20 ---- .../fabric/domain/models/fields/embedded.ts | 18 --- .../domain/models/fields/field-to-type.ts | 40 ------- packages/fabric/domain/models/fields/float.ts | 16 --- packages/fabric/domain/models/fields/index.ts | 39 ------- .../fabric/domain/models/fields/integer.ts | 19 --- .../domain/models/fields/string-field.ts | 19 --- .../fabric/domain/models/fields/timestamp.ts | 16 --- .../fabric/domain/models/fields/uuid-field.ts | 18 --- packages/fabric/domain/models/index.ts | 5 +- .../{fields => }/reference-field.test.ts | 4 +- .../models/{fields => }/reference-field.ts | 28 +---- .../fabric/domain/models/validations/index.ts | 1 - .../models/validations/parse-from-model.ts | 43 ------- 18 files changed, 165 insertions(+), 330 deletions(-) rename packages/fabric/domain/models/{validations => }/field-parsers.ts (64%) create mode 100644 packages/fabric/domain/models/fields.ts delete mode 100644 packages/fabric/domain/models/fields/base-field.ts delete mode 100644 packages/fabric/domain/models/fields/boolean-field.ts delete mode 100644 packages/fabric/domain/models/fields/decimal.ts delete mode 100644 packages/fabric/domain/models/fields/embedded.ts delete mode 100644 packages/fabric/domain/models/fields/field-to-type.ts delete mode 100644 packages/fabric/domain/models/fields/float.ts delete mode 100644 packages/fabric/domain/models/fields/index.ts delete mode 100644 packages/fabric/domain/models/fields/integer.ts delete mode 100644 packages/fabric/domain/models/fields/string-field.ts delete mode 100644 packages/fabric/domain/models/fields/timestamp.ts delete mode 100644 packages/fabric/domain/models/fields/uuid-field.ts rename packages/fabric/domain/models/{fields => }/reference-field.test.ts (96%) rename packages/fabric/domain/models/{fields => }/reference-field.ts (66%) delete mode 100644 packages/fabric/domain/models/validations/index.ts delete mode 100644 packages/fabric/domain/models/validations/parse-from-model.ts diff --git a/packages/fabric/domain/models/validations/field-parsers.ts b/packages/fabric/domain/models/field-parsers.ts similarity index 64% rename from packages/fabric/domain/models/validations/field-parsers.ts rename to packages/fabric/domain/models/field-parsers.ts index b0847ce..e2dfcf2 100644 --- a/packages/fabric/domain/models/validations/field-parsers.ts +++ b/packages/fabric/domain/models/field-parsers.ts @@ -1,11 +1,12 @@ import { + Decimal, PosixDate, Result, TaggedError, type VariantFromTag, } from "@fabric/core"; import { isUUID, parseAndSanitizeString } from "@fabric/validations"; -import type { FieldDefinition, FieldToType } from "../index.ts"; +import type { FieldDefinition, FieldToType } from "./index.ts"; export type FieldParsers = { [K in FieldDefinition["_tag"]]: FieldParser< @@ -32,46 +33,56 @@ export const fieldParsers: FieldParsers = { : Result.failWith(new InvalidFieldTypeError()) ); }, - TimestampField: (f, v) => { - return parseOptionality(f, v).flatMap((parsedValue) => { - if (parsedValue === undefined) return Result.ok(undefined); - - if (!(v instanceof PosixDate)) { - return Result.failWith(new InvalidFieldTypeError()); + PosixDateField: (f, v) => { + return parseOptionality(f, v, (v) => { + if (v instanceof PosixDate) { + return Result.ok(v); } - - return Result.ok(v); + return Result.failWith(new InvalidFieldTypeError()); }); }, BooleanField: (f, v) => { - if (!f.isOptional && v === undefined) { - return Result.failWith(new MissingRequiredFieldError()); - } - if (v === undefined) { - return Result.ok(undefined); - } + return parseOptionality(f, v, (v) => { + if (typeof v === "boolean") { + return Result.ok(v); + } - if (typeof v === "boolean") { - return Result.ok(v); - } - - return Result.failWith(new InvalidFieldTypeError()); + return Result.failWith(new InvalidFieldTypeError()); + }); }, - IntegerField: function (): Result< - number | bigint | undefined, - FieldParsingError - > { - throw new Error("Function not implemented."); + IntegerField: (f, v) => { + return parseOptionality(f, v, (v) => { + if ( + (typeof v === "number" && + Number.isInteger(v)) || (typeof v === "bigint") + ) { + return Result.ok(v); + } + return Result.failWith(new InvalidFieldTypeError()); + }); }, - FloatField: function () { - throw new Error("Function not implemented."); + FloatField: (f, v) => { + return parseOptionality(f, v, (v) => { + if (typeof v === "number") { + return Result.ok(v); + } + return Result.failWith(new InvalidFieldTypeError()); + }); }, - DecimalField: function () { - throw new Error("Function not implemented."); + DecimalField: (f, v) => { + return parseOptionality(f, v, (v) => { + if (v instanceof Decimal) { + return Result.ok(v); + } + return Result.failWith(new InvalidFieldTypeError()); + }); }, EmbeddedField: function () { throw new Error("Function not implemented."); }, + EmailField: function () { + throw new Error("Function not implemented."); + }, }; /** @@ -125,10 +136,17 @@ function parseStringValue( */ function parseOptionality( field: FieldDefinition, - value: T | undefined, + value: unknown, + withMapping?: (value: unknown) => Result, ): Result { if (!field.isOptional && value === undefined) { return Result.failWith(new MissingRequiredFieldError()); } - return Result.ok(value); + if (value === undefined) { + return Result.ok(value); + } + if (!withMapping) { + return Result.ok(value as T); + } + return withMapping(value); } diff --git a/packages/fabric/domain/models/fields.ts b/packages/fabric/domain/models/fields.ts new file mode 100644 index 0000000..2ea83ec --- /dev/null +++ b/packages/fabric/domain/models/fields.ts @@ -0,0 +1,108 @@ +// deno-lint-ignore-file no-explicit-any +import type { + Decimal, + Email, + PosixDate, + TaggedVariant, + UUID, +} from "@fabric/core"; +import { variantConstructor } from "@fabric/core"; + +export const Field = { + string: variantConstructor("StringField"), + uuid: variantConstructor("UUIDField"), + integer: variantConstructor("IntegerField"), + float: variantConstructor("FloatField"), + decimal: variantConstructor("DecimalField"), + reference: variantConstructor("ReferenceField"), + posixDate: variantConstructor("PosixDateField"), + embedded: variantConstructor("EmbeddedField"), + boolean: variantConstructor("BooleanField"), + email: variantConstructor("EmailField"), +} as const; + +export type FieldDefinition = + | StringField + | UUIDField + | IntegerField + | FloatField + | DecimalField + | ReferenceField + | PosixDateField + | EmbeddedField + | EmailField + | BooleanField; + +/** + * Converts a field definition to its corresponding TypeScript type. + */ +//prettier-ignore +export type FieldToType = TField extends StringField + ? MaybeOptional + : TField extends UUIDField ? MaybeOptional + : TField extends IntegerField ? IntegerFieldToType + : TField extends ReferenceField ? MaybeOptional + : TField extends DecimalField ? MaybeOptional + : TField extends FloatField ? MaybeOptional + : TField extends PosixDateField ? MaybeOptional + : TField extends BooleanField ? MaybeOptional + : TField extends EmailField ? MaybeOptional + : TField extends EmbeddedField + ? MaybeOptional + : never; + +//prettier-ignore +type IntegerFieldToType = + TField["hasArbitraryPrecision"] extends true ? MaybeOptional + : TField["hasArbitraryPrecision"] extends false + ? MaybeOptional + : MaybeOptional; + +type MaybeOptional = TField extends { isOptional: true } + ? TType | undefined + : TType; + +interface BaseField { + isOptional?: boolean; + isUnique?: boolean; + isIndexed?: boolean; +} + +export interface UUIDField extends TaggedVariant<"UUIDField">, BaseField { + isPrimaryKey?: boolean; +} + +export interface PosixDateField + extends TaggedVariant<"PosixDateField">, BaseField {} + +export interface BooleanField + extends TaggedVariant<"BooleanField">, BaseField {} + +export interface StringField extends TaggedVariant<"StringField">, BaseField { + maxLength?: number; + minLength?: number; +} + +export interface EmailField extends TaggedVariant<"EmailField">, BaseField {} + +export interface IntegerField extends TaggedVariant<"IntegerField">, BaseField { + isUnsigned?: boolean; + hasArbitraryPrecision?: boolean; +} + +export interface FloatField extends TaggedVariant<"FloatField">, BaseField {} + +export interface ReferenceField + extends TaggedVariant<"ReferenceField">, BaseField { + targetModel: string; + targetKey?: string; +} + +export interface DecimalField extends TaggedVariant<"DecimalField">, BaseField { + isUnsigned?: boolean; + precision?: number; + scale?: number; +} + +export interface EmbeddedField + extends TaggedVariant<"EmbeddedField">, BaseField {} diff --git a/packages/fabric/domain/models/fields/base-field.ts b/packages/fabric/domain/models/fields/base-field.ts deleted file mode 100644 index 6ef73a4..0000000 --- a/packages/fabric/domain/models/fields/base-field.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface BaseField { - isOptional?: boolean; - isUnique?: boolean; - isIndexed?: boolean; -} diff --git a/packages/fabric/domain/models/fields/boolean-field.ts b/packages/fabric/domain/models/fields/boolean-field.ts deleted file mode 100644 index 68c8d9e..0000000 --- a/packages/fabric/domain/models/fields/boolean-field.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface BooleanFieldOptions extends BaseField {} - -export interface BooleanField - extends TaggedVariant<"BooleanField">, BooleanFieldOptions {} - -export function createBooleanField( - opts: T = {} as T, -): BooleanField & T { - return { - [VariantTag]: "BooleanField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/decimal.ts b/packages/fabric/domain/models/fields/decimal.ts deleted file mode 100644 index 36269f0..0000000 --- a/packages/fabric/domain/models/fields/decimal.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface DecimalFieldOptions extends BaseField { - isUnsigned?: boolean; - precision?: number; - scale?: number; -} - -export interface DecimalField - extends TaggedVariant<"DecimalField">, DecimalFieldOptions {} - -export function createDecimalField( - opts: T = {} as T, -): DecimalField & T { - return { - [VariantTag]: "DecimalField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/embedded.ts b/packages/fabric/domain/models/fields/embedded.ts deleted file mode 100644 index 0bda75a..0000000 --- a/packages/fabric/domain/models/fields/embedded.ts +++ /dev/null @@ -1,18 +0,0 @@ -// deno-lint-ignore-file no-explicit-any -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface EmbeddedFieldOptions extends BaseField {} - -export interface EmbeddedField - extends TaggedVariant<"EmbeddedField">, EmbeddedFieldOptions {} - -export function createEmbeddedField< - K = any, - T extends EmbeddedFieldOptions = EmbeddedFieldOptions, ->(opts: T = {} as T): EmbeddedField & T { - return { - [VariantTag]: "EmbeddedField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/field-to-type.ts b/packages/fabric/domain/models/fields/field-to-type.ts deleted file mode 100644 index 5d50dbd..0000000 --- a/packages/fabric/domain/models/fields/field-to-type.ts +++ /dev/null @@ -1,40 +0,0 @@ -import type { PosixDate } from "@fabric/core"; -import type Decimal from "decimal"; -import type { UUID } from "../../../core/types/uuid.ts"; -import type { BooleanField } from "./boolean-field.ts"; -import type { DecimalField } from "./decimal.ts"; -import type { EmbeddedField } from "./embedded.ts"; -import type { FloatField } from "./float.ts"; -import type { IntegerField } from "./integer.ts"; -import type { ReferenceField } from "./reference-field.ts"; -import type { StringField } from "./string-field.ts"; -import type { TimestampField } from "./timestamp.ts"; -import type { UUIDField } from "./uuid-field.ts"; - -/** - * Converts a field definition to its corresponding TypeScript type. - */ -//prettier-ignore -export type FieldToType = TField extends StringField - ? MaybeOptional - : TField extends UUIDField ? MaybeOptional - : TField extends IntegerField ? IntegerFieldToType - : TField extends ReferenceField ? MaybeOptional - : TField extends DecimalField ? MaybeOptional - : TField extends FloatField ? MaybeOptional - : TField extends TimestampField ? MaybeOptional - : TField extends BooleanField ? MaybeOptional - : TField extends EmbeddedField - ? MaybeOptional - : never; - -//prettier-ignore -type IntegerFieldToType = - TField["hasArbitraryPrecision"] extends true ? MaybeOptional - : TField["hasArbitraryPrecision"] extends false - ? MaybeOptional - : MaybeOptional; - -type MaybeOptional = TField extends { isOptional: true } - ? TType | null - : TType; diff --git a/packages/fabric/domain/models/fields/float.ts b/packages/fabric/domain/models/fields/float.ts deleted file mode 100644 index b107f7c..0000000 --- a/packages/fabric/domain/models/fields/float.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface FloatFieldOptions extends BaseField {} - -export interface FloatField - extends TaggedVariant<"FloatField">, FloatFieldOptions {} - -export function createFloatField( - opts: T = {} as T, -): FloatField & T { - return { - [VariantTag]: "FloatField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/index.ts b/packages/fabric/domain/models/fields/index.ts deleted file mode 100644 index e88d495..0000000 --- a/packages/fabric/domain/models/fields/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { type BooleanField, createBooleanField } from "./boolean-field.ts"; -import { createDecimalField, type DecimalField } from "./decimal.ts"; -import { createEmbeddedField, type EmbeddedField } from "./embedded.ts"; -import { createFloatField, type FloatField } from "./float.ts"; -import { createIntegerField, type IntegerField } from "./integer.ts"; -import { - createReferenceField, - type ReferenceField, -} from "./reference-field.ts"; -import { createStringField, type StringField } from "./string-field.ts"; -import { createTimestampField, type TimestampField } from "./timestamp.ts"; -import { createUUIDField, type UUIDField } from "./uuid-field.ts"; -export * from "./base-field.ts"; -export * from "./field-to-type.ts"; -export * from "./reference-field.ts"; -export * from "./uuid-field.ts"; - -export type FieldDefinition = - | StringField - | UUIDField - | IntegerField - | FloatField - | DecimalField - | ReferenceField - | TimestampField - | EmbeddedField - | BooleanField; - -export namespace Field { - export const string = createStringField; - export const uuid = createUUIDField; - export const integer = createIntegerField; - export const reference = createReferenceField; - export const decimal = createDecimalField; - export const float = createFloatField; - export const timestamp = createTimestampField; - export const embedded = createEmbeddedField; - export const boolean = createBooleanField; -} diff --git a/packages/fabric/domain/models/fields/integer.ts b/packages/fabric/domain/models/fields/integer.ts deleted file mode 100644 index 6b8cc58..0000000 --- a/packages/fabric/domain/models/fields/integer.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface IntegerFieldOptions extends BaseField { - isUnsigned?: boolean; - hasArbitraryPrecision?: boolean; -} - -export interface IntegerField - extends TaggedVariant<"IntegerField">, IntegerFieldOptions {} - -export function createIntegerField( - opts: T = {} as T, -): IntegerField & T { - return { - [VariantTag]: "IntegerField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/string-field.ts b/packages/fabric/domain/models/fields/string-field.ts deleted file mode 100644 index d39654a..0000000 --- a/packages/fabric/domain/models/fields/string-field.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface StringFieldOptions extends BaseField { - maxLength?: number; - minLength?: number; -} - -export interface StringField - extends TaggedVariant<"StringField">, StringFieldOptions {} - -export function createStringField( - opts: T = {} as T, -): StringField & T { - return { - [VariantTag]: "StringField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/timestamp.ts b/packages/fabric/domain/models/fields/timestamp.ts deleted file mode 100644 index 5e4da2d..0000000 --- a/packages/fabric/domain/models/fields/timestamp.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface TimestampFieldOptions extends BaseField {} - -export interface TimestampField - extends TaggedVariant<"TimestampField">, TimestampFieldOptions {} - -export function createTimestampField( - opts: T = {} as T, -): TimestampField & T { - return { - [VariantTag]: "TimestampField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/fields/uuid-field.ts b/packages/fabric/domain/models/fields/uuid-field.ts deleted file mode 100644 index c20b128..0000000 --- a/packages/fabric/domain/models/fields/uuid-field.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type TaggedVariant, VariantTag } from "@fabric/core"; -import type { BaseField } from "./base-field.ts"; - -export interface UUIDFieldOptions extends BaseField { - isPrimaryKey?: boolean; -} - -export interface UUIDField - extends TaggedVariant<"UUIDField">, UUIDFieldOptions {} - -export function createUUIDField( - opts: T = {} as T, -): UUIDField & T { - return { - [VariantTag]: "UUIDField", - ...opts, - } as const; -} diff --git a/packages/fabric/domain/models/index.ts b/packages/fabric/domain/models/index.ts index 012833c..a67caf4 100644 --- a/packages/fabric/domain/models/index.ts +++ b/packages/fabric/domain/models/index.ts @@ -1,6 +1,7 @@ -export * from "./fields/index.ts"; +export * from "./field-parsers.ts"; +export * from "./fields.ts"; export * from "./model-schema.ts"; export * from "./model.ts"; +export * from "./reference-field.ts"; export * from "./state-store.ts"; export * from "./store-query/index.ts"; -export * from "./validations/index.ts"; diff --git a/packages/fabric/domain/models/fields/reference-field.test.ts b/packages/fabric/domain/models/reference-field.test.ts similarity index 96% rename from packages/fabric/domain/models/fields/reference-field.test.ts rename to packages/fabric/domain/models/reference-field.test.ts index 4685553..d7c2651 100644 --- a/packages/fabric/domain/models/fields/reference-field.test.ts +++ b/packages/fabric/domain/models/reference-field.test.ts @@ -1,7 +1,7 @@ import { isError } from "@fabric/core"; import { describe, expect, test } from "@fabric/testing"; -import { Model } from "../model.ts"; -import { Field } from "./index.ts"; +import { Field } from "../index.ts"; +import { Model } from "./model.ts"; import { InvalidReferenceFieldError, validateReferenceField, diff --git a/packages/fabric/domain/models/fields/reference-field.ts b/packages/fabric/domain/models/reference-field.ts similarity index 66% rename from packages/fabric/domain/models/fields/reference-field.ts rename to packages/fabric/domain/models/reference-field.ts index 87c08dd..877bf89 100644 --- a/packages/fabric/domain/models/fields/reference-field.ts +++ b/packages/fabric/domain/models/reference-field.ts @@ -1,28 +1,6 @@ -import { - Result, - TaggedError, - type TaggedVariant, - VariantTag, -} from "@fabric/core"; -import type { ModelSchema } from "../model-schema.ts"; -import type { BaseField } from "./base-field.ts"; - -export interface ReferenceFieldOptions extends BaseField { - targetModel: string; - targetKey?: string; -} - -export interface ReferenceField - extends TaggedVariant<"ReferenceField">, ReferenceFieldOptions {} - -export function createReferenceField( - opts: T = {} as T, -): ReferenceField & T { - return { - [VariantTag]: "ReferenceField", - ...opts, - } as const; -} +import { Result, TaggedError } from "@fabric/core"; +import type { ReferenceField } from "./fields.ts"; +import type { ModelSchema } from "./model-schema.ts"; export function getTargetKey(field: ReferenceField): string { return field.targetKey || "id"; diff --git a/packages/fabric/domain/models/validations/index.ts b/packages/fabric/domain/models/validations/index.ts deleted file mode 100644 index de8552c..0000000 --- a/packages/fabric/domain/models/validations/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./parse-from-model.ts"; diff --git a/packages/fabric/domain/models/validations/parse-from-model.ts b/packages/fabric/domain/models/validations/parse-from-model.ts deleted file mode 100644 index db20514..0000000 --- a/packages/fabric/domain/models/validations/parse-from-model.ts +++ /dev/null @@ -1,43 +0,0 @@ -// deno-lint-ignore-file no-explicit-any - -import { isRecordEmpty, Result, TaggedError } from "@fabric/core"; -import type { FieldDefinition, Model, ModelToType } from "../index.ts"; -import { fieldParsers, type FieldParsingError } from "./field-parsers.ts"; - -export function parseFromModel< - TModel extends Model, ->( - model: TModel, - value: unknown, -): Result, SchemaParsingError> { - const parsingErrors = {} as Record; - const parsedValue = {} as ModelToType; - - for (const key in model) { - const field = model[key] as FieldDefinition; - const fieldResult = fieldParsers[field._tag](field as any, value); - - if (fieldResult.isOk()) { - parsedValue[key as keyof ModelToType] = fieldResult - .value(); - } else { - parsingErrors[key] = fieldResult.unwrapErrorOrThrow(); - } - } - - if (!isRecordEmpty(parsingErrors)) { - return Result.failWith(new SchemaParsingError(parsingErrors, parsedValue)); - } else { - return Result.succeedWith(parsedValue); - } -} - -export class SchemaParsingError - extends TaggedError<"SchemaParsingFailed"> { - constructor( - public readonly errors: Record, - public readonly value?: Partial>, - ) { - super("SchemaParsingFailed"); - } -}