[fabric/domain] improve structure
This commit is contained in:
parent
3f91e35790
commit
9092b032b3
@ -1,7 +1,7 @@
|
||||
import { AsyncResult, MaybePromise, PosixDate } from "@fabric/core";
|
||||
import { StoreQueryError } from "../errors/query-error.js";
|
||||
import { Event, StoredEvent } from "../events/event.js";
|
||||
import { UUID } from "../types/uuid.js";
|
||||
import { Event, StoredEvent } from "./event.js";
|
||||
|
||||
export interface EventStore<TEvent extends Event = Event> {
|
||||
getStream<TEventStreamEvent extends TEvent>(
|
||||
@ -1 +1,2 @@
|
||||
export * from "./event-store.js";
|
||||
export * from "./event.js";
|
||||
|
||||
@ -2,7 +2,6 @@ export * from "./errors/index.js";
|
||||
export * from "./events/index.js";
|
||||
export * from "./files/index.js";
|
||||
export * from "./models/index.js";
|
||||
export * from "./query/index.js";
|
||||
export * from "./security/index.js";
|
||||
export * from "./storage/index.js";
|
||||
export * from "./types/index.js";
|
||||
|
||||
@ -9,22 +9,22 @@ import {
|
||||
|
||||
describe("Validate Reference Field", () => {
|
||||
const schema = {
|
||||
user: defineModel({
|
||||
User: defineModel("User", {
|
||||
name: Field.string(),
|
||||
password: Field.string(),
|
||||
phone: Field.string({ isOptional: true }),
|
||||
otherUnique: Field.integer({ isUnique: true }),
|
||||
otherNotUnique: Field.uuid(),
|
||||
otherUser: Field.reference({
|
||||
targetModel: "User",
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
it("should return an error when the target model is not in the schema", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
"post",
|
||||
"authorId",
|
||||
Field.reference({
|
||||
model: "foo",
|
||||
targetModel: "foo",
|
||||
}),
|
||||
);
|
||||
|
||||
@ -33,33 +33,26 @@ describe("Validate Reference Field", () => {
|
||||
}
|
||||
|
||||
expect(result).toBeInstanceOf(InvalidReferenceField);
|
||||
expect(result.toString()).toBe(
|
||||
"InvalidReferenceField: post.authorId. The target model 'foo' is not in the schema.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not return an error if the target model is in the schema", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
"post",
|
||||
"authorId",
|
||||
Field.reference({
|
||||
model: "user",
|
||||
targetModel: "User",
|
||||
}),
|
||||
);
|
||||
|
||||
if (isError(result)) {
|
||||
throw result.toString();
|
||||
throw result.reason;
|
||||
}
|
||||
});
|
||||
|
||||
it("should return an error if the target key is not in the target model", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
"post",
|
||||
"authorId",
|
||||
Field.reference({
|
||||
model: "user",
|
||||
targetModel: "User",
|
||||
targetKey: "foo",
|
||||
}),
|
||||
);
|
||||
@ -69,34 +62,13 @@ describe("Validate Reference Field", () => {
|
||||
}
|
||||
|
||||
expect(result).toBeInstanceOf(InvalidReferenceField);
|
||||
expect(result.toString()).toBe(
|
||||
"InvalidReferenceField: post.authorId. The target key 'foo' is not in the target model 'user'.",
|
||||
);
|
||||
});
|
||||
|
||||
it("should not return an error if the target key is in the target model", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
"post",
|
||||
"authorId",
|
||||
Field.reference({
|
||||
model: "user",
|
||||
targetKey: "otherUnique",
|
||||
}),
|
||||
);
|
||||
|
||||
if (isError(result)) {
|
||||
throw result.toString();
|
||||
}
|
||||
});
|
||||
|
||||
it("should return error if the target key is not unique", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
"post",
|
||||
"authorId",
|
||||
Field.reference({
|
||||
model: "user",
|
||||
targetModel: "User",
|
||||
targetKey: "otherNotUnique",
|
||||
}),
|
||||
);
|
||||
@ -106,8 +78,19 @@ describe("Validate Reference Field", () => {
|
||||
}
|
||||
|
||||
expect(result).toBeInstanceOf(InvalidReferenceField);
|
||||
expect(result.toString()).toBe(
|
||||
"InvalidReferenceField: post.authorId. The target key 'user'.'otherNotUnique' is not unique.",
|
||||
});
|
||||
|
||||
it("should not return an error if the target key is in the target model and is unique", () => {
|
||||
const result = validateReferenceField(
|
||||
schema,
|
||||
Field.reference({
|
||||
targetModel: "User",
|
||||
targetKey: "otherUnique",
|
||||
}),
|
||||
);
|
||||
|
||||
if (isError(result)) {
|
||||
throw result.toString();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -3,7 +3,7 @@ import { ModelSchema } from "../model-schema.js";
|
||||
import { BaseField } from "./base-field.js";
|
||||
|
||||
export interface ReferenceFieldOptions extends BaseField {
|
||||
model: string;
|
||||
targetModel: string;
|
||||
targetKey?: string;
|
||||
}
|
||||
|
||||
@ -22,45 +22,32 @@ export function createReferenceField<T extends ReferenceFieldOptions>(
|
||||
|
||||
export function validateReferenceField(
|
||||
schema: ModelSchema,
|
||||
modelName: string,
|
||||
fieldName: string,
|
||||
field: ReferenceField,
|
||||
): Result<void, InvalidReferenceField> {
|
||||
if (!schema[field.model]) {
|
||||
if (!schema[field.targetModel]) {
|
||||
return new InvalidReferenceField(
|
||||
modelName,
|
||||
fieldName,
|
||||
`The target model '${field.model}' is not in the schema.`,
|
||||
`The target model '${field.targetModel}' is not in the schema.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (field.targetKey && !schema[field.model][field.targetKey]) {
|
||||
if (field.targetKey && !schema[field.targetModel].fields[field.targetKey]) {
|
||||
return new InvalidReferenceField(
|
||||
modelName,
|
||||
fieldName,
|
||||
`The target key '${field.targetKey}' is not in the target model '${field.model}'.`,
|
||||
`The target key '${field.targetKey}' is not in the target model '${field.targetModel}'.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (field.targetKey && !schema[field.model][field.targetKey].isUnique) {
|
||||
if (
|
||||
field.targetKey &&
|
||||
!schema[field.targetModel].fields[field.targetKey].isUnique
|
||||
) {
|
||||
return new InvalidReferenceField(
|
||||
modelName,
|
||||
fieldName,
|
||||
`The target key '${field.model}'.'${field.targetKey}' is not unique.`,
|
||||
`The target key '${field.targetModel}'.'${field.targetKey}' is not unique.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidReferenceField extends TaggedError<"InvalidReferenceField"> {
|
||||
constructor(
|
||||
readonly modelName: string,
|
||||
readonly fieldName: string,
|
||||
readonly reason: string,
|
||||
) {
|
||||
constructor(readonly reason: string) {
|
||||
super("InvalidReferenceField");
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `InvalidReferenceField: ${this.modelName}.${this.fieldName}. ${this.reason}`;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
export * from "./fields/index.js";
|
||||
export * from "./model-schema.js";
|
||||
export * from "./model.js";
|
||||
export * from "./types/index.js";
|
||||
export * from "./query/index.js";
|
||||
export * from "./state-store.js";
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { describe, expectTypeOf, it } from "vitest";
|
||||
import { UUID } from "../types/uuid.js";
|
||||
import { Field } from "./fields/index.js";
|
||||
import { defineModel } from "./model.js";
|
||||
import { ModelToType } from "./types/model-to-type.js";
|
||||
import { defineModel, ModelToType } from "./model.js";
|
||||
|
||||
describe("CreateModel", () => {
|
||||
it("should create a model and it's interface type", () => {
|
||||
const User = defineModel({
|
||||
const User = defineModel("User", {
|
||||
name: Field.string(),
|
||||
password: Field.string(),
|
||||
phone: Field.string({ isOptional: true }),
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Keyof } from "@fabric/core";
|
||||
import { FieldToType } from "./fields/field-to-type.js";
|
||||
import { Field, FieldDefinition } from "./fields/index.js";
|
||||
|
||||
export type CustomModelFields = Record<string, FieldDefinition>;
|
||||
@ -10,14 +12,34 @@ export const DefaultModelFields = {
|
||||
hasArbitraryPrecision: true,
|
||||
}),
|
||||
};
|
||||
export type Model<TFields extends CustomModelFields = CustomModelFields> =
|
||||
typeof DefaultModelFields & TFields;
|
||||
export interface Model<
|
||||
TName extends string = string,
|
||||
TFields extends CustomModelFields = CustomModelFields,
|
||||
> {
|
||||
name: TName;
|
||||
fields: typeof DefaultModelFields & TFields;
|
||||
}
|
||||
|
||||
export function defineModel<TFields extends CustomModelFields>(
|
||||
fields: TFields,
|
||||
): Model<TFields> {
|
||||
export function defineModel<
|
||||
TName extends string,
|
||||
TFields extends CustomModelFields,
|
||||
>(name: TName, fields: TFields): Model<TName, TFields> {
|
||||
return {
|
||||
...fields,
|
||||
...DefaultModelFields,
|
||||
name,
|
||||
fields: { ...DefaultModelFields, ...fields },
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type ModelToType<TModel extends Model> = {
|
||||
[K in Keyof<TModel["fields"]>]: FieldToType<TModel["fields"][K]>;
|
||||
};
|
||||
|
||||
export type ModelFieldNames<TModel extends CustomModelFields> = Keyof<
|
||||
TModel["fields"]
|
||||
>;
|
||||
|
||||
export type ModelAddressableFields<TModel extends Model> = {
|
||||
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends { isUnique: true }
|
||||
? K
|
||||
: never;
|
||||
}[Keyof<TModel["fields"]>];
|
||||
|
||||
@ -14,9 +14,9 @@ export type SingleFilterOption<T = any> = {
|
||||
|
||||
export type MultiFilterOption<T = any> = SingleFilterOption<T>[];
|
||||
|
||||
export const FILTER_OPTION_TYPE_SYMBOL = Symbol("$type");
|
||||
export const FILTER_OPTION_VALUE_SYMBOL = Symbol("$value");
|
||||
export const FILTER_OPTION_OPERATOR_SYMBOL = Symbol("$operator");
|
||||
export const FILTER_OPTION_TYPE_SYMBOL = Symbol("filter_type");
|
||||
export const FILTER_OPTION_VALUE_SYMBOL = Symbol("filter_value");
|
||||
export const FILTER_OPTION_OPERATOR_SYMBOL = Symbol("filter_operator");
|
||||
|
||||
export type LikeFilterOption<T> = T extends string
|
||||
? {
|
||||
@ -1,9 +1,6 @@
|
||||
import { AsyncResult, Keyof } from "@fabric/core";
|
||||
import { StoreQueryError } from "../errors/query-error.js";
|
||||
import { ModelToType } from "../models/index.js";
|
||||
import { ModelSchema } from "../models/model-schema.js";
|
||||
import { StorageDriver } from "../storage/storage-driver.js";
|
||||
import { AggregateOptions } from "./aggregate-options.js";
|
||||
import { StoreQueryError } from "../../errors/query-error.js";
|
||||
import { StorageDriver } from "../../storage/storage-driver.js";
|
||||
import { FilterOptions } from "./filter-options.js";
|
||||
import { OrderByOptions } from "./order-by-options.js";
|
||||
import {
|
||||
@ -14,44 +11,32 @@ import {
|
||||
StoreSortableQuery,
|
||||
} from "./query.js";
|
||||
|
||||
export class QueryBuilder<
|
||||
TModels extends ModelSchema,
|
||||
TEntityName extends Keyof<TModels>,
|
||||
T = ModelToType<TModels[TEntityName]>,
|
||||
> implements StoreQuery<T>
|
||||
{
|
||||
export class QueryBuilder<T> implements StoreQuery<T> {
|
||||
constructor(
|
||||
private driver: StorageDriver,
|
||||
private query: QueryDefinition<TEntityName>,
|
||||
private query: QueryDefinition,
|
||||
) {}
|
||||
aggregate<K extends AggregateOptions<T>>(): SelectableQuery<K> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
|
||||
where(where: FilterOptions<T>): StoreSortableQuery<T> {
|
||||
this.query = {
|
||||
return new QueryBuilder(this.driver, {
|
||||
...this.query,
|
||||
where,
|
||||
};
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
orderBy(opts: OrderByOptions<T>): StoreLimitableQuery<T> {
|
||||
this.query = {
|
||||
return new QueryBuilder(this.driver, {
|
||||
...this.query,
|
||||
orderBy: opts,
|
||||
};
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
limit(limit: number, offset?: number | undefined): SelectableQuery<T> {
|
||||
this.query = {
|
||||
return new QueryBuilder(this.driver, {
|
||||
...this.query,
|
||||
limit,
|
||||
offset,
|
||||
};
|
||||
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
select<K extends Keyof<T>>(
|
||||
@ -1,7 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { AsyncResult, Keyof } from "@fabric/core";
|
||||
import { StoreQueryError } from "../errors/query-error.js";
|
||||
import { AggregateOptions } from "./aggregate-options.js";
|
||||
import { StoreQueryError } from "../../errors/query-error.js";
|
||||
import { FilterOptions } from "./filter-options.js";
|
||||
import { OrderByOptions } from "./order-by-options.js";
|
||||
|
||||
@ -10,8 +9,6 @@ export interface StoreQuery<T> {
|
||||
orderBy(opts: OrderByOptions<T>): StoreLimitableQuery<T>;
|
||||
limit(limit: number, offset?: number): SelectableQuery<T>;
|
||||
|
||||
aggregate<K extends AggregateOptions<T>>(opts: K): SelectableQuery<K>;
|
||||
|
||||
select(): AsyncResult<T[], StoreQueryError>;
|
||||
select<K extends Keyof<T>>(
|
||||
keys: K[],
|
||||
5
packages/fabric/domain/src/models/state-store.ts
Normal file
5
packages/fabric/domain/src/models/state-store.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { StorageDriver } from "../storage/storage-driver.js";
|
||||
|
||||
export class StateStore {
|
||||
constructor(private driver: StorageDriver) {}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
export * from "./model-field-names.js";
|
||||
export * from "./model-to-type.js";
|
||||
@ -1,3 +0,0 @@
|
||||
import { CustomModelFields } from "../model.js";
|
||||
|
||||
export type ModelFieldNames<TModel extends CustomModelFields> = keyof TModel;
|
||||
@ -1,6 +0,0 @@
|
||||
import { FieldToType } from "../fields/field-to-type.js";
|
||||
import { Model } from "../model.js";
|
||||
|
||||
export type ModelToType<TModel extends Model> = {
|
||||
[K in keyof TModel]: FieldToType<TModel[K]>;
|
||||
};
|
||||
@ -1,3 +1 @@
|
||||
export * from "./event-store.js";
|
||||
export * from "./state-store.js";
|
||||
export * from "./storage-driver.js";
|
||||
|
||||
@ -1,18 +0,0 @@
|
||||
import { Keyof } from "@fabric/core";
|
||||
import { ModelToType } from "../models/index.js";
|
||||
import { ModelSchema } from "../models/model-schema.js";
|
||||
import { QueryBuilder } from "../query/query-builder.js";
|
||||
import { StoreQuery } from "../query/query.js";
|
||||
import { StorageDriver } from "./storage-driver.js";
|
||||
|
||||
export class StateStore<TModels extends ModelSchema> {
|
||||
constructor(private driver: StorageDriver) {}
|
||||
|
||||
from<TEntityName extends Keyof<TModels>>(
|
||||
entityName: TEntityName,
|
||||
): StoreQuery<ModelToType<TModels[TEntityName]>> {
|
||||
return new QueryBuilder(this.driver, {
|
||||
from: entityName,
|
||||
}) as StoreQuery<ModelToType<TModels[TEntityName]>>;
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@ import { AsyncResult, UnexpectedError } from "@fabric/core";
|
||||
import { CircularDependencyError } from "../errors/circular-dependency-error.js";
|
||||
import { StoreQueryError } from "../errors/query-error.js";
|
||||
import { ModelSchema } from "../models/model-schema.js";
|
||||
import { QueryDefinition } from "../query/query.js";
|
||||
import { QueryDefinition } from "../models/query/query.js";
|
||||
|
||||
export interface StorageDriver {
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user