Compare commits
15 Commits
Author | SHA1 | Date |
---|---|---|
Madeorsk | 13c852cd52 | |
Madeorsk | 5c465ed7ec | |
Madeorsk | 1c3c87a4a6 | |
Madeorsk | 13072b453f | |
Madeorsk | dfecfd141c | |
Madeorsk | fb118bd584 | |
Madeorsk | cfbf74911b | |
Madeorsk | c1b8e8adb1 | |
Madeorsk | 9f202bdd9d | |
Madeorsk | 6f62b2d053 | |
Madeorsk | d25585cc89 | |
Madeorsk | c6cdfc75ea | |
Madeorsk | 3ce2b74900 | |
Madeorsk | 2441c4e8d3 | |
Madeorsk | f7a200ecc7 |
|
@ -12,5 +12,6 @@ lib/
|
|||
.yarnrc*
|
||||
yarn-error.log
|
||||
.pnp*
|
||||
node_modules/
|
||||
|
||||
yarn.lock
|
||||
|
|
114
README.md
114
README.md
|
@ -4,21 +4,22 @@
|
|||
|
||||
Sharkitek is a Javascript / TypeScript library designed to ease development of client-side models.
|
||||
|
||||
With Sharkitek, you define the architecture of your models by applying decorators (which define their type) on your class properties.
|
||||
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`.
|
||||
|
||||
Sharkitek makes use of decorators as defined in the [TypeScript Reference](https://www.typescriptlang.org/docs/handbook/decorators.html).
|
||||
Due to the way decorators work, you must always set a value to your properties when you declare them, even if this value is `undefined`.
|
||||
|
||||
```typescript
|
||||
class Example extends Model
|
||||
class Example extends Model<Example>
|
||||
{
|
||||
@Property(SNumeric)
|
||||
@Identifier
|
||||
id: number = undefined;
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@Property(SString)
|
||||
name: string = undefined;
|
||||
protected SDefinition(): ModelDefinition<Example>
|
||||
{
|
||||
return {
|
||||
id: SDefine(SNumeric),
|
||||
name: SDefine(SString),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -30,47 +31,60 @@ class Example extends Model
|
|||
/**
|
||||
* A person.
|
||||
*/
|
||||
class Person extends Model
|
||||
class Person extends Model<Person>
|
||||
{
|
||||
@Property(SNumeric)
|
||||
@Identifier
|
||||
id: number = undefined;
|
||||
id: number;
|
||||
name: string;
|
||||
firstName: string;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
active: boolean = true;
|
||||
|
||||
@Property(SString)
|
||||
name: string = undefined;
|
||||
protected SIdentifier(): ModelIdentifier<Person>
|
||||
{
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Property(SString)
|
||||
firstName: string = undefined;
|
||||
|
||||
@Property(SString)
|
||||
email: string = undefined;
|
||||
protected SDefinition(): ModelDefinition<Person>
|
||||
{
|
||||
return {
|
||||
name: SDefine(SString),
|
||||
firstName: SDefine(SString),
|
||||
email: SDefine(SString),
|
||||
createdAt: SDefine(SDate),
|
||||
active: SDefine(SBool),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important**: You _must_ set a value to all your defined properties. If there is no set value, the decorator will not
|
||||
be applied instantly on object initialization and the deserialization will not work properly.
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* An article.
|
||||
*/
|
||||
class Article extends Model
|
||||
class Article extends Model<Article>
|
||||
{
|
||||
@Property(SNumeric)
|
||||
@Identifier
|
||||
id: number = undefined;
|
||||
|
||||
@Property(SString)
|
||||
title: string = undefined;
|
||||
|
||||
@Property(SArray(SModel(Author)))
|
||||
id: number;
|
||||
title: string;
|
||||
authors: Author[] = [];
|
||||
text: string;
|
||||
evaluation: number;
|
||||
|
||||
@Property(SString)
|
||||
text: string = undefined;
|
||||
protected SIdentifier(): ModelIdentifier<Article>
|
||||
{
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Property(SDecimal)
|
||||
evaluation: number = undefined;
|
||||
protected SDefinition(): ModelDefinition<Article>
|
||||
{
|
||||
return {
|
||||
id: SDefine(SNumeric),
|
||||
title: SDefine(SString),
|
||||
authors: SDefine(SArray(SModel(Author))),
|
||||
text: SDefine(SString),
|
||||
evaluation: SDefine(SDecimal),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -82,28 +96,38 @@ Types are defined by a class extending `Type`.
|
|||
|
||||
Sharkitek defines some basic types by default, in these classes:
|
||||
|
||||
- `BoolType`: boolean value in the model, boolean value in the serialized object.
|
||||
- `StringType`: string in the model, string in the serialized object.
|
||||
- `NumericType`: number in the model, number in the serialized object.
|
||||
- `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.
|
||||
- `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.
|
||||
|
||||
```typescript
|
||||
class Example extends Model
|
||||
class Example extends Model<Example>
|
||||
{
|
||||
@Property(new StringType())
|
||||
foo: string = undefined;
|
||||
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}".
|
||||
|
||||
- `BoolType` => `SBool`
|
||||
- `StringType` => `SString`
|
||||
- `NumericType` => `SNumeric`
|
||||
- `DecimalType` => `SDecimal`
|
||||
- `DateType` => `SDate`
|
||||
- `ArrayType` => `SArray`
|
||||
- `ModelType` => `SModel`
|
||||
|
||||
|
@ -115,10 +139,16 @@ multiple functions or constants when predefined parameters. (For example, we cou
|
|||
be a variable similar to `SArray(SString)`.)
|
||||
|
||||
```typescript
|
||||
class Example extends Model
|
||||
class Example extends Model<Example>
|
||||
{
|
||||
@Property(SString)
|
||||
foo: string = undefined;
|
||||
|
||||
protected SDefinition(): ModelDefinition<Example>
|
||||
{
|
||||
return {
|
||||
foo: SDefine(SString),
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import {build} from "esbuild";
|
||||
|
||||
/**
|
||||
* Build the library.
|
||||
* @param devMode - Dev mode.
|
||||
*/
|
||||
function buildLibrary(devMode: boolean = false): void
|
||||
{
|
||||
// Compilation de l'application.
|
||||
build({
|
||||
entryPoints: ["src/index.ts"],
|
||||
outfile: "lib/index.js",
|
||||
bundle: true,
|
||||
minify: true,
|
||||
sourcemap: true,
|
||||
format: "esm",
|
||||
loader: {
|
||||
".ts": "ts",
|
||||
},
|
||||
watch: devMode ? {
|
||||
// Affichage suite à une recompilation.
|
||||
onRebuild(error, result) {
|
||||
console.log(new Date());
|
||||
if (!error && result.errors.length == 0)
|
||||
console.log("Successfully built.");
|
||||
else
|
||||
console.error("Error!");
|
||||
}
|
||||
} : false,
|
||||
})
|
||||
// Fonction lancée pour une compilation réussie.
|
||||
.then(() => { console.log(new Date()); console.log("Success."); })
|
||||
// Fonction lancée pour une compilation échouée.
|
||||
.catch((e) => console.error(e.message));
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
buildLibrary(process.argv?.[2] == "dev");
|
29
package.json
29
package.json
|
@ -1,33 +1,38 @@
|
|||
{
|
||||
"name": "@sharkitek/core",
|
||||
"version": "1.0.1",
|
||||
"version": "2.0.1",
|
||||
"description": "Sharkitek core models library.",
|
||||
"keywords": [
|
||||
"sharkitek",
|
||||
"model",
|
||||
"serialization",
|
||||
"diff",
|
||||
"dirty",
|
||||
"deserialization",
|
||||
"property"
|
||||
],
|
||||
"repository": "https://git.madeorsk.com/Sharkitek/core",
|
||||
"author": "Madeorsk <madeorsk@protonmail.com>",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"build": "parcel build",
|
||||
"dev": "parcel watch",
|
||||
"test": "jest"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"source": "src/index.ts",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"files": [
|
||||
"lib/**/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"reflect-metadata": "^0.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/packager-ts": "2.6.2",
|
||||
"@parcel/transformer-typescript-types": "2.6.2",
|
||||
"@parcel/packager-ts": "2.7.0",
|
||||
"@parcel/transformer-typescript-types": "2.7.0",
|
||||
"@types/jest": "^28.1.6",
|
||||
"esbuild": "^0.15.8",
|
||||
"jest": "^28.1.3",
|
||||
"parcel": "^2.6.2",
|
||||
"parcel": "^2.7.0",
|
||||
"ts-jest": "^28.0.7",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.7.4"
|
||||
},
|
||||
"packageManager": "yarn@3.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
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> = {})
|
||||
{
|
||||
return new Definition(type, options);
|
||||
}
|
|
@ -1,89 +1,50 @@
|
|||
import {Type} from "./Types/Type";
|
||||
import "reflect-metadata";
|
||||
import {ConstructorOf} from "./Types/ModelType";
|
||||
import {Definition} from "./Definition";
|
||||
|
||||
/**
|
||||
* Key of Sharkitek property metadata.
|
||||
* Model properties definition type.
|
||||
*/
|
||||
const sharkitekMetadataKey = Symbol("sharkitek");
|
||||
|
||||
export type ModelDefinition<T> = Partial<Record<keyof T, Definition<unknown, unknown>>>;
|
||||
/**
|
||||
* Key of Sharkitek model identifier.
|
||||
* Model identifier type.
|
||||
*/
|
||||
const modelIdentifierMetadataKey = Symbol("modelIdentifier");
|
||||
|
||||
/**
|
||||
* Sharkitek property metadata interface.
|
||||
*/
|
||||
interface SharkitekMetadataInterface
|
||||
{
|
||||
/**
|
||||
* Property type instance.
|
||||
*/
|
||||
type: Type<any, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class decorator for Sharkitek models.
|
||||
*/
|
||||
export function Sharkitek(constructor: Function)
|
||||
{
|
||||
/*return class extends (constructor as FunctionConstructor) {
|
||||
constructor()
|
||||
{
|
||||
super();
|
||||
}
|
||||
};*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Property decorator to define a Sharkitek model identifier.
|
||||
*/
|
||||
export function Identifier(obj: Model, propertyName: string): void
|
||||
{
|
||||
// Register the current property as identifier of the current model object.
|
||||
Reflect.defineMetadata(modelIdentifierMetadataKey, propertyName, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Property decorator for Sharkitek models properties.
|
||||
* @param type - Type of the property.
|
||||
*/
|
||||
export function Property<SerializedType, SharkitekType>(type: Type<SerializedType, SharkitekType>): PropertyDecorator
|
||||
{
|
||||
// Return the decorator function.
|
||||
return (obj: ConstructorOf<Model>, propertyName) => {
|
||||
// Initializing property metadata.
|
||||
const metadata: SharkitekMetadataInterface = {
|
||||
type: type,
|
||||
};
|
||||
// Set property metadata.
|
||||
Reflect.defineMetadata(sharkitekMetadataKey, metadata, obj, propertyName);
|
||||
};
|
||||
}
|
||||
export type ModelIdentifier<T> = keyof T;
|
||||
|
||||
/**
|
||||
* A Sharkitek model.
|
||||
*/
|
||||
export abstract class Model
|
||||
export abstract class Model<THIS>
|
||||
{
|
||||
/**
|
||||
* Get the Sharkitek model identifier.
|
||||
* @private
|
||||
* Model properties definition function.
|
||||
*/
|
||||
private getModelIdentifier(): string
|
||||
{
|
||||
return Reflect.getMetadata(modelIdentifierMetadataKey, this);
|
||||
}
|
||||
protected abstract SDefinition(): ModelDefinition<THIS>;
|
||||
|
||||
/**
|
||||
* Get the Sharkitek metadata of the property.
|
||||
* @param propertyName - The name of the property for which to get metadata.
|
||||
* @private
|
||||
* Return the name of the model identifier property.
|
||||
*/
|
||||
private getPropertyMetadata(propertyName: string): SharkitekMetadataInterface
|
||||
protected SIdentifier(): ModelIdentifier<THIS>
|
||||
{
|
||||
return Reflect.getMetadata(sharkitekMetadataKey, this, propertyName);
|
||||
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.
|
||||
|
@ -91,15 +52,15 @@ export abstract class Model
|
|||
* @param notProperty - The function called when the property is not defined.
|
||||
* @protected
|
||||
*/
|
||||
protected propertyWithMetadata(propertyName: string, callback: (propertyMetadata: SharkitekMetadataInterface) => void, notProperty: () => void = () => {}): unknown
|
||||
protected propertyWithDefinition(propertyName: string, callback: (propertyDefinition: Definition<unknown, unknown>) => void, notProperty: () => void = () => {}): unknown
|
||||
{
|
||||
// Getting the current property metadata.
|
||||
const propertyMetadata = this.getPropertyMetadata(propertyName);
|
||||
if (propertyMetadata)
|
||||
// Metadata are defined, calling the right callback.
|
||||
return callback(propertyMetadata);
|
||||
// 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
|
||||
// Metadata are not defined, calling the right callback.
|
||||
// No definition for the given property, calling the right callback.
|
||||
return notProperty();
|
||||
}
|
||||
/**
|
||||
|
@ -107,19 +68,16 @@ export abstract class Model
|
|||
* @param callback - The function to call.
|
||||
* @protected
|
||||
*/
|
||||
protected forEachModelProperty(callback: (propertyName: string, propertyMetadata: SharkitekMetadataInterface) => unknown): any|void
|
||||
protected forEachModelProperty(callback: (propertyName: string, propertyDefinition: Definition<unknown, unknown>) => unknown): any|void
|
||||
{
|
||||
for (const propertyName of Object.keys(this))
|
||||
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.propertyWithMetadata(propertyName, (propertyMetadata) => {
|
||||
// If the property is defined, calling the function with the property name and metadata.
|
||||
const result = callback(propertyName, propertyMetadata);
|
||||
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;
|
||||
|
||||
// Update metadata if they have changed.
|
||||
Reflect.defineMetadata(sharkitekMetadataKey, propertyMetadata, this, propertyName);
|
||||
});
|
||||
|
||||
// If there is a return value, returning it directly (loop is broken).
|
||||
|
@ -153,12 +111,12 @@ export abstract class Model
|
|||
*/
|
||||
isDirty(): boolean
|
||||
{
|
||||
return this.forEachModelProperty((propertyName, propertyMetadata) => (
|
||||
return this.forEachModelProperty((propertyName, propertyDefinition) => (
|
||||
// For each property, checking if it is different.
|
||||
propertyMetadata.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName])
|
||||
propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName])
|
||||
// There is a difference, we should return false.
|
||||
? true
|
||||
// There is not difference, returning nothing.
|
||||
// There is no difference, returning nothing.
|
||||
: undefined
|
||||
)) === true;
|
||||
}
|
||||
|
@ -168,7 +126,7 @@ export abstract class Model
|
|||
*/
|
||||
getIdentifier(): unknown
|
||||
{
|
||||
return (this as any)[this.getModelIdentifier()];
|
||||
return (this as any)[this.SIdentifier()];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -176,10 +134,10 @@ export abstract class Model
|
|||
*/
|
||||
resetDiff()
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each property, set its original value to its current property value.
|
||||
this._originalProperties[propertyName] = (this as any)[propertyName];
|
||||
propertyMetadata.type.resetDiff((this as any)[propertyName]);
|
||||
propertyDefinition.type.resetDiff((this as any)[propertyName]);
|
||||
});
|
||||
}
|
||||
/**
|
||||
|
@ -190,12 +148,12 @@ export abstract class Model
|
|||
// Creating a serialized object.
|
||||
const serializedDiff: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object if it has changed.
|
||||
if (this.getModelIdentifier() == propertyName
|
||||
|| propertyMetadata.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName]))
|
||||
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] = propertyMetadata.type.serializeDiff((this as any)[propertyName]);
|
||||
serializedDiff[propertyName] = propertyDefinition.type.serializeDiff((this as any)[propertyName]);
|
||||
})
|
||||
|
||||
return serializedDiff; // Returning the serialized object.
|
||||
|
@ -224,9 +182,9 @@ export abstract class Model
|
|||
// Creating a serialized object.
|
||||
const serializedObject: any = {};
|
||||
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, adding it to the serialized object.
|
||||
serializedObject[propertyName] = propertyMetadata.type.serialize((this as any)[propertyName]);
|
||||
serializedObject[propertyName] = propertyDefinition.type.serialize((this as any)[propertyName]);
|
||||
});
|
||||
|
||||
return serializedObject; // Returning the serialized object.
|
||||
|
@ -237,16 +195,16 @@ export abstract class Model
|
|||
* @protected
|
||||
*/
|
||||
protected parse(): void
|
||||
{} // Nothing by default. TODO: create a event system to create functions like "beforeDeserialization" or "afterDeserialization".
|
||||
{} // Nothing by default. TODO: create an event system to create functions like "beforeDeserialization" or "afterDeserialization".
|
||||
|
||||
/**
|
||||
* Deserialize the model.
|
||||
*/
|
||||
deserialize(serializedObject: any): this
|
||||
deserialize(serializedObject: any): THIS
|
||||
{
|
||||
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
||||
// For each defined model property, assigning its deserialized value to the model.
|
||||
(this as any)[propertyName] = propertyMetadata.type.deserialize(serializedObject[propertyName]);
|
||||
(this as any)[propertyName] = propertyDefinition.type.deserialize(serializedObject[propertyName]);
|
||||
});
|
||||
|
||||
// Reset original property values.
|
||||
|
@ -254,6 +212,6 @@ export abstract class Model
|
|||
|
||||
this._originalObject = serializedObject; // The model is not a new one, but loaded from a deserialized one.
|
||||
|
||||
return this; // Returning this, after deserialization.
|
||||
return this as unknown as THIS; // Returning this, after deserialization.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,9 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<Ser
|
|||
|
||||
serialize(value: SharkitekValueType[]): SerializedValueType[]
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value.map((value) => (
|
||||
// Serializing each value of the array.
|
||||
this.valueType.serialize(value)
|
||||
|
@ -24,6 +27,9 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<Ser
|
|||
|
||||
deserialize(value: SerializedValueType[]): SharkitekValueType[]
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
return value.map((serializedValue) => (
|
||||
// Deserializing each value of the array.
|
||||
this.valueType.deserialize(serializedValue)
|
||||
|
@ -32,12 +38,18 @@ export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<Ser
|
|||
|
||||
serializeDiff(value: SharkitekValueType[]): any
|
||||
{
|
||||
if (value === undefined) return undefined;
|
||||
if (value === null) return null;
|
||||
|
||||
// Serializing diff of all elements.
|
||||
return value.map((value) => this.valueType.serializeDiff(value));
|
||||
}
|
||||
|
||||
resetDiff(value: SharkitekValueType[]): 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));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of any boolean value.
|
||||
*/
|
||||
export class BoolType extends Type<boolean, boolean>
|
||||
{
|
||||
deserialize(value: boolean): boolean
|
||||
{
|
||||
return !!value; // ensure bool type.
|
||||
}
|
||||
|
||||
serialize(value: boolean): boolean
|
||||
{
|
||||
return !!value; // ensure bool type.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of any boolean value.
|
||||
*/
|
||||
export const SBool = new BoolType();
|
|
@ -0,0 +1,22 @@
|
|||
import {Type} from "./Type";
|
||||
|
||||
/**
|
||||
* Type of dates.
|
||||
*/
|
||||
export class DateType extends Type<string, Date>
|
||||
{
|
||||
deserialize(value: string): Date
|
||||
{
|
||||
return new Date(value);
|
||||
}
|
||||
|
||||
serialize(value: Date): string
|
||||
{
|
||||
return value.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of dates.
|
||||
*/
|
||||
export const SDate = new DateType();
|
|
@ -17,6 +17,6 @@ export class DecimalType extends Type<string, number>
|
|||
}
|
||||
|
||||
/**
|
||||
* Type of decimal numbers;
|
||||
* Type of decimal numbers.
|
||||
*/
|
||||
export const SDecimal = new DecimalType();
|
||||
|
|
|
@ -9,7 +9,7 @@ export type ConstructorOf<T> = { new(): T; }
|
|||
/**
|
||||
* Type of a Sharkitek model value.
|
||||
*/
|
||||
export class ModelType<M extends Model> extends Type<any, M>
|
||||
export class ModelType<M extends Model<M>> extends Type<any, M>
|
||||
{
|
||||
/**
|
||||
* Constructs a new model type of a Sharkitek model property.
|
||||
|
@ -20,28 +20,28 @@ export class ModelType<M extends Model> extends Type<any, M>
|
|||
super();
|
||||
}
|
||||
|
||||
serialize(value: M): any
|
||||
serialize(value: M|null): any
|
||||
{
|
||||
// Serializing the given model.
|
||||
return value.serialize();
|
||||
return value ? value.serialize() : null;
|
||||
}
|
||||
|
||||
deserialize(value: any): M
|
||||
deserialize(value: any): M|null
|
||||
{
|
||||
// Deserializing the given object in the new model.
|
||||
return (new this.modelConstructor()).deserialize(value);
|
||||
return value ? (new this.modelConstructor()).deserialize(value) : null;
|
||||
}
|
||||
|
||||
serializeDiff(value: M): any
|
||||
{
|
||||
// Serializing the given model.
|
||||
return value.serializeDiff();
|
||||
return value ? value.serializeDiff() : null;
|
||||
}
|
||||
|
||||
resetDiff(value: M): void
|
||||
{
|
||||
// Reset diff of the given model.
|
||||
value.resetDiff();
|
||||
value?.resetDiff();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ export class ModelType<M extends Model> extends Type<any, M>
|
|||
* Type of a Sharkitek model value.
|
||||
* @param modelConstructor - Constructor of the model.
|
||||
*/
|
||||
export function SModel<M extends Model>(modelConstructor: ConstructorOf<M>)
|
||||
export function SModel<M extends Model<M>>(modelConstructor: ConstructorOf<M>)
|
||||
{
|
||||
return new ModelType(modelConstructor);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,12 @@
|
|||
|
||||
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";
|
||||
|
|
|
@ -1,49 +1,75 @@
|
|||
import {SArray, SDecimal, SModel, SNumeric, SString, Identifier, Model, Property} from "../src";
|
||||
import {
|
||||
SArray,
|
||||
SDecimal,
|
||||
SModel,
|
||||
SNumeric,
|
||||
SString,
|
||||
SDate,
|
||||
SBool,
|
||||
Model,
|
||||
ModelDefinition,
|
||||
SDefine, ModelIdentifier
|
||||
} from "../src";
|
||||
|
||||
/**
|
||||
* Another test model.
|
||||
*/
|
||||
class Author extends Model
|
||||
class Author extends Model<Author>
|
||||
{
|
||||
@Property(SString)
|
||||
name: string = undefined;
|
||||
name: string;
|
||||
firstName: string;
|
||||
email: string;
|
||||
createdAt: Date;
|
||||
active: boolean = true;
|
||||
|
||||
@Property(SString)
|
||||
firstName: string = undefined;
|
||||
protected SDefinition(): ModelDefinition<Author>
|
||||
{
|
||||
return {
|
||||
name: SDefine(SString),
|
||||
firstName: SDefine(SString),
|
||||
email: SDefine(SString),
|
||||
createdAt: SDefine(SDate),
|
||||
active: SDefine(SBool),
|
||||
};
|
||||
}
|
||||
|
||||
@Property(SString)
|
||||
email: string = undefined;
|
||||
|
||||
constructor(name: string = undefined, firstName: string = undefined, email: string = undefined)
|
||||
constructor(name: string = undefined, firstName: string = undefined, email: string = undefined, createdAt: Date = undefined)
|
||||
{
|
||||
super();
|
||||
|
||||
this.name = name;
|
||||
this.firstName = firstName;
|
||||
this.email = email;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A test model.
|
||||
*/
|
||||
class Article extends Model
|
||||
class Article extends Model<Article>
|
||||
{
|
||||
@Property(SNumeric)
|
||||
@Identifier
|
||||
id: number = undefined;
|
||||
|
||||
@Property(SString)
|
||||
title: string = undefined;
|
||||
|
||||
@Property(SArray(SModel(Author)))
|
||||
id: number;
|
||||
title: string;
|
||||
authors: Author[] = [];
|
||||
text: string;
|
||||
evaluation: number;
|
||||
|
||||
@Property(SString)
|
||||
text: string = undefined;
|
||||
protected SIdentifier(): ModelIdentifier<Article>
|
||||
{
|
||||
return "id";
|
||||
}
|
||||
|
||||
@Property(SDecimal)
|
||||
evaluation: number = undefined;
|
||||
protected SDefinition(): ModelDefinition<Article>
|
||||
{
|
||||
return {
|
||||
id: SDefine(SNumeric),
|
||||
title: SDefine(SString),
|
||||
authors: SDefine(SArray(SModel(Author))),
|
||||
text: SDefine(SString),
|
||||
evaluation: SDefine(SDecimal),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
it("deserialize", () => {
|
||||
|
@ -51,8 +77,8 @@ it("deserialize", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
@ -60,8 +86,8 @@ it("deserialize", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
@ -69,11 +95,12 @@ it("deserialize", () => {
|
|||
});
|
||||
|
||||
it("create and check state then serialize", () => {
|
||||
const now = new Date();
|
||||
const article = new Article();
|
||||
article.id = 1;
|
||||
article.title = "this is a test";
|
||||
article.authors = [
|
||||
new Author("DOE", "John", "test@test.test"),
|
||||
new Author("DOE", "John", "test@test.test", now),
|
||||
];
|
||||
article.text = "this is a long test.";
|
||||
article.evaluation = 25.23;
|
||||
|
@ -85,7 +112,7 @@ it("create and check state then serialize", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: now.toISOString(), active: true, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
@ -98,8 +125,8 @@ it("deserialize then save", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
{ 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, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
@ -124,8 +151,8 @@ it("save with modified submodels", () => {
|
|||
id: 1,
|
||||
title: "this is a test",
|
||||
authors: [
|
||||
{ name: "DOE", firstName: "John", email: "test@test.test" },
|
||||
{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||
{ 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, },
|
||||
],
|
||||
text: "this is a long test.",
|
||||
evaluation: "25.23",
|
||||
|
|
|
@ -1,17 +1,21 @@
|
|||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS"
|
||||
}
|
||||
},
|
||||
|
||||
"files": ["src/index.ts"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib/",
|
||||
"noImplicitAny": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"module": "ES6",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES5",
|
||||
"target": "ES6",
|
||||
|
||||
"lib": [
|
||||
"ESNext",
|
||||
|
|
Loading…
Reference in New Issue