[fabric/domain] Simplify model definition
This commit is contained in:
parent
25870c8558
commit
9f9419c2b6
@ -1,37 +0,0 @@
|
||||
import type { Keyof } from "../../core/index.ts";
|
||||
import type { CustomModelFields } from "./custom-model-fields.ts";
|
||||
import { DefaultEntityFields, type EntityModel } from "./entity-model.ts";
|
||||
import { Field } from "./fields/index.ts";
|
||||
|
||||
export const DefaultAggregateFields = {
|
||||
...DefaultEntityFields,
|
||||
streamId: Field.uuid({ isIndexed: true }),
|
||||
streamVersion: Field.integer({
|
||||
isUnsigned: true,
|
||||
hasArbitraryPrecision: true,
|
||||
}),
|
||||
deletedAt: Field.timestamp({ isOptional: true }),
|
||||
};
|
||||
|
||||
export interface AggregateModel<
|
||||
TName extends string = string,
|
||||
TFields extends CustomModelFields = CustomModelFields,
|
||||
> extends EntityModel<TName, TFields> {
|
||||
fields: typeof DefaultAggregateFields & TFields;
|
||||
}
|
||||
|
||||
export function defineAggregateModel<
|
||||
TName extends string,
|
||||
TFields extends CustomModelFields,
|
||||
>(name: TName, fields: TFields): AggregateModel<TName, TFields> {
|
||||
return {
|
||||
name,
|
||||
fields: { ...DefaultAggregateFields, ...fields },
|
||||
} as const;
|
||||
}
|
||||
|
||||
export type ModelAddressableFields<TModel extends AggregateModel> = {
|
||||
[K in Keyof<TModel["fields"]>]: TModel["fields"][K] extends { isUnique: true }
|
||||
? K
|
||||
: never;
|
||||
}[Keyof<TModel["fields"]>];
|
||||
@ -1,3 +0,0 @@
|
||||
import type { FieldDefinition } from "./fields/index.ts";
|
||||
|
||||
export type CustomModelFields = Record<string, FieldDefinition>;
|
||||
@ -1,14 +0,0 @@
|
||||
import type { CustomModelFields } from "./custom-model-fields.ts";
|
||||
import { Field } from "./fields/index.ts";
|
||||
import type { Model } from "./model.ts";
|
||||
|
||||
export const DefaultEntityFields = {
|
||||
id: Field.uuid({ isPrimaryKey: true }),
|
||||
};
|
||||
|
||||
export interface EntityModel<
|
||||
TName extends string = string,
|
||||
TFields extends CustomModelFields = CustomModelFields,
|
||||
> extends Model<TName, TFields> {
|
||||
fields: typeof DefaultEntityFields & TFields;
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { isError } from "@fabric/core";
|
||||
import { describe, expect, test } from "@fabric/testing";
|
||||
import { defineAggregateModel } from "../aggregate-model.ts";
|
||||
import { Model } from "../model.ts";
|
||||
import { Field } from "./index.ts";
|
||||
import {
|
||||
InvalidReferenceFieldError,
|
||||
@ -9,7 +9,7 @@ import {
|
||||
|
||||
describe("Validate Reference Field", () => {
|
||||
const schema = {
|
||||
User: defineAggregateModel("User", {
|
||||
User: Model.aggregateFrom("User", {
|
||||
name: Field.string(),
|
||||
password: Field.string(),
|
||||
otherUnique: Field.integer({ isUnique: true }),
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
export * from "./aggregate-model.ts";
|
||||
export * from "./custom-model-fields.ts";
|
||||
export * from "./fields/index.ts";
|
||||
export * from "./model-schema.ts";
|
||||
export * from "./model.ts";
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import type { UUID } from "@fabric/core";
|
||||
import { describe, expectTypeOf, test } from "@fabric/testing";
|
||||
import type { PosixDate } from "../../core/index.ts";
|
||||
import { defineAggregateModel } from "./aggregate-model.ts";
|
||||
import { Field } from "./fields/index.ts";
|
||||
import type { ModelToType } from "./model.ts";
|
||||
import { Model, type ModelToType } from "./model.ts";
|
||||
|
||||
describe("CreateModel", () => {
|
||||
test("should create a model and it's interface type", () => {
|
||||
const User = defineAggregateModel("User", {
|
||||
const User = Model.aggregateFrom("User", {
|
||||
name: Field.string(),
|
||||
password: Field.string(),
|
||||
phone: Field.string({ isOptional: true }),
|
||||
|
||||
@ -1,29 +1,73 @@
|
||||
import type { Keyof } from "@fabric/core";
|
||||
import type { CustomModelFields } from "./custom-model-fields.ts";
|
||||
import type { FieldToType } from "./fields/field-to-type.ts";
|
||||
import { Field, type FieldDefinition } from "./fields/index.ts";
|
||||
|
||||
export interface Model<
|
||||
/**
|
||||
* A model is a schema definition for some type of structured data.
|
||||
*/
|
||||
export class Model<
|
||||
TName extends string = string,
|
||||
TFields extends CustomModelFields = CustomModelFields,
|
||||
TFields extends ModelFields = ModelFields,
|
||||
> {
|
||||
name: TName;
|
||||
fields: TFields;
|
||||
static from<TName extends string, TFields extends ModelFields>(
|
||||
name: TName,
|
||||
fields: TFields,
|
||||
) {
|
||||
return new Model(name, fields);
|
||||
}
|
||||
|
||||
export function defineModel<
|
||||
TName extends string,
|
||||
TFields extends CustomModelFields,
|
||||
>(name: TName, fields: TFields): Model<TName, TFields> {
|
||||
return {
|
||||
name,
|
||||
fields,
|
||||
} as const;
|
||||
static aggregateFrom<TName extends string, TFields extends ModelFields>(
|
||||
name: TName,
|
||||
fields: TFields,
|
||||
): Model<TName, TFields & typeof DefaultAggregateFields> {
|
||||
return new Model(name, { ...fields, ...DefaultAggregateFields });
|
||||
}
|
||||
|
||||
static entityFrom<TName extends string, TFields extends ModelFields>(
|
||||
name: TName,
|
||||
fields: TFields,
|
||||
): Model<TName, TFields & typeof DefaultEntityFields> {
|
||||
return new Model(name, { ...fields, ...DefaultEntityFields });
|
||||
}
|
||||
private constructor(readonly name: TName, readonly fields: TFields) {}
|
||||
}
|
||||
|
||||
export type EntityModel = Model<
|
||||
string,
|
||||
typeof DefaultEntityFields & ModelFields
|
||||
>;
|
||||
|
||||
export type AggregateModel = Model<
|
||||
string,
|
||||
typeof DefaultAggregateFields & ModelFields
|
||||
>;
|
||||
|
||||
export type ModelToType<TModel extends Model> = {
|
||||
[K in Keyof<TModel["fields"]>]: FieldToType<TModel["fields"][K]>;
|
||||
};
|
||||
|
||||
export type ModelFieldNames<TModel extends CustomModelFields> = Keyof<
|
||||
export type ModelFieldNames<TModel extends ModelFields> = 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"]>];
|
||||
|
||||
type ModelFields = Record<string, FieldDefinition>;
|
||||
|
||||
const DefaultEntityFields = {
|
||||
id: Field.uuid({ isPrimaryKey: true }),
|
||||
} as const;
|
||||
|
||||
const DefaultAggregateFields = {
|
||||
...DefaultEntityFields,
|
||||
streamId: Field.uuid({ isIndexed: true }),
|
||||
streamVersion: Field.integer({
|
||||
isUnsigned: true,
|
||||
hasArbitraryPrecision: true,
|
||||
}),
|
||||
deletedAt: Field.timestamp({ isOptional: true }),
|
||||
} as const;
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { VariantTag } from "@fabric/core";
|
||||
import type { Event } from "../events/event.ts";
|
||||
import type { StoredEvent } from "../events/stored-event.ts";
|
||||
import type { AggregateModel } from "../models/aggregate-model.ts";
|
||||
import type { ModelToType } from "../models/model.ts";
|
||||
import type { AggregateModel, ModelToType } from "../models/model.ts";
|
||||
|
||||
export interface Projection<
|
||||
TModel extends AggregateModel,
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import {
|
||||
defineModel,
|
||||
Field,
|
||||
isGreaterOrEqualTo,
|
||||
isGreaterThan,
|
||||
@ -8,12 +7,13 @@ import {
|
||||
isLessThan,
|
||||
isLike,
|
||||
isNotEqualTo,
|
||||
Model,
|
||||
} from "@fabric/domain";
|
||||
import { describe, expect, test } from "@fabric/testing";
|
||||
import { filterToParams, filterToSQL } from "./filter-to-sql.ts";
|
||||
|
||||
describe("SQL where clause from filter options", () => {
|
||||
const col = defineModel("users", {
|
||||
const col = Model.from("users", {
|
||||
name: Field.string(),
|
||||
age: Field.integer(),
|
||||
status: Field.string(),
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
MultiFilterOption,
|
||||
SingleFilterOption,
|
||||
} from "@fabric/domain";
|
||||
import { keyToParam } from "./record-utils.ts";
|
||||
import { keyToParamKey } from "./record-utils.ts";
|
||||
import { fieldValueToSQL } from "./value-to-sql.ts";
|
||||
|
||||
export function filterToSQL(filterOptions?: FilterOptions) {
|
||||
@ -57,7 +57,7 @@ function getWhereFromSingleOption(
|
||||
const WHERE_KEY_PREFIX = "where_";
|
||||
|
||||
function getWhereParamKey(key: string, opts: { postfix?: string } = {}) {
|
||||
return keyToParam(`${WHERE_KEY_PREFIX}${key}${opts.postfix ?? ""}`);
|
||||
return keyToParamKey(`${WHERE_KEY_PREFIX}${key}${opts.postfix ?? ""}`);
|
||||
}
|
||||
|
||||
function getWhereFromKeyValue(
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { defineModel, Field } from "@fabric/domain";
|
||||
import { Field, Model } from "@fabric/domain";
|
||||
import { describe, expect, test } from "@fabric/testing";
|
||||
import { modelToSql } from "./model-to-sql.ts";
|
||||
|
||||
describe("ModelToSQL", () => {
|
||||
const model = defineModel("something", {
|
||||
const model = Model.from("something", {
|
||||
id: Field.uuid({ isPrimaryKey: true }),
|
||||
name: Field.string(),
|
||||
age: Field.integer(),
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { PosixDate, Run, UUID } from "@fabric/core";
|
||||
import { defineAggregateModel, Field, isLike } from "@fabric/domain";
|
||||
import { Run, UUID } from "@fabric/core";
|
||||
import { Field, isLike, Model } from "@fabric/domain";
|
||||
import { UUIDGeneratorMock } from "@fabric/domain/mocks";
|
||||
import {
|
||||
afterEach,
|
||||
@ -13,11 +13,11 @@ import { SQLiteStateStore } from "./state-store.ts";
|
||||
|
||||
describe("State Store", () => {
|
||||
const models = [
|
||||
defineAggregateModel("demo", {
|
||||
Model.entityFrom("demo", {
|
||||
value: Field.float(),
|
||||
owner: Field.reference({ targetModel: "users" }),
|
||||
}),
|
||||
defineAggregateModel("users", {
|
||||
Model.entityFrom("users", {
|
||||
name: Field.string(),
|
||||
}),
|
||||
];
|
||||
@ -39,9 +39,6 @@ describe("State Store", () => {
|
||||
await store.insertInto("users", {
|
||||
id: newUUID,
|
||||
name: "test",
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
});
|
||||
|
||||
@ -51,9 +48,6 @@ describe("State Store", () => {
|
||||
await store.insertInto("users", {
|
||||
name: "test",
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
|
||||
const result = await store.from("users").select().unwrapOrThrow();
|
||||
@ -61,20 +55,14 @@ describe("State Store", () => {
|
||||
expectTypeOf(result).toEqualTypeOf<
|
||||
{
|
||||
id: UUID;
|
||||
streamId: UUID;
|
||||
streamVersion: bigint;
|
||||
name: string;
|
||||
deletedAt: PosixDate | null;
|
||||
}[]
|
||||
>();
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
name: "test",
|
||||
deletedAt: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -87,25 +75,16 @@ describe("State Store", () => {
|
||||
store.insertInto("users", {
|
||||
name: "test",
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}),
|
||||
() =>
|
||||
store.insertInto("users", {
|
||||
name: "anotherName",
|
||||
id: UUIDGeneratorMock.generate(),
|
||||
streamId: UUIDGeneratorMock.generate(),
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}),
|
||||
() =>
|
||||
store.insertInto("users", {
|
||||
name: "anotherName2",
|
||||
id: UUIDGeneratorMock.generate(),
|
||||
streamId: UUIDGeneratorMock.generate(),
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -119,20 +98,14 @@ describe("State Store", () => {
|
||||
expectTypeOf(result).toEqualTypeOf<
|
||||
{
|
||||
id: UUID;
|
||||
streamId: UUID;
|
||||
streamVersion: bigint;
|
||||
name: string;
|
||||
deletedAt: PosixDate | null;
|
||||
}[]
|
||||
>();
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
name: "test",
|
||||
deletedAt: null,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@ -143,9 +116,6 @@ describe("State Store", () => {
|
||||
await store.insertInto("users", {
|
||||
name: "test",
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
|
||||
await store.update("users", newUUID, {
|
||||
@ -157,10 +127,7 @@ describe("State Store", () => {
|
||||
|
||||
expect(result).toEqual({
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
name: "updated",
|
||||
deletedAt: null,
|
||||
});
|
||||
});
|
||||
|
||||
@ -170,9 +137,6 @@ describe("State Store", () => {
|
||||
await store.insertInto("users", {
|
||||
name: "test",
|
||||
id: newUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
|
||||
await store.delete("users", newUUID).orThrow();
|
||||
@ -192,18 +156,12 @@ describe("State Store", () => {
|
||||
await store.insertInto("users", {
|
||||
id: ownerUUID,
|
||||
name: "test",
|
||||
streamId: ownerUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
|
||||
await store.insertInto("demo", {
|
||||
id: newUUID,
|
||||
value: 1.0,
|
||||
owner: ownerUUID,
|
||||
streamId: newUUID,
|
||||
streamVersion: 1n,
|
||||
deletedAt: null,
|
||||
}).orThrow();
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user