Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
3e291d6bd5 | |||
72df9f6453 | |||
e43e27e2e1 | |||
498d25a909 |
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
Copyright (c) 2024 Zeptotech
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
139
README.md
139
README.md
@ -1,25 +1,20 @@
|
||||
# Sharkitek Core
|
||||
|
||||
![Version 3.0.0](https://img.shields.io/badge/version-3.0.0-blue)
|
||||
|
||||
## Introduction
|
||||
|
||||
Sharkitek is a Javascript / TypeScript library designed to ease development of client-side models.
|
||||
|
||||
With Sharkitek, you define the architecture of your models by specifying their properties and their types.
|
||||
Then, you can use the defined methods like `serialize`, `deserialize` or `serializeDiff`.
|
||||
Then, you can use the defined methods like `serialize`, `deserialize`, `save` or `serializeDiff`.
|
||||
|
||||
```typescript
|
||||
class Example extends Model<Example>
|
||||
class Example extends s.model({
|
||||
id: s.property.numeric(),
|
||||
name: s.property.string(),
|
||||
})
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
protected SDefinition(): ModelDefinition<Example>
|
||||
{
|
||||
return {
|
||||
id: SDefine(SNumeric),
|
||||
name: SDefine(SString),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -31,30 +26,16 @@ class Example extends Model<Example>
|
||||
/**
|
||||
* A person.
|
||||
*/
|
||||
class Person extends Model<Person>
|
||||
class Person extends s.model({
|
||||
id: s.property.numeric(),
|
||||
name: s.property.string(),
|
||||
firstName: s.property.string(),
|
||||
email: s.property.string(),
|
||||
createdAt: s.property.date(),
|
||||
active: s.property.boolean(),
|
||||
}, "id")
|
||||
{
|
||||
id: number;
|
||||
name: string;
|
||||
firstName: string;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
active: boolean = true;
|
||||
|
||||
protected SIdentifier(): ModelIdentifier<Person>
|
||||
{
|
||||
return "id";
|
||||
}
|
||||
|
||||
protected SDefinition(): ModelDefinition<Person>
|
||||
{
|
||||
return {
|
||||
name: SDefine(SString),
|
||||
firstName: SDefine(SString),
|
||||
email: SDefine(SString),
|
||||
createdAt: SDefine(SDate),
|
||||
active: SDefine(SBool),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -62,29 +43,27 @@ class Person extends Model<Person>
|
||||
/**
|
||||
* An article.
|
||||
*/
|
||||
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;
|
||||
authors: Author[] = [];
|
||||
text: string;
|
||||
evaluation: number;
|
||||
|
||||
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: {
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
```
|
||||
|
||||
@ -102,53 +81,42 @@ Sharkitek defines some basic types by default, in these classes:
|
||||
- `DecimalType`: number in the model, formatted string in the serialized object.
|
||||
- `DateType`: date in the model, ISO formatted date in the serialized object.
|
||||
- `ArrayType`: array in the model, array in the serialized object.
|
||||
- `ObjectType`: object in the model, object in the serialized object.
|
||||
- `ModelType`: instance of a specific class in the model, object in the serialized object.
|
||||
|
||||
When you are defining a Sharkitek property, you must provide its type by instantiating one of these classes.
|
||||
When you are defining a property of a Sharkitek model, you must provide its type by instantiating one of these classes.
|
||||
|
||||
```typescript
|
||||
class Example extends Model<Example>
|
||||
class Example extends s.model({
|
||||
foo: s.property.define(new StringType()),
|
||||
})
|
||||
{
|
||||
foo: string;
|
||||
|
||||
protected SDefinition(): ModelDefinition<Example>
|
||||
{
|
||||
return {
|
||||
foo: new Definition(new StringType()),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
To ease the use of these classes and reduce read complexity, some constant variables and functions are defined in the library,
|
||||
following a certain naming convention: "S{type_name}".
|
||||
To ease the use of these classes and reduce read complexity,
|
||||
properties of each type are easily definable with a function for each type.
|
||||
|
||||
- `BoolType` => `SBool`
|
||||
- `StringType` => `SString`
|
||||
- `NumericType` => `SNumeric`
|
||||
- `DecimalType` => `SDecimal`
|
||||
- `DateType` => `SDate`
|
||||
- `ArrayType` => `SArray`
|
||||
- `ModelType` => `SModel`
|
||||
- `BoolType` => `s.property.boolean`
|
||||
- `StringType` => `s.property.string`
|
||||
- `NumericType` => `s.property.numeric`
|
||||
- `DecimalType` => `s.property.decimal`
|
||||
- `DateType` => `s.property.date`
|
||||
- `ArrayType` => `s.property.array`
|
||||
- `ObjectType` => `s.property.object`
|
||||
- `ModelType` => `s.property.model`
|
||||
|
||||
When the types require parameters, the constant is defined as a function. If there is no parameter, then a simple
|
||||
variable is enough.
|
||||
|
||||
Type implementers should provide a corresponding variable or function for each defined type. They can even provide
|
||||
multiple functions or constants when predefined parameters. (For example, we could define `SStringArray` which would
|
||||
be a variable similar to `SArray(SString)`.)
|
||||
Type implementers should provide a corresponding function for each defined type. They can even provide
|
||||
multiple functions or constants with predefined parameters.
|
||||
(For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.)
|
||||
|
||||
```typescript
|
||||
class Example extends Model<Example>
|
||||
class Example extends s.model({
|
||||
foo: s.property.string(),
|
||||
})
|
||||
{
|
||||
foo: string = undefined;
|
||||
|
||||
protected SDefinition(): ModelDefinition<Example>
|
||||
{
|
||||
return {
|
||||
foo: SDefine(SString),
|
||||
};
|
||||
}
|
||||
foo: string;
|
||||
}
|
||||
```
|
||||
|
||||
@ -208,7 +176,6 @@ const result = model.serializeDiff();
|
||||
// result = { id: 5, title: "A new title for a new world" }
|
||||
// if `id` is not defined as the model identifier:
|
||||
// result = { title: "A new title for a new world" }
|
||||
|
||||
```
|
||||
|
||||
#### `resetDiff()`
|
||||
@ -238,7 +205,6 @@ const result = model.serializeDiff();
|
||||
// result = { id: 5 }
|
||||
// if `id` is not defined as the model identifier:
|
||||
// result = {}
|
||||
|
||||
```
|
||||
|
||||
#### `save()`
|
||||
@ -265,5 +231,4 @@ const result = model.save();
|
||||
// result = { id: 5, title: "A new title for a new world" }
|
||||
// if `id` is not defined as the model identifier:
|
||||
// result = { title: "A new title for a new world" }
|
||||
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@sharkitek/core",
|
||||
"version": "2.1.2",
|
||||
"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
|
||||
{
|
||||
return value.toISOString();
|
||||
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",
|
||||
|
Reference in New Issue
Block a user