Compare commits
No commits in common. "b77ba6dc838579adbb57de7c2df2e3d24ab4ef4b" and "ab41ff028d246c45aa2dd8114a185f8979edb0b9" have entirely different histories.
b77ba6dc83
...
ab41ff028d
@ -1,10 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@fabric/core",
|
"name": "@fabric/core",
|
||||||
"version": "0.1.0",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts"
|
".": "./index.ts"
|
||||||
},
|
|
||||||
"imports": {
|
|
||||||
"decimal": "jsr:@quentinadam/decimal@^0.1.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import Decimal from "decimal";
|
|
||||||
export * from "./array/index.ts";
|
export * from "./array/index.ts";
|
||||||
export * from "./error/index.ts";
|
export * from "./error/index.ts";
|
||||||
export * from "./record/index.ts";
|
export * from "./record/index.ts";
|
||||||
@ -8,4 +7,3 @@ export * from "./time/index.ts";
|
|||||||
export * from "./types/index.ts";
|
export * from "./types/index.ts";
|
||||||
export * from "./utils/index.ts";
|
export * from "./utils/index.ts";
|
||||||
export * from "./variant/index.ts";
|
export * from "./variant/index.ts";
|
||||||
export { Decimal };
|
|
||||||
|
|||||||
@ -35,9 +35,7 @@ export class AsyncResult<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ok(): AsyncResult<void, never>;
|
static ok<T>(value: T): AsyncResult<T, never> {
|
||||||
static ok<T>(value: T): AsyncResult<T, never>;
|
|
||||||
static ok(value?: any) {
|
|
||||||
return new AsyncResult(Promise.resolve(Result.ok(value)));
|
return new AsyncResult(Promise.resolve(Result.ok(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,6 @@ export type Nothing = null;
|
|||||||
export const Nothing = null;
|
export const Nothing = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Optional` type is a type that represents a value that may or may not be present.
|
* Un Optional es un tipo que puede ser un valor o no ser nada.
|
||||||
*/
|
*/
|
||||||
export type Optional<T> = T | Nothing;
|
export type Optional<T> = T | Nothing;
|
||||||
|
|||||||
@ -1,2 +1 @@
|
|||||||
export * from "./ensure.ts";
|
export * from "./ensure.ts";
|
||||||
export * from "./json-utils.ts";
|
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import { isRecord, PosixDate } from "@fabric/core";
|
|
||||||
import Decimal from "decimal";
|
|
||||||
|
|
||||||
export namespace JSONUtils {
|
|
||||||
export function reviver(key: string, value: unknown) {
|
|
||||||
if (isRecord(value)) {
|
|
||||||
if (value._type === "bigint" && typeof value.value == "string") {
|
|
||||||
return BigInt(value.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value._type === "decimal" && typeof value.value == "string") {
|
|
||||||
return Decimal.from(value.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (PosixDate.isPosixDateJSON(value)) {
|
|
||||||
return PosixDate.fromJson(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse<T>(json: string): T {
|
|
||||||
return JSON.parse(json, reviver);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringify<T>(value: T): string {
|
|
||||||
return JSON.stringify(value, (key, value) => {
|
|
||||||
if (typeof value === "bigint") {
|
|
||||||
return {
|
|
||||||
_type: "bigint",
|
|
||||||
value: value.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (value instanceof Decimal) {
|
|
||||||
return {
|
|
||||||
_type: "decimal",
|
|
||||||
value: value.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,7 +5,7 @@ export function variantConstructor<
|
|||||||
>(
|
>(
|
||||||
tag: T[VariantTag],
|
tag: T[VariantTag],
|
||||||
) {
|
) {
|
||||||
return <TOpts extends Omit<T, VariantTag>>(options: TOpts = {} as TOpts) => {
|
return <TOpts extends Omit<T, VariantTag>>(options: TOpts) => {
|
||||||
return {
|
return {
|
||||||
_tag: tag,
|
_tag: tag,
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"@fabric/core": "jsr:@fabric/core",
|
"@fabric/core": "jsr:@fabric/core",
|
||||||
"@fabric/validations": "jsr:@fabric/validations"
|
"@fabric/validations": "jsr:@fabric/validations",
|
||||||
|
"decimal": "jsr:@quentinadam/decimal@^0.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,108 +0,0 @@
|
|||||||
// 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 {}
|
|
||||||
5
packages/fabric/domain/models/fields/base-field.ts
Normal file
5
packages/fabric/domain/models/fields/base-field.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface BaseField {
|
||||||
|
isOptional?: boolean;
|
||||||
|
isUnique?: boolean;
|
||||||
|
isIndexed?: boolean;
|
||||||
|
}
|
||||||
16
packages/fabric/domain/models/fields/boolean-field.ts
Normal file
16
packages/fabric/domain/models/fields/boolean-field.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
20
packages/fabric/domain/models/fields/decimal.ts
Normal file
20
packages/fabric/domain/models/fields/decimal.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
18
packages/fabric/domain/models/fields/embedded.ts
Normal file
18
packages/fabric/domain/models/fields/embedded.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// 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;
|
||||||
|
}
|
||||||
40
packages/fabric/domain/models/fields/field-to-type.ts
Normal file
40
packages/fabric/domain/models/fields/field-to-type.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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;
|
||||||
16
packages/fabric/domain/models/fields/float.ts
Normal file
16
packages/fabric/domain/models/fields/float.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
39
packages/fabric/domain/models/fields/index.ts
Normal file
39
packages/fabric/domain/models/fields/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
19
packages/fabric/domain/models/fields/integer.ts
Normal file
19
packages/fabric/domain/models/fields/integer.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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,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 { Field } from "../index.ts";
|
import { Model } from "../model.ts";
|
||||||
import { Model } from "./model.ts";
|
import { Field } from "./index.ts";
|
||||||
import {
|
import {
|
||||||
InvalidReferenceFieldError,
|
InvalidReferenceFieldError,
|
||||||
validateReferenceField,
|
validateReferenceField,
|
||||||
@ -1,6 +1,28 @@
|
|||||||
import { Result, TaggedError } from "@fabric/core";
|
import {
|
||||||
import type { ReferenceField } from "./fields.ts";
|
Result,
|
||||||
import type { ModelSchema } from "./model-schema.ts";
|
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<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";
|
||||||
19
packages/fabric/domain/models/fields/string-field.ts
Normal file
19
packages/fabric/domain/models/fields/string-field.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
16
packages/fabric/domain/models/fields/timestamp.ts
Normal file
16
packages/fabric/domain/models/fields/timestamp.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
18
packages/fabric/domain/models/fields/uuid-field.ts
Normal file
18
packages/fabric/domain/models/fields/uuid-field.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
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,7 +1,6 @@
|
|||||||
export * from "./field-parsers.ts";
|
export * from "./fields/index.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,6 +1,8 @@
|
|||||||
import { describe, expect, test } from "@fabric/testing";
|
import type { UUID } from "@fabric/core";
|
||||||
import { Field } from "./fields.ts";
|
import { describe, expectTypeOf, test } from "@fabric/testing";
|
||||||
import { Model, type ModelToType, SchemaParsingError } from "./model.ts";
|
import type { PosixDate } from "../../core/index.ts";
|
||||||
|
import { Field } from "./fields/index.ts";
|
||||||
|
import { Model, 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", () => {
|
||||||
@ -11,79 +13,14 @@ describe("CreateModel", () => {
|
|||||||
});
|
});
|
||||||
type User = ModelToType<typeof User>;
|
type User = ModelToType<typeof User>;
|
||||||
|
|
||||||
// expectTypeOf<User>().toEqualTypeOf<{
|
expectTypeOf<User>().toEqualTypeOf<{
|
||||||
// id: UUID;
|
id: UUID;
|
||||||
// streamId: UUID;
|
streamId: UUID;
|
||||||
// streamVersion: bigint;
|
streamVersion: bigint;
|
||||||
// name: string;
|
name: string;
|
||||||
// password: string;
|
password: string;
|
||||||
// phone?: string | undefined;
|
phone: string | null;
|
||||||
// deletedAt?: PosixDate | undefined;
|
deletedAt: PosixDate | null;
|
||||||
// }>();
|
}>();
|
||||||
|
|
||||||
// deno-lint-ignore no-unused-vars
|
|
||||||
const p: User = {
|
|
||||||
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
streamId: "123e4567-e89b-12d3-a456-426614174001",
|
|
||||||
streamVersion: 1n,
|
|
||||||
name: "John Doe",
|
|
||||||
password: "password123",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should parse valid data correctly", () => {
|
|
||||||
const User = Model.aggregateFrom("User", {
|
|
||||||
name: Field.string(),
|
|
||||||
password: Field.string(),
|
|
||||||
phone: Field.string({ isOptional: true }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = User.parse({
|
|
||||||
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
streamId: "123e4567-e89b-12d3-a456-426614174001",
|
|
||||||
streamVersion: 1n,
|
|
||||||
name: "John Doe",
|
|
||||||
password: "password123",
|
|
||||||
phone: "123-456-7890",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.unwrapOrThrow()).toEqual({
|
|
||||||
id: "123e4567-e89b-12d3-a456-426614174000",
|
|
||||||
streamId: "123e4567-e89b-12d3-a456-426614174001",
|
|
||||||
streamVersion: 1n,
|
|
||||||
name: "John Doe",
|
|
||||||
password: "password123",
|
|
||||||
phone: "123-456-7890",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("should fail to parse invalid data", () => {
|
|
||||||
const User = Model.aggregateFrom("User", {
|
|
||||||
name: Field.string(),
|
|
||||||
password: Field.string(),
|
|
||||||
phone: Field.string({ isOptional: true }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = User.parse({
|
|
||||||
id: "invalid-uuid",
|
|
||||||
streamId: "invalid-uuid",
|
|
||||||
streamVersion: "not-a-bigint",
|
|
||||||
name: 123,
|
|
||||||
password: true,
|
|
||||||
phone: 456,
|
|
||||||
deletedAt: "not-a-date",
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.isError()).toBe(true);
|
|
||||||
if (result.isError()) {
|
|
||||||
const error = result.unwrapErrorOrThrow();
|
|
||||||
expect(error).toBeInstanceOf(SchemaParsingError);
|
|
||||||
expect(error.errors).toHaveProperty("id");
|
|
||||||
expect(error.errors).toHaveProperty("streamId");
|
|
||||||
expect(error.errors).toHaveProperty("streamVersion");
|
|
||||||
expect(error.errors).toHaveProperty("name");
|
|
||||||
expect(error.errors).toHaveProperty("password");
|
|
||||||
expect(error.errors).toHaveProperty("deletedAt");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// deno-lint-ignore-file no-explicit-any
|
import type { Keyof } from "@fabric/core";
|
||||||
import { isRecordEmpty, type Keyof, Result, TaggedError } from "@fabric/core";
|
import type { FieldToType } from "./fields/field-to-type.ts";
|
||||||
import { fieldParsers, type FieldParsingError } from "./field-parsers.ts";
|
import { Field, type FieldDefinition } from "./fields/index.ts";
|
||||||
import { Field, type FieldDefinition, type FieldToType } from "./fields.ts";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model is a schema definition for some type of structured data.
|
* A model is a schema definition for some type of structured data.
|
||||||
@ -31,35 +30,6 @@ export class Model<
|
|||||||
return new Model(name, { ...fields, ...DefaultEntityFields });
|
return new Model(name, { ...fields, ...DefaultEntityFields });
|
||||||
}
|
}
|
||||||
private constructor(readonly name: TName, readonly fields: TFields) {}
|
private constructor(readonly name: TName, readonly fields: TFields) {}
|
||||||
|
|
||||||
public parse(
|
|
||||||
value: unknown,
|
|
||||||
): Result<ModelToType<this>, SchemaParsingError<this>> {
|
|
||||||
const parsingErrors = {} as Record<keyof this["fields"], FieldParsingError>;
|
|
||||||
const parsedValue = {} as ModelToType<this>;
|
|
||||||
|
|
||||||
for (const key in this.fields) {
|
|
||||||
const field = this.fields[key]!;
|
|
||||||
const fieldResult = fieldParsers[field._tag](
|
|
||||||
field as any,
|
|
||||||
(value as any)[key],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (fieldResult.isOk()) {
|
|
||||||
parsedValue[key as keyof ModelToType<this>] = fieldResult.value;
|
|
||||||
} else {
|
|
||||||
parsingErrors[key] = fieldResult.unwrapErrorOrThrow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isRecordEmpty(parsingErrors)) {
|
|
||||||
return Result.failWith(
|
|
||||||
new SchemaParsingError(parsingErrors, parsedValue),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return Result.succeedWith(parsedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EntityModel = Model<
|
export type EntityModel = Model<
|
||||||
@ -72,9 +42,9 @@ export type AggregateModel = Model<
|
|||||||
typeof DefaultAggregateFields & ModelFields
|
typeof DefaultAggregateFields & ModelFields
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type ModelToType<TModel extends Model> =
|
export type ModelToType<TModel extends Model> = {
|
||||||
& ModelToOptionalFields<TModel>
|
[K in Keyof<TModel["fields"]>]: FieldToType<TModel["fields"][K]>;
|
||||||
& ModelToRequiredFields<TModel>;
|
};
|
||||||
|
|
||||||
export type ModelFieldNames<TModel extends ModelFields> = Keyof<
|
export type ModelFieldNames<TModel extends ModelFields> = Keyof<
|
||||||
TModel["fields"]
|
TModel["fields"]
|
||||||
@ -86,18 +56,6 @@ export type ModelAddressableFields<TModel extends Model> = {
|
|||||||
: never;
|
: never;
|
||||||
}[Keyof<TModel["fields"]>];
|
}[Keyof<TModel["fields"]>];
|
||||||
|
|
||||||
export class SchemaParsingError<TModel extends Model>
|
|
||||||
extends TaggedError<"SchemaParsingFailed"> {
|
|
||||||
constructor(
|
|
||||||
public readonly errors: Record<keyof TModel["fields"], FieldParsingError>,
|
|
||||||
public readonly value?: Partial<ModelToType<TModel>>,
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
"SchemaParsingFailed",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ModelFields = Record<string, FieldDefinition>;
|
type ModelFields = Record<string, FieldDefinition>;
|
||||||
|
|
||||||
const DefaultEntityFields = {
|
const DefaultEntityFields = {
|
||||||
@ -111,31 +69,5 @@ const DefaultAggregateFields = {
|
|||||||
isUnsigned: true,
|
isUnsigned: true,
|
||||||
hasArbitraryPrecision: true,
|
hasArbitraryPrecision: true,
|
||||||
}),
|
}),
|
||||||
deletedAt: Field.posixDate({ isOptional: true }),
|
deletedAt: Field.timestamp({ isOptional: true }),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type ModelToOptionalFields<TModel extends Model> = {
|
|
||||||
[K in OptionalFields<TModel>]?: FieldToType<
|
|
||||||
TModel["fields"][K]
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ModelToRequiredFields<TModel extends Model> = {
|
|
||||||
[K in RequiredFields<TModel>]: FieldToType<
|
|
||||||
TModel["fields"][K]
|
|
||||||
>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OptionalFields<TModel extends Model> = {
|
|
||||||
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends {
|
|
||||||
isOptional: true;
|
|
||||||
} ? K
|
|
||||||
: never;
|
|
||||||
}[Keyof<TModel["fields"]>];
|
|
||||||
|
|
||||||
type RequiredFields<TModel extends Model> = {
|
|
||||||
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends {
|
|
||||||
isOptional: true;
|
|
||||||
} ? never
|
|
||||||
: K;
|
|
||||||
}[Keyof<TModel["fields"]>];
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
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<
|
||||||
@ -33,54 +32,44 @@ export const fieldParsers: FieldParsers = {
|
|||||||
: Result.failWith(new InvalidFieldTypeError())
|
: Result.failWith(new InvalidFieldTypeError())
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
PosixDateField: (f, v) => {
|
TimestampField: (f, v) => {
|
||||||
return parseOptionality(f, v, (v) => {
|
return parseOptionality(f, v).flatMap((parsedValue) => {
|
||||||
if (v instanceof PosixDate) {
|
if (parsedValue === undefined) return Result.ok(undefined);
|
||||||
return Result.ok(v);
|
|
||||||
}
|
if (!(v instanceof PosixDate)) {
|
||||||
return Result.failWith(new InvalidFieldTypeError());
|
return Result.failWith(new InvalidFieldTypeError());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.ok(v);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
BooleanField: (f, v) => {
|
BooleanField: (f, v) => {
|
||||||
return parseOptionality(f, v, (v) => {
|
if (!f.isOptional && v === undefined) {
|
||||||
|
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: (f, v) => {
|
IntegerField: function (): Result<
|
||||||
return parseOptionality(f, v, (v) => {
|
number | bigint | undefined,
|
||||||
if (
|
FieldParsingError
|
||||||
(typeof v === "number" &&
|
> {
|
||||||
Number.isInteger(v)) || (typeof v === "bigint")
|
|
||||||
) {
|
|
||||||
return Result.ok(v);
|
|
||||||
}
|
|
||||||
return Result.failWith(new InvalidFieldTypeError());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
FloatField: (f, v) => {
|
|
||||||
return parseOptionality(f, v, (v) => {
|
|
||||||
if (typeof v === "number") {
|
|
||||||
return Result.ok(v);
|
|
||||||
}
|
|
||||||
return Result.failWith(new InvalidFieldTypeError());
|
|
||||||
});
|
|
||||||
},
|
|
||||||
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.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
EmailField: function () {
|
FloatField: function () {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
},
|
||||||
|
DecimalField: function () {
|
||||||
|
throw new Error("Function not implemented.");
|
||||||
|
},
|
||||||
|
EmbeddedField: function () {
|
||||||
throw new Error("Function not implemented.");
|
throw new Error("Function not implemented.");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -136,17 +125,10 @@ function parseStringValue(
|
|||||||
*/
|
*/
|
||||||
function parseOptionality<T>(
|
function parseOptionality<T>(
|
||||||
field: FieldDefinition,
|
field: FieldDefinition,
|
||||||
value: unknown,
|
value: T | undefined,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
1
packages/fabric/domain/models/validations/index.ts
Normal file
1
packages/fabric/domain/models/validations/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./parse-from-model.ts";
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +1,2 @@
|
|||||||
|
export * from "./json-utils.ts";
|
||||||
export * from "./sort-by-dependencies.ts";
|
export * from "./sort-by-dependencies.ts";
|
||||||
|
|||||||
14
packages/fabric/domain/utils/json-utils.ts
Normal file
14
packages/fabric/domain/utils/json-utils.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { PosixDate } from "@fabric/core";
|
||||||
|
|
||||||
|
export namespace JSONUtils {
|
||||||
|
export function reviver(key: string, value: unknown) {
|
||||||
|
if (PosixDate.isPosixDateJSON(value)) {
|
||||||
|
return PosixDate.fromJson(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parse<T>(json: string): T {
|
||||||
|
return JSON.parse(json, reviver);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
AsyncResult,
|
AsyncResult,
|
||||||
JSONUtils,
|
|
||||||
MaybePromise,
|
MaybePromise,
|
||||||
PosixDate,
|
PosixDate,
|
||||||
Run,
|
Run,
|
||||||
@ -12,6 +11,7 @@ import {
|
|||||||
EventFromKey,
|
EventFromKey,
|
||||||
EventStore,
|
EventStore,
|
||||||
EventSubscriber,
|
EventSubscriber,
|
||||||
|
JSONUtils,
|
||||||
StoredEvent,
|
StoredEvent,
|
||||||
StoreQueryError,
|
StoreQueryError,
|
||||||
} from "@fabric/domain";
|
} from "@fabric/domain";
|
||||||
|
|||||||
@ -8,7 +8,7 @@ describe("ModelToSQL", () => {
|
|||||||
name: Field.string(),
|
name: Field.string(),
|
||||||
age: Field.integer(),
|
age: Field.integer(),
|
||||||
// isTrue: Field.boolean(),
|
// isTrue: Field.boolean(),
|
||||||
date: Field.posixDate(),
|
date: Field.timestamp(),
|
||||||
reference: Field.reference({ targetModel: "somethingElse" }),
|
reference: Field.reference({ targetModel: "somethingElse" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -40,7 +40,7 @@ const FieldSQLDefinitionMap: FieldSQLDefinitionMap = {
|
|||||||
DecimalField: (n, f): string => {
|
DecimalField: (n, f): string => {
|
||||||
return [n, "REAL", modifiersFromOpts(f)].join(" ");
|
return [n, "REAL", modifiersFromOpts(f)].join(" ");
|
||||||
},
|
},
|
||||||
PosixDateField: (n, f): string => {
|
TimestampField: (n, f): string => {
|
||||||
return [n, "NUMERIC", modifiersFromOpts(f)].join(" ");
|
return [n, "NUMERIC", modifiersFromOpts(f)].join(" ");
|
||||||
},
|
},
|
||||||
EmbeddedField: (n, f): string => {
|
EmbeddedField: (n, f): string => {
|
||||||
@ -49,9 +49,6 @@ const FieldSQLDefinitionMap: FieldSQLDefinitionMap = {
|
|||||||
BooleanField: (n, f): string => {
|
BooleanField: (n, f): string => {
|
||||||
return [n, "BOOLEAN", modifiersFromOpts(f)].join(" ");
|
return [n, "BOOLEAN", modifiersFromOpts(f)].join(" ");
|
||||||
},
|
},
|
||||||
EmailField: (n, f): string => {
|
|
||||||
return [n, "TEXT", modifiersFromOpts(f)].join(" ");
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
function fieldDefinitionToSQL(name: string, field: FieldDefinition) {
|
function fieldDefinitionToSQL(name: string, field: FieldDefinition) {
|
||||||
return FieldSQLDefinitionMap[field[VariantTag]](name, field as any);
|
return FieldSQLDefinitionMap[field[VariantTag]](name, field as any);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
// deno-lint-ignore-file no-explicit-any
|
// deno-lint-ignore-file no-explicit-any
|
||||||
import { JSONUtils, PosixDate, VariantTag } from "@fabric/core";
|
import { PosixDate, VariantTag } from "@fabric/core";
|
||||||
import { FieldDefinition, FieldToType, Model } from "@fabric/domain";
|
import { FieldDefinition, FieldToType, Model } from "@fabric/domain";
|
||||||
|
|
||||||
export function transformRow(model: Model) {
|
export function transformRow(model: Model) {
|
||||||
@ -39,8 +39,7 @@ const FieldSQLInsertMap: FieldSQLInsertMap = {
|
|||||||
ReferenceField: (_, v) => v,
|
ReferenceField: (_, v) => v,
|
||||||
FloatField: (_, v) => v,
|
FloatField: (_, v) => v,
|
||||||
DecimalField: (_, v) => v,
|
DecimalField: (_, v) => v,
|
||||||
PosixDateField: (_, v) => new PosixDate(v),
|
TimestampField: (_, v) => new PosixDate(v),
|
||||||
EmbeddedField: (_, v: string) => JSONUtils.parse(v),
|
EmbeddedField: (_, v: string) => JSON.parse(v),
|
||||||
BooleanField: (_, v) => v,
|
BooleanField: (_, v) => v,
|
||||||
EmailField: (_, v) => v,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
// deno-lint-ignore-file no-explicit-any
|
// deno-lint-ignore-file no-explicit-any
|
||||||
import { JSONUtils, VariantTag } from "@fabric/core";
|
import { VariantTag } from "@fabric/core";
|
||||||
import { FieldDefinition, FieldToType } from "@fabric/domain";
|
import { FieldDefinition, FieldToType } from "@fabric/domain";
|
||||||
import { isNullish } from "@fabric/validations";
|
|
||||||
|
|
||||||
type FieldSQLInsertMap = {
|
type FieldSQLInsertMap = {
|
||||||
[K in FieldDefinition[VariantTag]]: (
|
[K in FieldDefinition[VariantTag]]: (
|
||||||
@ -21,14 +20,13 @@ const FieldSQLInsertMap: FieldSQLInsertMap = {
|
|||||||
ReferenceField: (_, v) => v,
|
ReferenceField: (_, v) => v,
|
||||||
FloatField: (_, v) => v,
|
FloatField: (_, v) => v,
|
||||||
DecimalField: (_, v) => v,
|
DecimalField: (_, v) => v,
|
||||||
PosixDateField: (_, v) => v.timestamp,
|
TimestampField: (_, v) => v.timestamp,
|
||||||
EmbeddedField: (_, v: string) => JSONUtils.stringify(v),
|
EmbeddedField: (_, v: string) => JSON.stringify(v),
|
||||||
BooleanField: (_, v) => v,
|
BooleanField: (_, v) => v,
|
||||||
EmailField: (_, v) => v,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fieldValueToSQL(field: FieldDefinition, value: any) {
|
export function fieldValueToSQL(field: FieldDefinition, value: any) {
|
||||||
if (isNullish(value)) {
|
if (value === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const r = FieldSQLInsertMap[field[VariantTag]] as any;
|
const r = FieldSQLInsertMap[field[VariantTag]] as any;
|
||||||
|
|||||||
@ -1,7 +1,14 @@
|
|||||||
import { Run } from "@fabric/core";
|
import { Run, UUID } from "@fabric/core";
|
||||||
import { Field, isLike, Model } from "@fabric/domain";
|
import { Field, isLike, Model } from "@fabric/domain";
|
||||||
import { UUIDGeneratorMock } from "@fabric/domain/mocks";
|
import { UUIDGeneratorMock } from "@fabric/domain/mocks";
|
||||||
import { afterEach, beforeEach, describe, expect, test } from "@fabric/testing";
|
import {
|
||||||
|
afterEach,
|
||||||
|
beforeEach,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
expectTypeOf,
|
||||||
|
test,
|
||||||
|
} from "@fabric/testing";
|
||||||
import { SQLiteStateStore } from "./state-store.ts";
|
import { SQLiteStateStore } from "./state-store.ts";
|
||||||
|
|
||||||
describe("State Store", () => {
|
describe("State Store", () => {
|
||||||
@ -9,7 +16,6 @@ describe("State Store", () => {
|
|||||||
Model.entityFrom("demo", {
|
Model.entityFrom("demo", {
|
||||||
value: Field.float(),
|
value: Field.float(),
|
||||||
owner: Field.reference({ targetModel: "users" }),
|
owner: Field.reference({ targetModel: "users" }),
|
||||||
optional: Field.string({ isOptional: true }),
|
|
||||||
}),
|
}),
|
||||||
Model.entityFrom("users", {
|
Model.entityFrom("users", {
|
||||||
name: Field.string(),
|
name: Field.string(),
|
||||||
@ -46,12 +52,12 @@ describe("State Store", () => {
|
|||||||
|
|
||||||
const result = await store.from("users").select().unwrapOrThrow();
|
const result = await store.from("users").select().unwrapOrThrow();
|
||||||
|
|
||||||
// expectTypeOf(result).toEqualTypeOf<
|
expectTypeOf(result).toEqualTypeOf<
|
||||||
// {
|
{
|
||||||
// id: UUID;
|
id: UUID;
|
||||||
// name: string;
|
name: string;
|
||||||
// }[]
|
}[]
|
||||||
// >();
|
>();
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
@ -89,12 +95,12 @@ describe("State Store", () => {
|
|||||||
})
|
})
|
||||||
.select().unwrapOrThrow();
|
.select().unwrapOrThrow();
|
||||||
|
|
||||||
// expectTypeOf(result).toEqualTypeOf<
|
expectTypeOf(result).toEqualTypeOf<
|
||||||
// {
|
{
|
||||||
// id: UUID;
|
id: UUID;
|
||||||
// name: string;
|
name: string;
|
||||||
// }[]
|
}[]
|
||||||
// >();
|
>();
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
|
|||||||
@ -17,13 +17,13 @@ describe("Sanitize String", () => {
|
|||||||
test("Given a number value it should convert it to a string", () => {
|
test("Given a number value it should convert it to a string", () => {
|
||||||
const sanitized = parseAndSanitizeString(123);
|
const sanitized = parseAndSanitizeString(123);
|
||||||
|
|
||||||
expect(sanitized).toBe(undefined);
|
expect(sanitized).toBe("123");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Given a boolean value it should convert it to a string", () => {
|
test("Given a boolean value it should convert it to a string", () => {
|
||||||
const sanitized = parseAndSanitizeString(true);
|
const sanitized = parseAndSanitizeString(true);
|
||||||
|
|
||||||
expect(sanitized).toBe(undefined);
|
expect(sanitized).toBe("true");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Given a null value it should return null", () => {
|
test("Given a null value it should return null", () => {
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import { isNullish } from "../nullish/is-nullish.ts";
|
|||||||
export function parseAndSanitizeString(
|
export function parseAndSanitizeString(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
if (isNullish(value) || typeof value != "string") return undefined;
|
if (isNullish(value)) return undefined;
|
||||||
return stripLow(value).trim();
|
return stripLow((String(value)).trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
// deno-lint-ignore no-control-regex
|
// deno-lint-ignore no-control-regex
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user