[fabric/domain] Simplify model field definitions
This commit is contained in:
parent
36b5286a09
commit
6329044415
@ -1,11 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
|
Decimal,
|
||||||
PosixDate,
|
PosixDate,
|
||||||
Result,
|
Result,
|
||||||
TaggedError,
|
TaggedError,
|
||||||
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 "./index.ts";
|
||||||
|
|
||||||
export type FieldParsers = {
|
export type FieldParsers = {
|
||||||
[K in FieldDefinition["_tag"]]: FieldParser<
|
[K in FieldDefinition["_tag"]]: FieldParser<
|
||||||
@ -32,46 +33,56 @@ export const fieldParsers: FieldParsers = {
|
|||||||
: Result.failWith(new InvalidFieldTypeError())
|
: Result.failWith(new InvalidFieldTypeError())
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
TimestampField: (f, v) => {
|
PosixDateField: (f, v) => {
|
||||||
return parseOptionality(f, v).flatMap((parsedValue) => {
|
return parseOptionality(f, v, (v) => {
|
||||||
if (parsedValue === undefined) return Result.ok(undefined);
|
if (v instanceof PosixDate) {
|
||||||
|
|
||||||
if (!(v instanceof PosixDate)) {
|
|
||||||
return Result.failWith(new InvalidFieldTypeError());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Result.ok(v);
|
return Result.ok(v);
|
||||||
|
}
|
||||||
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
BooleanField: (f, v) => {
|
BooleanField: (f, v) => {
|
||||||
if (!f.isOptional && v === undefined) {
|
return parseOptionality(f, v, (v) => {
|
||||||
return Result.failWith(new MissingRequiredFieldError());
|
|
||||||
}
|
|
||||||
if (v === undefined) {
|
|
||||||
return Result.ok(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof v === "boolean") {
|
if (typeof v === "boolean") {
|
||||||
return Result.ok(v);
|
return Result.ok(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.failWith(new InvalidFieldTypeError());
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
|
});
|
||||||
},
|
},
|
||||||
IntegerField: function (): Result<
|
IntegerField: (f, v) => {
|
||||||
number | bigint | undefined,
|
return parseOptionality(f, v, (v) => {
|
||||||
FieldParsingError
|
if (
|
||||||
> {
|
(typeof v === "number" &&
|
||||||
throw new Error("Function not implemented.");
|
Number.isInteger(v)) || (typeof v === "bigint")
|
||||||
|
) {
|
||||||
|
return Result.ok(v);
|
||||||
|
}
|
||||||
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
|
});
|
||||||
},
|
},
|
||||||
FloatField: function () {
|
FloatField: (f, v) => {
|
||||||
throw new Error("Function not implemented.");
|
return parseOptionality(f, v, (v) => {
|
||||||
|
if (typeof v === "number") {
|
||||||
|
return Result.ok(v);
|
||||||
|
}
|
||||||
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
|
});
|
||||||
},
|
},
|
||||||
DecimalField: function () {
|
DecimalField: (f, v) => {
|
||||||
throw new Error("Function not implemented.");
|
return parseOptionality(f, v, (v) => {
|
||||||
|
if (v instanceof Decimal) {
|
||||||
|
return Result.ok(v);
|
||||||
|
}
|
||||||
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
|
});
|
||||||
},
|
},
|
||||||
EmbeddedField: function () {
|
EmbeddedField: function () {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
|
EmailField: function () {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,10 +136,17 @@ function parseStringValue(
|
|||||||
*/
|
*/
|
||||||
function parseOptionality<T>(
|
function parseOptionality<T>(
|
||||||
field: FieldDefinition,
|
field: FieldDefinition,
|
||||||
value: T | undefined,
|
value: unknown,
|
||||||
|
withMapping?: (value: unknown) => Result<T, FieldParsingError>,
|
||||||
): Result<T | undefined, FieldParsingError> {
|
): Result<T | undefined, FieldParsingError> {
|
||||||
if (!field.isOptional && value === undefined) {
|
if (!field.isOptional && value === undefined) {
|
||||||
return Result.failWith(new MissingRequiredFieldError());
|
return Result.failWith(new MissingRequiredFieldError());
|
||||||
}
|
}
|
||||||
|
if (value === undefined) {
|
||||||
return Result.ok(value);
|
return Result.ok(value);
|
||||||
}
|
}
|
||||||
|
if (!withMapping) {
|
||||||
|
return Result.ok(value as T);
|
||||||
|
}
|
||||||
|
return withMapping(value);
|
||||||
|
}
|
||||||
108
packages/fabric/domain/models/fields.ts
Normal file
108
packages/fabric/domain/models/fields.ts
Normal file
@ -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>("StringField"),
|
||||||
|
uuid: variantConstructor<UUIDField>("UUIDField"),
|
||||||
|
integer: variantConstructor<IntegerField>("IntegerField"),
|
||||||
|
float: variantConstructor<FloatField>("FloatField"),
|
||||||
|
decimal: variantConstructor<DecimalField>("DecimalField"),
|
||||||
|
reference: variantConstructor<ReferenceField>("ReferenceField"),
|
||||||
|
posixDate: variantConstructor<PosixDateField>("PosixDateField"),
|
||||||
|
embedded: variantConstructor<EmbeddedField>("EmbeddedField"),
|
||||||
|
boolean: variantConstructor<BooleanField>("BooleanField"),
|
||||||
|
email: variantConstructor<EmailField>("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> = TField extends StringField
|
||||||
|
? MaybeOptional<TField, string>
|
||||||
|
: TField extends UUIDField ? MaybeOptional<TField, UUID>
|
||||||
|
: TField extends IntegerField ? IntegerFieldToType<TField>
|
||||||
|
: TField extends ReferenceField ? MaybeOptional<TField, UUID>
|
||||||
|
: TField extends DecimalField ? MaybeOptional<TField, Decimal>
|
||||||
|
: TField extends FloatField ? MaybeOptional<TField, number>
|
||||||
|
: TField extends PosixDateField ? MaybeOptional<TField, PosixDate>
|
||||||
|
: TField extends BooleanField ? MaybeOptional<TField, boolean>
|
||||||
|
: TField extends EmailField ? MaybeOptional<TField, Email>
|
||||||
|
: TField extends EmbeddedField<infer TSubModel>
|
||||||
|
? MaybeOptional<TField, TSubModel>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
//prettier-ignore
|
||||||
|
type IntegerFieldToType<TField extends IntegerField> =
|
||||||
|
TField["hasArbitraryPrecision"] extends true ? MaybeOptional<TField, bigint>
|
||||||
|
: TField["hasArbitraryPrecision"] extends false
|
||||||
|
? MaybeOptional<TField, number>
|
||||||
|
: MaybeOptional<TField, number | bigint>;
|
||||||
|
|
||||||
|
type MaybeOptional<TField, TType> = 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<T = any>
|
||||||
|
extends TaggedVariant<"EmbeddedField">, BaseField {}
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export interface BaseField {
|
|
||||||
isOptional?: boolean;
|
|
||||||
isUnique?: boolean;
|
|
||||||
isIndexed?: boolean;
|
|
||||||
}
|
|
||||||
@ -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<T extends BooleanFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): BooleanField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "BooleanField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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<T extends DecimalFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): DecimalField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "DecimalField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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<T = any> extends BaseField {}
|
|
||||||
|
|
||||||
export interface EmbeddedField<T = any>
|
|
||||||
extends TaggedVariant<"EmbeddedField">, EmbeddedFieldOptions<T> {}
|
|
||||||
|
|
||||||
export function createEmbeddedField<
|
|
||||||
K = any,
|
|
||||||
T extends EmbeddedFieldOptions<K> = EmbeddedFieldOptions<K>,
|
|
||||||
>(opts: T = {} as T): EmbeddedField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "EmbeddedField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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> = TField extends StringField
|
|
||||||
? MaybeOptional<TField, string>
|
|
||||||
: TField extends UUIDField ? MaybeOptional<TField, UUID>
|
|
||||||
: TField extends IntegerField ? IntegerFieldToType<TField>
|
|
||||||
: TField extends ReferenceField ? MaybeOptional<TField, UUID>
|
|
||||||
: TField extends DecimalField ? MaybeOptional<TField, Decimal>
|
|
||||||
: TField extends FloatField ? MaybeOptional<TField, number>
|
|
||||||
: TField extends TimestampField ? MaybeOptional<TField, PosixDate>
|
|
||||||
: TField extends BooleanField ? MaybeOptional<TField, boolean>
|
|
||||||
: TField extends EmbeddedField<infer TSubModel>
|
|
||||||
? MaybeOptional<TField, TSubModel>
|
|
||||||
: never;
|
|
||||||
|
|
||||||
//prettier-ignore
|
|
||||||
type IntegerFieldToType<TField extends IntegerField> =
|
|
||||||
TField["hasArbitraryPrecision"] extends true ? MaybeOptional<TField, bigint>
|
|
||||||
: TField["hasArbitraryPrecision"] extends false
|
|
||||||
? MaybeOptional<TField, number>
|
|
||||||
: MaybeOptional<TField, number | bigint>;
|
|
||||||
|
|
||||||
type MaybeOptional<TField, TType> = TField extends { isOptional: true }
|
|
||||||
? TType | null
|
|
||||||
: TType;
|
|
||||||
@ -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<T extends FloatFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): FloatField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "FloatField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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<T extends IntegerFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): IntegerField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "IntegerField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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<T extends StringFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): StringField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "StringField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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<T extends TimestampFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): TimestampField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "TimestampField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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<T extends UUIDFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): UUIDField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "UUIDField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
@ -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-schema.ts";
|
||||||
export * from "./model.ts";
|
export * from "./model.ts";
|
||||||
|
export * from "./reference-field.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";
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
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 { Field } from "../index.ts";
|
||||||
import { Field } from "./index.ts";
|
import { Model } from "./model.ts";
|
||||||
import {
|
import {
|
||||||
InvalidReferenceFieldError,
|
InvalidReferenceFieldError,
|
||||||
validateReferenceField,
|
validateReferenceField,
|
||||||
@ -1,28 +1,6 @@
|
|||||||
import {
|
import { Result, TaggedError } from "@fabric/core";
|
||||||
Result,
|
import type { ReferenceField } from "./fields.ts";
|
||||||
TaggedError,
|
import type { ModelSchema } from "./model-schema.ts";
|
||||||
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<T extends ReferenceFieldOptions>(
|
|
||||||
opts: T = {} as T,
|
|
||||||
): ReferenceField & T {
|
|
||||||
return {
|
|
||||||
[VariantTag]: "ReferenceField",
|
|
||||||
...opts,
|
|
||||||
} as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getTargetKey(field: ReferenceField): string {
|
export function getTargetKey(field: ReferenceField): string {
|
||||||
return field.targetKey || "id";
|
return field.targetKey || "id";
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from "./parse-from-model.ts";
|
|
||||||
@ -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<ModelToType<TModel>, SchemaParsingError<TModel>> {
|
|
||||||
const parsingErrors = {} as Record<keyof TModel, FieldParsingError>;
|
|
||||||
const parsedValue = {} as ModelToType<TModel>;
|
|
||||||
|
|
||||||
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<TModel>] = 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<TModel extends Model>
|
|
||||||
extends TaggedError<"SchemaParsingFailed"> {
|
|
||||||
constructor(
|
|
||||||
public readonly errors: Record<keyof TModel, FieldParsingError>,
|
|
||||||
public readonly value?: Partial<ModelToType<TModel>>,
|
|
||||||
) {
|
|
||||||
super("SchemaParsingFailed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user