Models rewrite with new API for better typings and extensibility.
This commit is contained in:
parent
498d25a909
commit
e43e27e2e1
17 changed files with 430 additions and 386 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@sharkitek/core",
|
||||
"version": "2.1.3",
|
||||
"version": "3.0.0",
|
||||
"description": "Sharkitek core models library.",
|
||||
"keywords": [
|
||||
"sharkitek",
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import {Type} from "./Types/Type";
|
||||
|
||||
/**
|
||||
* Options of a definition.
|
||||
*/
|
||||
export interface DefinitionOptions<SerializedType, SharkitekType>
|
||||
{ //TODO implement some options, like `mandatory`.
|
||||
}
|
||||
|
||||
/**
|
||||
* A Sharkitek model property definition.
|
||||
*/
|
||||
export class Definition<SerializedType, SharkitekType>
|
||||
{
|
||||
/**
|
||||
* Initialize a property definition with the given type and options.
|
||||
* @param type - The model property type.
|
||||
* @param options - Property definition options.
|
||||
*/
|
||||
constructor(
|
||||
public type: Type<SerializedType, SharkitekType>,
|
||||
public options: DefinitionOptions<SerializedType, SharkitekType> = {},
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a property definition with the given type and options.
|
||||
* @param type - The model property type.
|
||||
* @param options - Property definition options.
|
||||
*/
|
||||
export function SDefine<SerializedType, SharkitekType>(type: Type<SerializedType, SharkitekType>, options: DefinitionOptions<SerializedType, SharkitekType> = {}): Definition<SerializedType, SharkitekType>
|
||||
{
|
||||
return new Definition<SerializedType, SharkitekType>(type, options);
|
||||
}
|
|
@ -1,217 +1,231 @@
|
|||
import {Definition} from "./Definition";
|
||||
import {Definition} from "./PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Model properties definition type.
|
||||
* Type definition of a model constructor.
|
||||
*/
|
||||
export type ModelDefinition<T> = Partial<Record<keyof T, Definition<unknown, unknown>>>;
|
||||
/**
|
||||
* Model identifier type.
|
||||
*/
|
||||
export type ModelIdentifier<T> = keyof T;
|
||||
export type ConstructorOf<T extends object> = { new(): T; };
|
||||
|
||||
/**
|
||||
* A Sharkitek model.
|
||||
* Unknown property definition.
|
||||
*/
|
||||
export abstract class Model<THIS>
|
||||
export type UnknownDefinition = Definition<unknown, unknown>;
|
||||
|
||||
/**
|
||||
* A model shape.
|
||||
*/
|
||||
export type ModelShape = Record<string, UnknownDefinition>;
|
||||
|
||||
/**
|
||||
* Properties of a model based on its shape.
|
||||
*/
|
||||
export type PropertiesModel<Shape extends ModelShape> = {
|
||||
[k in keyof Shape]: Shape[k]["_sharkitek"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Serialized object type based on model shape.
|
||||
*/
|
||||
export type SerializedModel<Shape extends ModelShape> = {
|
||||
[k in keyof Shape]: Shape[k]["_serialized"];
|
||||
};
|
||||
|
||||
/**
|
||||
* Type of a model object.
|
||||
*/
|
||||
export type Model<Shape extends ModelShape, IdentifierType = unknown> = ModelDefinition<Shape, IdentifierType> & PropertiesModel<Shape>;
|
||||
|
||||
/**
|
||||
* Identifier type.
|
||||
*/
|
||||
export type IdentifierType<Shape extends ModelShape, K extends keyof Shape> = Shape[K]["_sharkitek"];
|
||||
|
||||
/**
|
||||
* Interface of a Sharkitek model definition.
|
||||
*/
|
||||
export interface ModelDefinition<Shape extends ModelShape, IdentifierType>
|
||||
{
|
||||
/**
|
||||
* Model properties definition function.
|
||||
*/
|
||||
protected abstract SDefinition(): ModelDefinition<THIS>;
|
||||
|
||||
/**
|
||||
* Return the name of the model identifier property.
|
||||
*/
|
||||
protected SIdentifier(): ModelIdentifier<THIS>
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get given property definition.
|
||||
* @protected
|
||||
*/
|
||||
protected getPropertyDefinition(propertyName: string): Definition<unknown, unknown>
|
||||
{
|
||||
return (this.SDefinition() as any)?.[propertyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of the model properties.
|
||||
* @protected
|
||||
*/
|
||||
protected getProperties(): string[]
|
||||
{
|
||||
return Object.keys(this.SDefinition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling a function for a defined property.
|
||||
* @param propertyName - The property for which to check definition.
|
||||
* @param callback - The function called when the property is defined.
|
||||
* @param notProperty - The function called when the property is not defined.
|
||||
* @protected
|
||||
*/
|
||||
protected propertyWithDefinition(propertyName: string, callback: (propertyDefinition: Definition<unknown, unknown>) => void, notProperty: () => void = () => {}): unknown
|
||||
{
|
||||
// Getting the current property definition.
|
||||
const propertyDefinition = this.getPropertyDefinition(propertyName);
|
||||
if (propertyDefinition)
|
||||
// There is a definition for the current property, calling the right callback.
|
||||
return callback(propertyDefinition);
|
||||
else
|
||||
// No definition for the given property, calling the right callback.
|
||||
return notProperty();
|
||||
}
|
||||
/**
|
||||
* Calling a function for each defined property.
|
||||
* @param callback - The function to call.
|
||||
* @protected
|
||||
*/
|
||||
protected forEachModelProperty(callback: (propertyName: string, propertyDefinition: Definition<unknown, unknown>) => unknown): any|void
|
||||
{
|
||||
for (const propertyName of this.getProperties())
|
||||
{ // For each property, checking that its type is defined and calling the callback with its type.
|
||||
const result = this.propertyWithDefinition(propertyName, (propertyDefinition) => {
|
||||
// If the property is defined, calling the function with the property name and definition.
|
||||
const result = callback(propertyName, propertyDefinition);
|
||||
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
if (typeof result !== "undefined") return result;
|
||||
});
|
||||
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
if (typeof result !== "undefined") return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The original properties values.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalProperties: Record<string, any> = {};
|
||||
|
||||
/**
|
||||
* The original (serialized) object.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalObject: any = null;
|
||||
|
||||
/**
|
||||
* Determine if the model is new or not.
|
||||
*/
|
||||
isNew(): boolean
|
||||
{
|
||||
return !this._originalObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model is dirty or not.
|
||||
*/
|
||||
isDirty(): boolean
|
||||
{
|
||||
return this.forEachModelProperty((propertyName, propertyDefinition) => (
|
||||
// For each property, checking if it is different.
|
||||
propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName])
|
||||
// There is a difference, we should return false.
|
||||
? true
|
||||
// There is no difference, returning nothing.
|
||||
: undefined
|
||||
)) === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model identifier.
|
||||
*/
|
||||
getIdentifier(): unknown
|
||||
{
|
||||
return (this as any)[this.SIdentifier()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current properties values as original values.
|
||||
*/
|
||||
resetDiff()
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each property, set its original value to its current property value.
|
||||
this._originalProperties[propertyName] = (this as any)[propertyName];
|
||||
propertyDefinition.type.resetDiff((this as any)[propertyName]);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Serialize the difference between current model state and original one.
|
||||
*/
|
||||
serializeDiff(): any
|
||||
{
|
||||
// Creating a serialized object.
|
||||
const serializedDiff: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object if it has changed.
|
||||
if (this.SIdentifier() == propertyName
|
||||
|| propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName]))
|
||||
// Adding the current property to the serialized object if it is the identifier or its value has changed.
|
||||
serializedDiff[propertyName] = propertyDefinition.type.serializeDiff((this as any)[propertyName]);
|
||||
})
|
||||
|
||||
return serializedDiff; // Returning the serialized object.
|
||||
}
|
||||
/**
|
||||
* Get difference between original values and current ones, then reset it.
|
||||
* Similar to call `serializeDiff()` then `resetDiff()`.
|
||||
*/
|
||||
save(): any
|
||||
{
|
||||
// Get the difference.
|
||||
const diff = this.serializeDiff();
|
||||
|
||||
// Once the difference has been gotten, reset it.
|
||||
this.resetDiff();
|
||||
|
||||
return diff; // Return the difference.
|
||||
}
|
||||
|
||||
getIdentifier(): IdentifierType;
|
||||
|
||||
/**
|
||||
* Serialize the model.
|
||||
*/
|
||||
serialize(): any
|
||||
{
|
||||
// Creating a serialized object.
|
||||
const serializedObject: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object.
|
||||
serializedObject[propertyName] = propertyDefinition.type.serialize((this as any)[propertyName]);
|
||||
});
|
||||
|
||||
return serializedObject; // Returning the serialized object.
|
||||
}
|
||||
|
||||
/**
|
||||
* Special operations on parse.
|
||||
* @protected
|
||||
*/
|
||||
protected parse(): void
|
||||
{} // Nothing by default. TODO: create an event system to create functions like "beforeDeserialization" or "afterDeserialization".
|
||||
|
||||
serialize(): SerializedModel<Shape>;
|
||||
/**
|
||||
* Deserialize the model.
|
||||
* @param obj Serialized object.
|
||||
*/
|
||||
deserialize(serializedObject: any): THIS
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, assigning its deserialized value to the model.
|
||||
(this as any)[propertyName] = propertyDefinition.type.deserialize(serializedObject[propertyName]);
|
||||
});
|
||||
deserialize(obj: SerializedModel<Shape>): Model<Shape, IdentifierType>;
|
||||
|
||||
// Reset original property values.
|
||||
this.resetDiff();
|
||||
/**
|
||||
* Find out if the model is new (never deserialized) or not.
|
||||
*/
|
||||
isNew(): boolean;
|
||||
/**
|
||||
* Find out if the model is dirty or not.
|
||||
*/
|
||||
isDirty(): boolean;
|
||||
|
||||
this._originalObject = serializedObject; // The model is not a new one, but loaded from a deserialized one.
|
||||
|
||||
return this as unknown as THIS; // Returning this, after deserialization.
|
||||
}
|
||||
/**
|
||||
* Serialize the difference between current model state and the original one.
|
||||
*/
|
||||
serializeDiff(): Partial<SerializedModel<Shape>>;
|
||||
/**
|
||||
* Set current properties values as original values.
|
||||
*/
|
||||
resetDiff(): void;
|
||||
/**
|
||||
* Get difference between original values and current ones, then reset it.
|
||||
* Similar to call `serializeDiff()` then `resetDiff()`.
|
||||
*/
|
||||
save(): Partial<SerializedModel<Shape>>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a Sharkitek model.
|
||||
* @param shape Model shape definition.
|
||||
* @param identifier Identifier property name.
|
||||
*/
|
||||
export function model<Shape extends ModelShape, Identifier extends keyof Shape = any>(
|
||||
shape: Shape,
|
||||
identifier?: Identifier,
|
||||
): ConstructorOf<Model<Shape, IdentifierType<Shape, Identifier>>>
|
||||
{
|
||||
// Get shape entries.
|
||||
const shapeEntries = Object.entries(shape) as [keyof Shape, UnknownDefinition][];
|
||||
|
||||
return class GenericModel implements ModelDefinition<Shape, IdentifierType<Shape, Identifier>>
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
// Initialize properties to undefined.
|
||||
Object.assign(this,
|
||||
// Build empty properties model from shape entries.
|
||||
Object.fromEntries(shapeEntries.map(([key]) => [key, undefined])) as PropertiesModel<Shape>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calling a function for each defined property.
|
||||
* @param callback - The function to call.
|
||||
* @protected
|
||||
*/
|
||||
protected forEachModelProperty<ReturnType>(callback: (propertyName: keyof Shape, propertyDefinition: UnknownDefinition) => ReturnType): ReturnType
|
||||
{
|
||||
for (const [propertyName, propertyDefinition] of shapeEntries)
|
||||
{ // For each property, checking that its type is defined and calling the callback with its type.
|
||||
// If the property is defined, calling the function with the property name and definition.
|
||||
const result = callback(propertyName, propertyDefinition);
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
if (typeof result !== "undefined") return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The original properties values.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalProperties: Partial<PropertiesModel<Shape>> = {};
|
||||
|
||||
/**
|
||||
* The original (serialized) object.
|
||||
* @protected
|
||||
*/
|
||||
protected _originalObject: SerializedModel<Shape>|null = null;
|
||||
|
||||
|
||||
|
||||
getIdentifier(): IdentifierType<Shape, Identifier>
|
||||
{
|
||||
return (this as PropertiesModel<Shape>)?.[identifier];
|
||||
}
|
||||
|
||||
serialize(): SerializedModel<Shape>
|
||||
{
|
||||
// Creating an empty (=> partial) serialized object.
|
||||
const serializedObject: Partial<SerializedModel<Shape>> = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object.
|
||||
serializedObject[propertyName] = propertyDefinition.type.serialize((this as PropertiesModel<Shape>)?.[propertyName]);
|
||||
});
|
||||
|
||||
return serializedObject as SerializedModel<Shape>; // Returning the serialized object.
|
||||
}
|
||||
|
||||
deserialize(obj: SerializedModel<Shape>): Model<Shape, IdentifierType<Shape, Identifier>>
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, assigning its deserialized value.
|
||||
(this as PropertiesModel<Shape>)[propertyName] = propertyDefinition.type.deserialize(obj[propertyName]);
|
||||
});
|
||||
|
||||
// Reset original property values.
|
||||
this.resetDiff();
|
||||
|
||||
this._originalObject = obj; // The model is not a new one, but loaded from a deserialized one. Storing it.
|
||||
|
||||
return this as Model<Shape, IdentifierType<Shape, Identifier>>;
|
||||
}
|
||||
|
||||
|
||||
isNew(): boolean
|
||||
{
|
||||
return !this._originalObject;
|
||||
}
|
||||
|
||||
isDirty(): boolean
|
||||
{
|
||||
return this.forEachModelProperty((propertyName, propertyDefinition) => (
|
||||
// For each property, checking if it is different.
|
||||
propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as PropertiesModel<Shape>)[propertyName])
|
||||
// There is a difference, we should return false.
|
||||
? true
|
||||
// There is no difference, returning nothing.
|
||||
: undefined
|
||||
)) === true;
|
||||
}
|
||||
|
||||
|
||||
serializeDiff(): Partial<SerializedModel<Shape>>
|
||||
{
|
||||
// Creating an empty (=> partial) serialized object.
|
||||
const serializedObject: Partial<SerializedModel<Shape>> = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object if it has changed or if it is the identifier.
|
||||
if (
|
||||
identifier == propertyName ||
|
||||
propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as PropertiesModel<Shape>)[propertyName])
|
||||
) // Adding the current property to the serialized object if it is the identifier or its value has changed.
|
||||
serializedObject[propertyName] = propertyDefinition.type.serializeDiff((this as PropertiesModel<Shape>)?.[propertyName]);
|
||||
});
|
||||
|
||||
return serializedObject; // Returning the serialized object.
|
||||
}
|
||||
|
||||
resetDiff(): void
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each property, set its original value to its current property value.
|
||||
this._originalProperties[propertyName] = (this as PropertiesModel<Shape>)[propertyName];
|
||||
propertyDefinition.type.resetDiff((this as PropertiesModel<Shape>)[propertyName]);
|
||||
});
|
||||
}
|
||||
|
||||
save(): Partial<SerializedModel<Shape>>
|
||||
{
|
||||
// Get the difference.
|
||||
const diff = this.serializeDiff();
|
||||
|
||||
// Once the difference has been obtained, reset it.
|
||||
this.resetDiff();
|
||||
|
||||
return diff; // Return the difference.
|
||||
}
|
||||
|
||||
} as unknown as ConstructorOf<Model<Shape, IdentifierType<Shape, Identifier>>>;
|
||||
}
|
||||
|
|
10
src/Model/Properties.ts
Normal file
10
src/Model/Properties.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
export {define} from "./PropertyDefinition";
|
||||
|
||||
export {array} from "./Types/ArrayType";
|
||||
export {bool, boolean} from "./Types/BoolType";
|
||||
export {date} from "./Types/DateType";
|
||||
export {decimal} from "./Types/DecimalType";
|
||||
export {model} from "./Types/ModelType";
|
||||
export {numeric} from "./Types/NumericType";
|
||||
export {object} from "./Types/ObjectType";
|
||||
export {string} from "./Types/StringType";
|
26
src/Model/PropertyDefinition.ts
Normal file
26
src/Model/PropertyDefinition.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {Type} from "./Types/Type";
|
||||
|
||||
/**
|
||||
* Property definition class.
|
||||
*/
|
||||
export class Definition<SerializedType, ModelType>
|
||||
{
|
||||
readonly _sharkitek: ModelType;
|
||||
readonly _serialized: SerializedType;
|
||||
|
||||
/**
|
||||
* Create a property definer instance.
|
||||
* @param type Property type.
|
||||
*/
|
||||
constructor(public readonly type: Type<SerializedType, ModelType>)
|
||||
{}
|
||||
}
|
||||
|
||||
/**
|
||||
* New definition of a property of the given type.
|
||||
* @param type Type of the property to define.
|
||||
*/
|
||||
export function define<SerializedType, ModelType>(type: Type<SerializedType, ModelType>): Definition<SerializedType, ModelType>
|
||||
{
|
||||
return new Definition(type);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of an array of values.
|
||||
|
@ -6,60 +7,60 @@ import {Type} from "./Type";
|
|||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
|
||||
{
|
||||
/**
|
||||
* Constructs a new array type of a Sharkitek model property.
|
||||
* @param valueType - Type of the array values.
|
||||
* Initialize a new array type of a Sharkitek model property.
|
||||
* @param valueDefinition Definition the array values.
|
||||
*/
|
||||
constructor(protected valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||
constructor(protected valueDefinition: Definition<SerializedValueType, SharkitekValueType>)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
serialize(value: SharkitekValueType[]): SerializedValueType[]
|
||||
serialize(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value.map((value) => (
|
||||
// Serializing each value of the array.
|
||||
this.valueType.serialize(value)
|
||||
this.valueDefinition.type.serialize(value)
|
||||
));
|
||||
}
|
||||
|
||||
deserialize(value: SerializedValueType[]): SharkitekValueType[]
|
||||
deserialize(value: SerializedValueType[]|null|undefined): SharkitekValueType[]|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value.map((serializedValue) => (
|
||||
// Deserializing each value of the array.
|
||||
this.valueType.deserialize(serializedValue)
|
||||
this.valueDefinition.type.deserialize(serializedValue)
|
||||
));
|
||||
}
|
||||
|
||||
serializeDiff(value: SharkitekValueType[]): any
|
||||
serializeDiff(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
// Serializing diff of all elements.
|
||||
return value.map((value) => this.valueType.serializeDiff(value));
|
||||
return value.map((value) => this.valueDefinition.type.serializeDiff(value) as SerializedValueType);
|
||||
}
|
||||
|
||||
resetDiff(value: SharkitekValueType[]): void
|
||||
resetDiff(value: SharkitekValueType[]|null|undefined): void
|
||||
{
|
||||
// Do nothing if it is not an array.
|
||||
if (!Array.isArray(value)) return;
|
||||
|
||||
// Reset diff of all elements.
|
||||
value.forEach((value) => this.valueType.resetDiff(value));
|
||||
value.forEach((value) => this.valueDefinition.type.resetDiff(value));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of an array of values.
|
||||
* @param valueType - Type of the array values.
|
||||
* New array property definition.
|
||||
* @param valueDefinition Array values type definition.
|
||||
*/
|
||||
export function SArray<SerializedValueType, SharkitekValueType>(valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||
export function array<SerializedValueType, SharkitekValueType>(valueDefinition: Definition<SerializedValueType, SharkitekValueType>): Definition<SerializedValueType[], SharkitekValueType[]>
|
||||
{
|
||||
return new ArrayType<SerializedValueType, SharkitekValueType>(valueType);
|
||||
return define(new ArrayType(valueDefinition));
|
||||
}
|
||||
|
|
|
@ -1,22 +1,42 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of any boolean value.
|
||||
*/
|
||||
export class BoolType extends Type<boolean, boolean>
|
||||
{
|
||||
deserialize(value: boolean): boolean
|
||||
deserialize(value: boolean|null|undefined): boolean|null|undefined
|
||||
{
|
||||
// Keep NULL and undefined values.
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return !!value; // ensure bool type.
|
||||
}
|
||||
|
||||
serialize(value: boolean): boolean
|
||||
serialize(value: boolean|null|undefined): boolean|null|undefined
|
||||
{
|
||||
// Keep NULL and undefined values.
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return !!value; // ensure bool type.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any boolean value.
|
||||
* New boolean property definition.
|
||||
*/
|
||||
export const SBool = new BoolType();
|
||||
export function bool(): Definition<boolean, boolean>
|
||||
{
|
||||
return define(new BoolType());
|
||||
}
|
||||
/**
|
||||
* New boolean property definition.
|
||||
* Alias of bool.
|
||||
*/
|
||||
export function boolean(): ReturnType<typeof bool>
|
||||
{
|
||||
return bool();
|
||||
}
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of dates.
|
||||
*/
|
||||
export class DateType extends Type<string, Date>
|
||||
{
|
||||
deserialize(value: string): Date
|
||||
deserialize(value: string|null|undefined): Date|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return new Date(value);
|
||||
}
|
||||
|
||||
serialize(value: Date): string
|
||||
serialize(value: Date|null|undefined): string|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value?.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of dates.
|
||||
* New date property definition.
|
||||
*/
|
||||
export const SDate = new DateType();
|
||||
export function date(): Definition<string, Date>
|
||||
{
|
||||
return define(new DateType());
|
||||
}
|
||||
|
|
|
@ -1,22 +1,32 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of decimal numbers.
|
||||
*/
|
||||
export class DecimalType extends Type<string, number>
|
||||
{
|
||||
deserialize(value: string): number
|
||||
deserialize(value: string|null|undefined): number|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return parseFloat(value);
|
||||
}
|
||||
|
||||
serialize(value: number): string
|
||||
serialize(value: number|null|undefined): string|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value?.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of decimal numbers.
|
||||
* New decimal property definition.
|
||||
*/
|
||||
export const SDecimal = new DecimalType();
|
||||
export function decimal(): Definition<string, number>
|
||||
{
|
||||
return define(new DecimalType());
|
||||
}
|
||||
|
|
|
@ -1,44 +1,49 @@
|
|||
import {Type} from "./Type";
|
||||
import {Model} from "../Model";
|
||||
|
||||
/**
|
||||
* Type definition of the constructor of a specific type.
|
||||
*/
|
||||
export type ConstructorOf<T> = { new(): T; }
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
import {ConstructorOf, Model, ModelShape, SerializedModel} from "../Model";
|
||||
|
||||
/**
|
||||
* Type of a Sharkitek model value.
|
||||
*/
|
||||
export class ModelType<M extends Model<M>> extends Type<any, M>
|
||||
export class ModelType<Shape extends ModelShape> extends Type<SerializedModel<Shape>, Model<Shape>>
|
||||
{
|
||||
/**
|
||||
* Constructs a new model type of a Sharkitek model property.
|
||||
* @param modelConstructor - Constructor of the model.
|
||||
* Initialize a new model type of a Sharkitek model property.
|
||||
* @param modelConstructor Model constructor.
|
||||
*/
|
||||
constructor(protected modelConstructor: ConstructorOf<M>)
|
||||
constructor(protected modelConstructor: ConstructorOf<Model<Shape>>)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
serialize(value: M|null): any
|
||||
serialize(value: Model<Shape>|null|undefined): SerializedModel<Shape>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
// Serializing the given model.
|
||||
return value ? value.serialize() : null;
|
||||
return value?.serialize();
|
||||
}
|
||||
|
||||
deserialize(value: any): M|null
|
||||
deserialize(value: SerializedModel<Shape>|null|undefined): Model<Shape>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
// Deserializing the given object in the new model.
|
||||
return value ? (new this.modelConstructor()).deserialize(value) : null;
|
||||
return (new this.modelConstructor()).deserialize(value) as Model<Shape>;
|
||||
}
|
||||
|
||||
serializeDiff(value: M): any
|
||||
serializeDiff(value: Model<Shape>|null|undefined): Partial<SerializedModel<Shape>>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
// Serializing the given model.
|
||||
return value ? value.serializeDiff() : null;
|
||||
return value?.serializeDiff();
|
||||
}
|
||||
|
||||
resetDiff(value: M): void
|
||||
resetDiff(value: Model<Shape>|null|undefined): void
|
||||
{
|
||||
// Reset diff of the given model.
|
||||
value?.resetDiff();
|
||||
|
@ -46,10 +51,10 @@ export class ModelType<M extends Model<M>> extends Type<any, M>
|
|||
}
|
||||
|
||||
/**
|
||||
* Type of a Sharkitek model value.
|
||||
* @param modelConstructor - Constructor of the model.
|
||||
* New model property definition.
|
||||
* @param modelConstructor Model constructor.
|
||||
*/
|
||||
export function SModel<M extends Model<M>>(modelConstructor: ConstructorOf<M>)
|
||||
export function model<Shape extends ModelShape>(modelConstructor: ConstructorOf<Model<Shape>>): Definition<SerializedModel<Shape>, Model<Shape>>
|
||||
{
|
||||
return new ModelType(modelConstructor);
|
||||
return define(new ModelType(modelConstructor));
|
||||
}
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of any numeric value.
|
||||
*/
|
||||
export class NumericType extends Type<number, number>
|
||||
{
|
||||
deserialize(value: number): number
|
||||
deserialize(value: number|null|undefined): number|null|undefined
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
serialize(value: number): number
|
||||
serialize(value: number|null|undefined): number|null|undefined
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any numeric value.
|
||||
* New numeric property definition.
|
||||
*/
|
||||
export const SNumeric = new NumericType();
|
||||
export function numeric(): Definition<number, number>
|
||||
{
|
||||
return define(new NumericType());
|
||||
}
|
||||
|
|
|
@ -1,66 +1,67 @@
|
|||
import {Type} from "./Type";
|
||||
import {Definition} from "../Definition";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
import {ModelShape, PropertiesModel, SerializedModel, UnknownDefinition} from "../Model";
|
||||
|
||||
/**
|
||||
* Type of a simple object.
|
||||
* Type of a custom object.
|
||||
*/
|
||||
export class ObjectType<Keys extends symbol|string> extends Type<Record<Keys, any>, Record<Keys, any>>
|
||||
export class ObjectType<Shape extends ModelShape> extends Type<SerializedModel<Shape>, PropertiesModel<Shape>>
|
||||
{
|
||||
/**
|
||||
* Constructs a new object type of a Sharkitek model property.
|
||||
* @param fieldsTypes Object fields types.
|
||||
* Initialize a new object type of a Sharkitek model property.
|
||||
* @param shape
|
||||
*/
|
||||
constructor(protected fieldsTypes: Record<Keys, Definition<unknown, unknown>>)
|
||||
constructor(protected readonly shape: Shape)
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
deserialize(value: Record<Keys, any>): Record<Keys, any>
|
||||
deserialize(value: SerializedModel<Shape>|null|undefined): PropertiesModel<Shape>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return Object.fromEntries(
|
||||
// For each defined field, deserialize its value according to its type.
|
||||
(Object.entries(this.fieldsTypes) as [Keys, Definition<any, any>][]).map(([fieldName, fieldDefinition]) => (
|
||||
(Object.entries(this.shape) as [keyof Shape, UnknownDefinition][]).map(([fieldName, fieldDefinition]) => (
|
||||
// Return an entry with the current field name and the deserialized value.
|
||||
[fieldName, fieldDefinition.type.deserialize(value?.[fieldName])]
|
||||
))
|
||||
) as Record<Keys, any>;
|
||||
) as PropertiesModel<Shape>;
|
||||
}
|
||||
|
||||
serialize(value: Record<Keys, any>): Record<Keys, any>
|
||||
serialize(value: PropertiesModel<Shape>|null|undefined): SerializedModel<Shape>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return Object.fromEntries(
|
||||
// For each defined field, serialize its value according to its type.
|
||||
(Object.entries(this.fieldsTypes) as [Keys, Definition<any, any>][]).map(([fieldName, fieldDefinition]) => (
|
||||
(Object.entries(this.shape) as [keyof Shape, UnknownDefinition][]).map(([fieldName, fieldDefinition]) => (
|
||||
// Return an entry with the current field name and the serialized value.
|
||||
[fieldName, fieldDefinition.type.serialize(value?.[fieldName])]
|
||||
))
|
||||
) as Record<Keys, any>;
|
||||
) as PropertiesModel<Shape>;
|
||||
}
|
||||
|
||||
serializeDiff(value: Record<Keys, any>): Record<Keys, any>
|
||||
serializeDiff(value: PropertiesModel<Shape>|null|undefined): Partial<SerializedModel<Shape>>|null|undefined
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return Object.fromEntries(
|
||||
// For each defined field, serialize its diff value according to its type.
|
||||
(Object.entries(this.fieldsTypes) as [Keys, Definition<any, any>][]).map(([fieldName, fieldDefinition]) => (
|
||||
(Object.entries(this.shape) as [keyof Shape, UnknownDefinition][]).map(([fieldName, fieldDefinition]) => (
|
||||
// Return an entry with the current field name and the serialized diff value.
|
||||
[fieldName, fieldDefinition.type.serializeDiff(value?.[fieldName])]
|
||||
))
|
||||
) as Record<Keys, any>;
|
||||
) as PropertiesModel<Shape>;
|
||||
}
|
||||
|
||||
resetDiff(value: Record<Keys, any>): void
|
||||
resetDiff(value: PropertiesModel<Shape>|null|undefined)
|
||||
{
|
||||
// For each field, reset its diff.
|
||||
(Object.entries(this.fieldsTypes) as [Keys, Definition<any, any>][]).forEach(([fieldName, fieldDefinition]) => {
|
||||
(Object.entries(this.shape) as [keyof Shape, UnknownDefinition][]).forEach(([fieldName, fieldDefinition]) => {
|
||||
// Reset diff of the current field.
|
||||
fieldDefinition.type.resetDiff(value?.[fieldName]);
|
||||
});
|
||||
|
@ -68,10 +69,10 @@ export class ObjectType<Keys extends symbol|string> extends Type<Record<Keys, an
|
|||
}
|
||||
|
||||
/**
|
||||
* Type of a simple object.
|
||||
* @param fieldsTypes Object fields types.
|
||||
* New object property definition.
|
||||
* @param shape Shape of the object.
|
||||
*/
|
||||
export function SObject<Keys extends symbol|string>(fieldsTypes: Record<Keys, Definition<unknown, unknown>>): ObjectType<Keys>
|
||||
export function object<Shape extends ModelShape>(shape: Shape): Definition<SerializedModel<Shape>, PropertiesModel<Shape>>
|
||||
{
|
||||
return new ObjectType<Keys>(fieldsTypes);
|
||||
return define(new ObjectType(shape));
|
||||
}
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
import {Type} from "./Type";
|
||||
import {define, Definition} from "../PropertyDefinition";
|
||||
|
||||
/**
|
||||
* Type of any string value.
|
||||
*/
|
||||
export class StringType extends Type<string, string>
|
||||
{
|
||||
deserialize(value: string): string
|
||||
deserialize(value: string|null|undefined): string|null|undefined
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
serialize(value: string): string
|
||||
serialize(value: string|null|undefined): string|null|undefined
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any string value.
|
||||
* New string property definition.
|
||||
*/
|
||||
export const SString = new StringType();
|
||||
export function string(): Definition<string, string>
|
||||
{
|
||||
return define(new StringType());
|
||||
}
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
/**
|
||||
* Abstract class of a Sharkitek model property type.
|
||||
*/
|
||||
export abstract class Type<SerializedType, SharkitekType>
|
||||
export abstract class Type<SerializedType, ModelType>
|
||||
{
|
||||
/**
|
||||
* Serialize the given value of a Sharkitek model property.
|
||||
* @param value - Value to serialize.
|
||||
* @param value Value to serialize.
|
||||
*/
|
||||
abstract serialize(value: SharkitekType): SerializedType;
|
||||
abstract serialize(value: ModelType|null|undefined): SerializedType|null|undefined;
|
||||
|
||||
/**
|
||||
* Deserialize the given value of a serialized Sharkitek model.
|
||||
* @param value - Value to deserialize.
|
||||
*/
|
||||
abstract deserialize(value: SerializedType): SharkitekType;
|
||||
abstract deserialize(value: SerializedType|null|undefined): ModelType|null|undefined;
|
||||
|
||||
/**
|
||||
* Serialize the given value only if it has changed.
|
||||
* @param value - Value to deserialize.
|
||||
*/
|
||||
serializeDiff(value: SharkitekType): SerializedType|null
|
||||
serializeDiff(value: ModelType|null|undefined): Partial<SerializedType>|null|undefined
|
||||
{
|
||||
return this.serialize(value); // By default, nothing changes.
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export abstract class Type<SerializedType, SharkitekType>
|
|||
* Reset the difference between the original value and the current one.
|
||||
* @param value - Value for which reset diff data.
|
||||
*/
|
||||
resetDiff(value: SharkitekType): void
|
||||
resetDiff(value: ModelType|null|undefined): void
|
||||
{
|
||||
// By default, nothing to do.
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ export abstract class Type<SerializedType, SharkitekType>
|
|||
* @param originalValue - Original property value.
|
||||
* @param currentValue - Current property value.
|
||||
*/
|
||||
propertyHasChanged(originalValue: SharkitekType, currentValue: SharkitekType): boolean
|
||||
propertyHasChanged(originalValue: ModelType|null|undefined, currentValue: ModelType|null|undefined): boolean
|
||||
{
|
||||
return originalValue != currentValue;
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export abstract class Type<SerializedType, SharkitekType>
|
|||
* @param originalValue - Original serialized property value.
|
||||
* @param currentValue - Current serialized property value.
|
||||
*/
|
||||
serializedPropertyHasChanged(originalValue: SerializedType, currentValue: SerializedType): boolean
|
||||
serializedPropertyHasChanged(originalValue: SerializedType|null|undefined, currentValue: SerializedType|null|undefined): boolean
|
||||
{
|
||||
return originalValue != currentValue;
|
||||
}
|
||||
|
|
14
src/Model/index.ts
Normal file
14
src/Model/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as property from "./Properties";
|
||||
export { property };
|
||||
|
||||
export * from "./Model";
|
||||
export {Definition} from "./PropertyDefinition";
|
||||
|
||||
export {ArrayType} from "./Types/ArrayType";
|
||||
export {BoolType} from "./Types/BoolType";
|
||||
export {DateType} from "./Types/DateType";
|
||||
export {DecimalType} from "./Types/DecimalType";
|
||||
export {ModelType} from "./Types/ModelType";
|
||||
export {NumericType} from "./Types/NumericType";
|
||||
export {ObjectType} from "./Types/ObjectType";
|
||||
export {StringType} from "./Types/StringType";
|
18
src/index.ts
18
src/index.ts
|
@ -1,14 +1,4 @@
|
|||
|
||||
export * from "./Model/Model";
|
||||
|
||||
export * from "./Model/Definition";
|
||||
|
||||
export * from "./Model/Types/Type";
|
||||
export * from "./Model/Types/ArrayType";
|
||||
export * from "./Model/Types/BoolType";
|
||||
export * from "./Model/Types/DateType";
|
||||
export * from "./Model/Types/DecimalType";
|
||||
export * from "./Model/Types/ModelType";
|
||||
export * from "./Model/Types/NumericType";
|
||||
export * from "./Model/Types/ObjectType";
|
||||
export * from "./Model/Types/StringType";
|
||||
import * as s from "./Model";
|
||||
export * from "./Model";
|
||||
export { s };
|
||||
export default s;
|
||||
|
|
|
@ -1,39 +1,18 @@
|
|||
import {
|
||||
SArray,
|
||||
SDecimal,
|
||||
SModel,
|
||||
SNumeric,
|
||||
SString,
|
||||
SDate,
|
||||
SBool,
|
||||
Model,
|
||||
ModelDefinition,
|
||||
SDefine, ModelIdentifier
|
||||
} from "../src";
|
||||
import {SObject} from "../src/Model/Types/ObjectType";
|
||||
import {s} from "../src";
|
||||
|
||||
/**
|
||||
* Another test model.
|
||||
*/
|
||||
class Author extends Model<Author>
|
||||
class Author extends s.model({
|
||||
name: s.property.string(),
|
||||
firstName: s.property.string(),
|
||||
email: s.property.string(),
|
||||
createdAt: s.property.date(),
|
||||
active: s.property.bool(),
|
||||
})
|
||||
{
|
||||
name: string;
|
||||
firstName: string;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
active: boolean = true;
|
||||
|
||||
protected SDefinition(): ModelDefinition<Author>
|
||||
{
|
||||
return {
|
||||
name: SDefine(SString),
|
||||
firstName: SDefine(SString),
|
||||
email: SDefine(SString),
|
||||
createdAt: SDefine(SDate),
|
||||
active: SDefine(SBool),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(name: string = "", firstName: string = "", email: string = "", createdAt: Date = new Date())
|
||||
{
|
||||
super();
|
||||
|
@ -48,7 +27,18 @@ class Author extends Model<Author>
|
|||
/**
|
||||
* A test model.
|
||||
*/
|
||||
class Article extends Model<Article>
|
||||
class Article extends s.model({
|
||||
id: s.property.numeric(),
|
||||
title: s.property.string(),
|
||||
authors: s.property.array(s.property.model(Author)),
|
||||
text: s.property.string(),
|
||||
evaluation: s.property.decimal(),
|
||||
tags: s.property.array(
|
||||
s.property.object({
|
||||
name: s.property.string(),
|
||||
})
|
||||
),
|
||||
}, "id")
|
||||
{
|
||||
id: number;
|
||||
title: string;
|
||||
|
@ -58,27 +48,6 @@ class Article extends Model<Article>
|
|||
tags: {
|
||||
name: string;
|
||||
}[];
|
||||
|
||||
protected SIdentifier(): ModelIdentifier<Article>
|
||||
{
|
||||
return "id";
|
||||
}
|
||||
|
||||
protected SDefinition(): ModelDefinition<Article>
|
||||
{
|
||||
return {
|
||||
id: SDefine(SNumeric),
|
||||
title: SDefine(SString),
|
||||
authors: SDefine(SArray(SModel(Author))),
|
||||
text: SDefine(SString),
|
||||
evaluation: SDefine(SDecimal),
|
||||
tags: SDefine(SArray(
|
||||
SObject({
|
||||
name: SDefine(SString),
|
||||
})
|
||||
)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
it("deserialize", () => {
|
||||
|
@ -140,8 +109,8 @@ it("deserialize then save", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: new Date(), active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: new Date(), active: false, },
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date()).toISOString(), active: false, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
@ -167,8 +136,8 @@ it("save with modified submodels", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: new Date(), active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: new Date(), active: false, },
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date()).toISOString(), active: false, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
|
Loading…
Reference in a new issue