Compare commits

...

3 commits
v4.1.0 ... main

Author SHA1 Message Date
bf89dc00fe
Add model manager extension type.
All checks were successful
/ test (push) Successful in 40s
2025-06-29 16:41:14 +02:00
f238499f06
Improve jsdoc.
All checks were successful
/ test (push) Successful in 52s
2025-06-29 16:06:27 +02:00
d296658f64
Add model builder.
All checks were successful
/ test (push) Successful in 57s
2025-06-29 15:59:52 +02:00
5 changed files with 131 additions and 2 deletions

77
src/model/builder.ts Normal file
View file

@ -0,0 +1,77 @@
import {
defineModel,
IdentifierDefinition,
ModelDefinition,
ModelShape,
} from "./model";
import {ConstructorOf} from "../utils";
import {Definition} from "./property-definition";
/**
* Model definition builder.
*/
export class ModelBuilder<
T extends object,
Shape extends ModelShape<T>,
Identifier extends IdentifierDefinition<T, Shape>,
> {
/**
* The built model definition.
*/
definition: ModelDefinition<T, Shape, Identifier>;
/**
* Define a new property.
* @param name The new property name.
* @param definition The new property definition.
*/
property<
SerializedType,
PropertyName extends Exclude<keyof T, keyof Shape>,
PropertyDefinition extends Definition<SerializedType, T[PropertyName]>,
>(name: PropertyName, definition: PropertyDefinition) {
(this.definition.properties[name] as Definition<unknown, T[typeof name]>) =
definition;
return this as unknown as ModelBuilder<
T,
Shape & {[k in PropertyName]: PropertyDefinition},
Identifier
>;
}
/**
* Set the model identifier.
* @param identifier The new model identifier.
*/
identifier<NewIdentifier extends IdentifierDefinition<T, Shape>>(
identifier: NewIdentifier,
) {
(this.definition.identifier as unknown) = identifier;
return this as unknown as ModelBuilder<T, Shape, NewIdentifier>;
}
/**
* Define a model using the current model definition.
*/
define() {
return defineModel(this.definition);
}
}
/**
* Initialize a model builder for the provided class.
* @param Class The class for which to build a model.
*/
export function newModel<
T extends object,
Shape extends ModelShape<T> = object,
Identifier extends IdentifierDefinition<T, Shape> = never,
>(Class: ConstructorOf<T>): ModelBuilder<T, Shape, Identifier> {
const builder = new ModelBuilder<T, Shape, Identifier>();
builder.definition = {
Class,
properties: {} as Shape,
};
return builder;
}

View file

@ -1,8 +1,8 @@
import * as property from "./properties"; export * as property from "./properties";
export {property};
export * from "./model"; export * from "./model";
export {Definition} from "./property-definition"; export {Definition} from "./property-definition";
export {newModel, ModelBuilder} from "./builder";
export {ArrayType} from "./types/array"; export {ArrayType} from "./types/array";
export {BooleanType} from "./types/boolean"; export {BooleanType} from "./types/boolean";

View file

@ -584,6 +584,19 @@ export class ModelManager<
} }
} }
/**
* A model manager extension is a mixin, building a new model manager class with extended capabilities.
* @see https://www.typescriptlang.org/docs/handbook/mixins.html
*/
export type ModelManagerExtension<
Extension extends object,
T extends object,
Shape extends ModelShape<T>,
Identifier extends IdentifierDefinition<T, Shape>,
> = (
modelManager: ModelManager<T, Shape, Identifier>,
) => ModelManager<T, Shape, Identifier> & Extension;
/** /**
* Define a new model. * Define a new model.
* @param definition The model definition object. * @param definition The model definition object.

View file

@ -214,6 +214,10 @@ export function model<
return define(new ModelType(definedModel)); return define(new ModelType(definedModel));
} }
/**
* Utility function to fix circular dependencies issues.
* @param definedModel A function returning the model to use.
*/
export function circular<T extends object>( export function circular<T extends object>(
definedModel: () => any, definedModel: () => any,
): () => GenericModelManager<T> { ): () => GenericModelManager<T> {

35
tests/builder.test.ts Normal file
View file

@ -0,0 +1,35 @@
import {describe, expect, test} from "vitest";
import {defineModel, newModel, s} from "../src/library";
class Testest {
foo: string;
bar: number;
baz: boolean;
}
describe("model builder", () => {
const modelBuilder = newModel(Testest)
.property("foo", s.property.string())
.property("bar", s.property.decimal())
.property("baz", s.property.boolean())
.identifier("foo");
test("build model definition", () => {
expect(modelBuilder.definition).toStrictEqual({
Class: Testest,
identifier: "foo",
properties: {
foo: s.property.string(),
bar: s.property.decimal(),
baz: s.property.boolean(),
},
});
});
test("build a model", () => {
expect(modelBuilder.define()).toStrictEqual(
defineModel(modelBuilder.definition),
);
});
});