Compare commits
No commits in common. "main" and "v1.1.0" have entirely different histories.
23 changed files with 475 additions and 928 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -12,6 +12,5 @@ lib/
|
||||||
.yarnrc*
|
.yarnrc*
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.pnp*
|
.pnp*
|
||||||
node_modules/
|
|
||||||
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2024 Zeptotech
|
Copyright (c) <year> <copyright holders>
|
||||||
|
|
||||||
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:
|
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:
|
||||||
|
|
||||||
|
|
157
README.md
157
README.md
|
@ -1,40 +1,24 @@
|
||||||
<p align="center">
|
# Sharkitek Core
|
||||||
<a href="https://code.zeptotech.net/Sharkitek/Core">
|
|
||||||
<picture>
|
|
||||||
<img alt="Sharkitek logo" width="200" src="https://code.zeptotech.net/Sharkitek/Core/raw/branch/main/logo.svg" />
|
|
||||||
</picture>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<h1 align="center">
|
|
||||||
Sharkitek
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<h4 align="center">
|
|
||||||
<a href="https://code.zeptotech.net/Sharkitek/Core">Documentation</a> |
|
|
||||||
<a href="https://code.zeptotech.net/Sharkitek/Core">Website</a>
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
TypeScript library for well-designed model architectures
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<img alt="Version 3.3.0" src="https://img.shields.io/badge/version-3.3.0-blue" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Sharkitek is a Javascript / TypeScript library designed to ease development of client-side models.
|
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.
|
With Sharkitek, you define the architecture of your models by applying decorators (which define their type) on your class properties.
|
||||||
Then, you can use the defined methods like `serialize`, `deserialize`, `patch` or `serializeDiff`.
|
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
|
```typescript
|
||||||
class Example extends s.model({
|
class Example extends Model
|
||||||
id: s.property.numeric(),
|
|
||||||
name: s.property.string(),
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
|
@Property(SNumeric)
|
||||||
|
@Identifier
|
||||||
|
id: number = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
name: string = undefined;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -46,44 +30,50 @@ class Example extends s.model({
|
||||||
/**
|
/**
|
||||||
* A person.
|
* A person.
|
||||||
*/
|
*/
|
||||||
class Person extends s.model({
|
class Person extends 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")
|
|
||||||
{
|
{
|
||||||
active: boolean = true;
|
@Property(SNumeric)
|
||||||
|
@Identifier
|
||||||
|
id: number = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
name: string = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
firstName: string = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
email: string = undefined;
|
||||||
|
|
||||||
|
@Property(SDate)
|
||||||
|
createdAt: Date = undefined;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**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
|
```typescript
|
||||||
/**
|
/**
|
||||||
* An article.
|
* An article.
|
||||||
*/
|
*/
|
||||||
class Article extends s.model({
|
class Article extends 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;
|
@Property(SNumeric)
|
||||||
title: string;
|
@Identifier
|
||||||
|
id: number = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
title: string = undefined;
|
||||||
|
|
||||||
|
@Property(SArray(SModel(Author)))
|
||||||
authors: Author[] = [];
|
authors: Author[] = [];
|
||||||
text: string;
|
|
||||||
evaluation: number;
|
@Property(SString)
|
||||||
tags: {
|
text: string = undefined;
|
||||||
name: string;
|
|
||||||
}[];
|
@Property(SDecimal)
|
||||||
|
evaluation: number = undefined;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -95,48 +85,44 @@ Types are defined by a class extending `Type`.
|
||||||
|
|
||||||
Sharkitek defines some basic types by default, in these classes:
|
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.
|
- `StringType`: string in the model, string in the serialized object.
|
||||||
- `NumericType`: number in the model, number 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.
|
- `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.
|
- `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.
|
- `ModelType`: instance of a specific class in the model, object in the serialized object.
|
||||||
|
|
||||||
When you are defining a property of a Sharkitek model, you must provide its type by instantiating one of these classes.
|
When you are defining a Sharkitek property, you must provide its type by instantiating one of these classes.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Example extends s.model({
|
class Example extends Model
|
||||||
foo: s.property.define(new StringType()),
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
foo: string;
|
@Property(new StringType())
|
||||||
|
foo: string = undefined;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To ease the use of these classes and reduce read complexity,
|
To ease the use of these classes and reduce read complexity, some constant variables and functions are defined in the library,
|
||||||
properties of each type are easily definable with a function for each type.
|
following a certain naming convention: "S{type_name}".
|
||||||
|
|
||||||
- `BoolType` => `s.property.boolean`
|
- `StringType` => `SString`
|
||||||
- `StringType` => `s.property.string`
|
- `NumericType` => `SNumeric`
|
||||||
- `NumericType` => `s.property.numeric`
|
- `DecimalType` => `SDecimal`
|
||||||
- `DecimalType` => `s.property.decimal`
|
- `DateType` => `SDate`
|
||||||
- `DateType` => `s.property.date`
|
- `ArrayType` => `SArray`
|
||||||
- `ArrayType` => `s.property.array`
|
- `ModelType` => `SModel`
|
||||||
- `ObjectType` => `s.property.object`
|
|
||||||
- `ModelType` => `s.property.model`
|
|
||||||
|
|
||||||
Type implementers should provide a corresponding function for each defined type. They can even provide
|
When the types require parameters, the constant is defined as a function. If there is no parameter, then a simple
|
||||||
multiple functions or constants with predefined parameters.
|
variable is enough.
|
||||||
(For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.)
|
|
||||||
|
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)`.)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
class Example extends s.model({
|
class Example extends Model
|
||||||
foo: s.property.string(),
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
foo: string;
|
@Property(SString)
|
||||||
|
foo: string = undefined;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -196,6 +182,7 @@ const result = model.serializeDiff();
|
||||||
// result = { id: 5, title: "A new title for a new world" }
|
// result = { id: 5, title: "A new title for a new world" }
|
||||||
// if `id` is not defined as the model identifier:
|
// if `id` is not defined as the model identifier:
|
||||||
// result = { title: "A new title for a new world" }
|
// result = { title: "A new title for a new world" }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `resetDiff()`
|
#### `resetDiff()`
|
||||||
|
@ -225,9 +212,10 @@ const result = model.serializeDiff();
|
||||||
// result = { id: 5 }
|
// result = { id: 5 }
|
||||||
// if `id` is not defined as the model identifier:
|
// if `id` is not defined as the model identifier:
|
||||||
// result = {}
|
// result = {}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `patch()`
|
#### `save()`
|
||||||
|
|
||||||
Get difference between original values and current ones, then reset it.
|
Get difference between original values and current ones, then reset it.
|
||||||
Similar to call `serializeDiff()` then `resetDiff()`.
|
Similar to call `serializeDiff()` then `resetDiff()`.
|
||||||
|
@ -246,9 +234,10 @@ const model = (new TestModel()).deserialize({
|
||||||
|
|
||||||
model.title = "A new title for a new world";
|
model.title = "A new title for a new world";
|
||||||
|
|
||||||
const result = model.patch();
|
const result = model.save();
|
||||||
// if `id` is defined as the model identifier:
|
// if `id` is defined as the model identifier:
|
||||||
// result = { id: 5, title: "A new title for a new world" }
|
// result = { id: 5, title: "A new title for a new world" }
|
||||||
// if `id` is not defined as the model identifier:
|
// if `id` is not defined as the model identifier:
|
||||||
// result = { title: "A new title for a new world" }
|
// result = { title: "A new title for a new world" }
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
export default {
|
|
||||||
preset: "ts-jest",
|
preset: "ts-jest",
|
||||||
testEnvironment: "node",
|
testEnvironment: "node",
|
||||||
|
|
||||||
|
|
37
logo.svg
37
logo.svg
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
fill="#000000"
|
|
||||||
width="900"
|
|
||||||
height="900"
|
|
||||||
viewBox="0 0 36 36"
|
|
||||||
version="1.1"
|
|
||||||
id="svg1"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
|
||||||
id="defs1" />
|
|
||||||
<title
|
|
||||||
id="title1">shark</title>
|
|
||||||
<path
|
|
||||||
d="M 18.033615,1.4513338 C 10.403464,6.2983278 6.2910007,18.59117 6.2910007,34.205527 H 29.708999 c 0,-15.613237 -4.045235,-27.9071992 -11.675384,-32.7541932 z m -7.467688,15.9532972 1.284018,1.073376 -0.440332,-2.462712 c 0.5154,-0.767498 1.064412,-1.447601 1.641435,-2.029105 l 1.345642,1.913699 0.275626,-3.236931 c 0.657695,-0.416801 1.342279,-0.713715 2.045911,-0.877298 l 1.281776,2.534419 1.281775,-2.534419 c 0.69915,0.162462 1.378133,0.456016 2.032467,0.869455 l 0.28907,3.393792 1.406144,-2.001093 c 0.554614,0.568059 1.082339,1.226874 1.579811,1.96636 l -0.43921,2.462712 1.284016,-1.073375 c 1.706421,3.099118 2.943378,7.246962 3.466621,11.948299 -0.06683,3.889694 -21.7515603,2.320881 -21.8036319,-0.0011 0.5221219,-4.700217 1.7590797,-8.849181 3.4666199,-11.947179 z"
|
|
||||||
id="path2"
|
|
||||||
style="display:inline;fill:#1c4878;fill-opacity:1;stroke-width:1.12043" />
|
|
||||||
<path
|
|
||||||
d="M 18.030001,4.0195273 C 11.220001,8.3455277 6.2910007,20.269527 6.2910007,34.205527 H 29.708999 c 0,-13.935 -4.869,-25.8599993 -11.678998,-30.1859997 z m -6.664999,15.1909997 1.146,0.958 -0.393,-2.198 c 0.46,-0.685 0.949999,-1.292 1.465,-1.811 l 1.200997,1.708 0.246,-2.889 c 0.587,-0.372 1.198,-0.637 1.826,-0.783 l 1.144,2.262 1.144,-2.262 c 0.624,0.145 1.23,0.407 1.814,0.776 l 0.258,3.029 1.255,-1.786 c 0.495,0.507 0.966,1.095 1.41,1.755 l -0.392,2.198 1.146,-0.958 c 1.523,2.766 2.627,6.468 3.094,10.664 -0.626,-2.009 -1.659,-3.774 -2.975,-5.146 l 0.25,-2.235 -1.6,1.042 c -0.381,-0.283 -0.777,-0.537 -1.188,-0.759 l -0.208,-2.556 -1.641,1.801 c -0.456,-0.13 -0.924,-0.222 -1.401,-0.276 l -0.968,-2.074 -0.968,2.074 c -0.508,0.057 -1.006,0.159 -1.49,0.302 l -1.549999,-1.701 -0.197,2.425 c -0.415,0.224 -0.815,0.479 -1.199,0.765 l -1.6,-1.042 0.25,2.234 c -1.3159993,1.371 -2.3489993,3.136 -2.9749993,5.145 0.466,-4.195 1.57,-7.898 3.0939993,-10.663 z"
|
|
||||||
id="path1"
|
|
||||||
style="display:inline;fill:#3178c6;fill-opacity:1" />
|
|
||||||
<metadata
|
|
||||||
id="metadata1">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:title>shark</dc:title>
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.6 KiB |
55
package.json
55
package.json
|
@ -1,47 +1,34 @@
|
||||||
{
|
{
|
||||||
"name": "@sharkitek/core",
|
"name": "@sharkitek/core",
|
||||||
"version": "3.3.0",
|
"version": "1.1.0",
|
||||||
"description": "TypeScript library for well-designed model architectures.",
|
"description": "Sharkitek core models library.",
|
||||||
"keywords": [
|
"keywords": ["sharkitek", "model", "serialization", "diff", "dirty", "deserialization", "property"],
|
||||||
"deserialization",
|
"repository": "https://git.madeorsk.com/Sharkitek/core",
|
||||||
"diff",
|
"author": "Madeorsk <madeorsk@protonmail.com>",
|
||||||
"dirty",
|
|
||||||
"model",
|
|
||||||
"object",
|
|
||||||
"property",
|
|
||||||
"serialization",
|
|
||||||
"sharkitek",
|
|
||||||
"typescript"
|
|
||||||
],
|
|
||||||
"repository": "https://code.zeptotech.net/Sharkitek/Core",
|
|
||||||
"author": {
|
|
||||||
"name": "Madeorsk",
|
|
||||||
"email": "madeorsk@protonmail.com"
|
|
||||||
},
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"publishConfig": {
|
"private": false,
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "parcel build",
|
||||||
|
"dev": "parcel watch",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"main": "lib/index.js",
|
||||||
"source": "src/index.ts",
|
"source": "src/index.ts",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"main": "lib/index.js",
|
|
||||||
"files": [
|
"files": [
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"dependencies": {
|
||||||
"@types/jest": "^29.5.13",
|
"reflect-metadata": "^0.1.13"
|
||||||
"@types/node": "^22.7.4",
|
|
||||||
"jest": "^29.7.0",
|
|
||||||
"ts-jest": "^29.2.5",
|
|
||||||
"ts-node": "^10.9.2",
|
|
||||||
"typescript": "^5.6.2",
|
|
||||||
"vite": "^5.4.8",
|
|
||||||
"vite-plugin-dts": "^4.2.2"
|
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.0"
|
"devDependencies": {
|
||||||
|
"@parcel/packager-ts": "2.6.2",
|
||||||
|
"@parcel/transformer-typescript-types": "2.6.2",
|
||||||
|
"@types/jest": "^28.1.6",
|
||||||
|
"jest": "^28.1.3",
|
||||||
|
"parcel": "^2.6.2",
|
||||||
|
"ts-jest": "^28.0.7",
|
||||||
|
"typescript": "^4.7.4"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@3.2.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,150 +1,127 @@
|
||||||
import {Definition} from "./PropertyDefinition";
|
import {Type} from "./Types/Type";
|
||||||
|
import "reflect-metadata";
|
||||||
|
import {ConstructorOf} from "./Types/ModelType";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type definition of a model constructor.
|
* Key of Sharkitek property metadata.
|
||||||
*/
|
*/
|
||||||
export type ConstructorOf<T extends object> = { new(): T; };
|
const sharkitekMetadataKey = Symbol("sharkitek");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unknown property definition.
|
* Key of Sharkitek model identifier.
|
||||||
*/
|
*/
|
||||||
export type UnknownDefinition = Definition<unknown, unknown>;
|
const modelIdentifierMetadataKey = Symbol("modelIdentifier");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A model shape.
|
* Sharkitek property metadata interface.
|
||||||
*/
|
*/
|
||||||
export type ModelShape = Record<string, UnknownDefinition>;
|
interface SharkitekMetadataInterface
|
||||||
|
|
||||||
/**
|
|
||||||
* 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>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of the extends function of model classes.
|
|
||||||
*/
|
|
||||||
export type ExtendsFunctionType<ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>, Shape extends ModelShape, Identifier extends keyof Shape = any> =
|
|
||||||
<Extension extends object>(extension: ThisType<ModelType> & Extension) => ModelClass<ModelType & Extension, Shape, Identifier>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of a model class.
|
|
||||||
*/
|
|
||||||
export type ModelClass<ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>, Shape extends ModelShape, Identifier extends keyof Shape = any> = (
|
|
||||||
ConstructorOf<ModelType> & {
|
|
||||||
extends: ExtendsFunctionType<ModelType, Shape, Identifier>;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier type.
|
|
||||||
*/
|
|
||||||
export type IdentifierType<Shape extends ModelShape, K extends keyof Shape> = Shape[K]["_sharkitek"];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifier name type.
|
|
||||||
*/
|
|
||||||
export type IdentifierNameType<Shape> = Shape extends ModelShape ? keyof Shape : unknown;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface of a Sharkitek model definition.
|
|
||||||
*/
|
|
||||||
export interface ModelDefinition<Shape extends ModelShape, IdentifierType, ModelType extends Model<Shape, IdentifierType> = Model<Shape, IdentifierType>>
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Get model identifier.
|
* Property type instance.
|
||||||
*/
|
*/
|
||||||
getIdentifier(): IdentifierType;
|
type: Type<any, any>;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get model identifier name.
|
|
||||||
*/
|
|
||||||
getIdentifierName(): IdentifierNameType<Shape>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serialize the model.
|
|
||||||
*/
|
|
||||||
serialize(): SerializedModel<Shape>;
|
|
||||||
/**
|
|
||||||
* Deserialize the model.
|
|
||||||
* @param obj Serialized object.
|
|
||||||
*/
|
|
||||||
deserialize(obj: SerializedModel<Shape>): ModelType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find out if the model is new (never deserialized) or not.
|
|
||||||
*/
|
|
||||||
isNew(): boolean;
|
|
||||||
/**
|
|
||||||
* Find out if the model is dirty or not.
|
|
||||||
*/
|
|
||||||
isDirty(): boolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()`.
|
|
||||||
*/
|
|
||||||
patch(): Partial<SerializedModel<Shape>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a Sharkitek model.
|
* Class decorator for Sharkitek models.
|
||||||
* @param shape Model shape definition.
|
|
||||||
* @param identifier Identifier property name.
|
|
||||||
*/
|
*/
|
||||||
export function model<ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>, Shape extends ModelShape, Identifier extends IdentifierNameType<Shape> = any>(
|
export function Sharkitek(constructor: Function)
|
||||||
shape: Shape,
|
|
||||||
identifier?: Identifier,
|
|
||||||
): ModelClass<ModelType, Shape, Identifier>
|
|
||||||
{
|
|
||||||
// Get shape entries.
|
|
||||||
const shapeEntries = Object.entries(shape) as [keyof Shape, UnknownDefinition][];
|
|
||||||
|
|
||||||
return withExtends(
|
|
||||||
// Initialize generic model class.
|
|
||||||
class GenericModel implements ModelDefinition<Shape, IdentifierType<Shape, Identifier>, ModelType>
|
|
||||||
{
|
{
|
||||||
|
/*return class extends (constructor as FunctionConstructor) {
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
// Initialize properties to undefined.
|
super();
|
||||||
Object.assign(this,
|
}
|
||||||
// Build empty properties model from shape entries.
|
};*/
|
||||||
Object.fromEntries(shapeEntries.map(([key]) => [key, undefined])) as PropertiesModel<Shape>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Sharkitek model.
|
||||||
|
*/
|
||||||
|
export abstract class Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the Sharkitek model identifier.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private getModelIdentifier(): string
|
||||||
|
{
|
||||||
|
return Reflect.getMetadata(modelIdentifierMetadataKey, this);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the Sharkitek metadata of the property.
|
||||||
|
* @param propertyName - The name of the property for which to get metadata.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private getPropertyMetadata(propertyName: string): SharkitekMetadataInterface
|
||||||
|
{
|
||||||
|
return Reflect.getMetadata(sharkitekMetadataKey, this, propertyName);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 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 propertyWithMetadata(propertyName: string, callback: (propertyMetadata: SharkitekMetadataInterface) => 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);
|
||||||
|
else
|
||||||
|
// Metadata are not defined, calling the right callback.
|
||||||
|
return notProperty();
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Calling a function for each defined property.
|
* Calling a function for each defined property.
|
||||||
* @param callback - The function to call.
|
* @param callback - The function to call.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected forEachModelProperty<ReturnType>(callback: (propertyName: keyof Shape, propertyDefinition: UnknownDefinition) => ReturnType): ReturnType
|
protected forEachModelProperty(callback: (propertyName: string, propertyMetadata: SharkitekMetadataInterface) => unknown): any|void
|
||||||
{
|
{
|
||||||
for (const [propertyName, propertyDefinition] of shapeEntries)
|
for (const propertyName of Object.keys(this))
|
||||||
{ // For each property, checking that its type is defined and calling the callback with its type.
|
{ // 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 = this.propertyWithMetadata(propertyName, (propertyMetadata) => {
|
||||||
const result = callback(propertyName, propertyDefinition);
|
// If the property is defined, calling the function with the property name and metadata.
|
||||||
|
const result = callback(propertyName, propertyMetadata);
|
||||||
|
|
||||||
|
// 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).
|
// If there is a return value, returning it directly (loop is broken).
|
||||||
if (typeof result !== "undefined") return result;
|
if (typeof result !== "undefined") return result;
|
||||||
}
|
}
|
||||||
|
@ -155,142 +132,128 @@ export function model<ModelType extends Model<Shape, IdentifierType<Shape, Ident
|
||||||
* The original properties values.
|
* The original properties values.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected _originalProperties: Partial<PropertiesModel<Shape>> = {};
|
protected _originalProperties: Record<string, any> = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The original (serialized) object.
|
* The original (serialized) object.
|
||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
protected _originalObject: SerializedModel<Shape>|null = null;
|
protected _originalObject: any = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
getIdentifier(): IdentifierType<Shape, Identifier>
|
|
||||||
{
|
|
||||||
return (this as PropertiesModel<Shape>)?.[identifier];
|
|
||||||
}
|
|
||||||
|
|
||||||
getIdentifierName(): IdentifierNameType<Shape>
|
|
||||||
{
|
|
||||||
return 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>): ModelType
|
|
||||||
{
|
|
||||||
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 unknown as ModelType;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the model is new or not.
|
||||||
|
*/
|
||||||
isNew(): boolean
|
isNew(): boolean
|
||||||
{
|
{
|
||||||
return !this._originalObject;
|
return !this._originalObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if the model is dirty or not.
|
||||||
|
*/
|
||||||
isDirty(): boolean
|
isDirty(): boolean
|
||||||
{
|
{
|
||||||
return this.forEachModelProperty((propertyName, propertyDefinition) => (
|
return this.forEachModelProperty((propertyName, propertyMetadata) => (
|
||||||
// For each property, checking if it is different.
|
// For each property, checking if it is different.
|
||||||
propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as PropertiesModel<Shape>)[propertyName])
|
propertyMetadata.type.propertyHasChanged(this._originalProperties[propertyName], (this as any)[propertyName])
|
||||||
// There is a difference, we should return false.
|
// There is a difference, we should return false.
|
||||||
? true
|
? true
|
||||||
// There is no difference, returning nothing.
|
// There is not difference, returning nothing.
|
||||||
: undefined
|
: undefined
|
||||||
)) === true;
|
)) === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
serializeDiff(): Partial<SerializedModel<Shape>>
|
* Get model identifier.
|
||||||
|
*/
|
||||||
|
getIdentifier(): unknown
|
||||||
{
|
{
|
||||||
// Creating an empty (=> partial) serialized object.
|
return (this as any)[this.getModelIdentifier()];
|
||||||
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
|
/**
|
||||||
|
* Set current properties values as original values.
|
||||||
|
*/
|
||||||
|
resetDiff()
|
||||||
{
|
{
|
||||||
this.forEachModelProperty((propertyName, propertyDefinition) => {
|
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||||
// For each property, set its original value to its current property value.
|
// For each property, set its original value to its current property value.
|
||||||
this._originalProperties[propertyName] = structuredClone(this as PropertiesModel<Shape>)[propertyName];
|
this._originalProperties[propertyName] = (this as any)[propertyName];
|
||||||
propertyDefinition.type.resetDiff((this as PropertiesModel<Shape>)[propertyName]);
|
propertyMetadata.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 = {};
|
||||||
|
|
||||||
patch(): Partial<SerializedModel<Shape>>
|
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||||
|
// 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]))
|
||||||
|
// 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]);
|
||||||
|
})
|
||||||
|
|
||||||
|
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.
|
// Get the difference.
|
||||||
const diff = this.serializeDiff();
|
const diff = this.serializeDiff();
|
||||||
|
|
||||||
// Once the difference has been obtained, reset it.
|
// Once the difference has been gotten, reset it.
|
||||||
this.resetDiff();
|
this.resetDiff();
|
||||||
|
|
||||||
return diff; // Return the difference.
|
return diff; // Return the difference.
|
||||||
}
|
}
|
||||||
|
|
||||||
} as unknown as ConstructorOf<ModelType>
|
|
||||||
);
|
/**
|
||||||
|
* Serialize the model.
|
||||||
|
*/
|
||||||
|
serialize(): any
|
||||||
|
{
|
||||||
|
// Creating a serialized object.
|
||||||
|
const serializedObject: any = {};
|
||||||
|
|
||||||
|
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||||
|
// For each defined model property, adding it to the serialized object.
|
||||||
|
serializedObject[propertyName] = propertyMetadata.type.serialize((this as any)[propertyName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
return serializedObject; // Returning the serialized object.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any Sharkitek model.
|
* Special operations on parse.
|
||||||
|
* @protected
|
||||||
*/
|
*/
|
||||||
export type AnyModel = Model<any, any>;
|
protected parse(): void
|
||||||
/**
|
{} // Nothing by default. TODO: create a event system to create functions like "beforeDeserialization" or "afterDeserialization".
|
||||||
* Any Sharkitek model class.
|
|
||||||
*/
|
|
||||||
export type AnyModelClass = ModelClass<AnyModel, any>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add extends function to a model class.
|
* Deserialize the model.
|
||||||
* @param genericModel The model class on which to add the extends function.
|
|
||||||
*/
|
*/
|
||||||
function withExtends<ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>, Shape extends ModelShape, Identifier extends keyof Shape = any>(
|
deserialize(serializedObject: any): this
|
||||||
genericModel: ConstructorOf<ModelType>
|
|
||||||
): ModelClass<ModelType, Shape, Identifier>
|
|
||||||
{
|
{
|
||||||
return Object.assign(
|
this.forEachModelProperty((propertyName, propertyMetadata) => {
|
||||||
genericModel,
|
// For each defined model property, assigning its deserialized value to the model.
|
||||||
{ // Extends function definition.
|
(this as any)[propertyName] = propertyMetadata.type.deserialize(serializedObject[propertyName]);
|
||||||
extends<Extension extends object>(extension: Extension): ModelClass<ModelType & Extension, Shape, Identifier>
|
});
|
||||||
{
|
|
||||||
// Clone the model class and add extends function.
|
// Reset original property values.
|
||||||
const classClone = withExtends(class extends (genericModel as AnyModelClass) {} as AnyModelClass as ConstructorOf<ModelType & Extension>);
|
this.resetDiff();
|
||||||
// Add extension to the model class prototype.
|
|
||||||
Object.assign(classClone.prototype, extension);
|
this._originalObject = serializedObject; // The model is not a new one, but loaded from a deserialized one.
|
||||||
return classClone;
|
|
||||||
|
return this; // Returning this, after deserialization.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) as AnyModelClass as ModelClass<ModelType, Shape, Identifier>;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
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";
|
|
|
@ -1,26 +0,0 @@
|
||||||
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,5 +1,4 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of an array of values.
|
* Type of an array of values.
|
||||||
|
@ -7,94 +6,48 @@ import {define, Definition} from "../PropertyDefinition";
|
||||||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
|
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Initialize a new array type of a Sharkitek model property.
|
* Constructs a new array type of Sharkitek model property.
|
||||||
* @param valueDefinition Definition the array values.
|
* @param valueType - Type of the array values.
|
||||||
*/
|
*/
|
||||||
constructor(protected valueDefinition: Definition<SerializedValueType, SharkitekValueType>)
|
constructor(protected valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
serialize(value: SharkitekValueType[]): SerializedValueType[]
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return value.map((value) => (
|
return value.map((value) => (
|
||||||
// Serializing each value of the array.
|
// Serializing each value of the array.
|
||||||
this.valueDefinition.type.serialize(value)
|
this.valueType.serialize(value)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(value: SerializedValueType[]|null|undefined): SharkitekValueType[]|null|undefined
|
deserialize(value: SerializedValueType[]): SharkitekValueType[]
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return value.map((serializedValue) => (
|
return value.map((serializedValue) => (
|
||||||
// Deserializing each value of the array.
|
// Deserializing each value of the array.
|
||||||
this.valueDefinition.type.deserialize(serializedValue)
|
this.valueType.deserialize(serializedValue)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDiff(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
|
serializeDiff(value: SharkitekValueType[]): any
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
// Serializing diff of all elements.
|
// Serializing diff of all elements.
|
||||||
return value.map((value) => this.valueDefinition.type.serializeDiff(value) as SerializedValueType);
|
return value.map((value) => this.valueType.serializeDiff(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDiff(value: SharkitekValueType[]|null|undefined): void
|
resetDiff(value: SharkitekValueType[]): void
|
||||||
{
|
{
|
||||||
// Do nothing if it is not an array.
|
|
||||||
if (!Array.isArray(value)) return;
|
|
||||||
|
|
||||||
// Reset diff of all elements.
|
// Reset diff of all elements.
|
||||||
value.forEach((value) => this.valueDefinition.type.resetDiff(value));
|
value.forEach((value) => this.valueType.resetDiff(value));
|
||||||
}
|
|
||||||
|
|
||||||
propertyHasChanged(originalValue: SharkitekValueType[]|null|undefined, currentValue: SharkitekValueType[]|null|undefined): boolean
|
|
||||||
{
|
|
||||||
// If any array length is different, arrays are different.
|
|
||||||
if (originalValue?.length != currentValue?.length) return true;
|
|
||||||
// If length is undefined, values are probably not arrays.
|
|
||||||
if (originalValue?.length == undefined) return false;
|
|
||||||
|
|
||||||
for (const key of originalValue.keys())
|
|
||||||
{ // Check for any change for each value in the array.
|
|
||||||
if (this.valueDefinition.type.propertyHasChanged(originalValue[key], currentValue[key]))
|
|
||||||
// The value has changed, the array is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedPropertyHasChanged(originalValue: SerializedValueType[] | null | undefined, currentValue: SerializedValueType[] | null | undefined): boolean
|
|
||||||
{
|
|
||||||
// If any array length is different, arrays are different.
|
|
||||||
if (originalValue?.length != currentValue?.length) return true;
|
|
||||||
// If length is undefined, values are probably not arrays.
|
|
||||||
if (originalValue?.length == undefined) return false;
|
|
||||||
|
|
||||||
for (const key of originalValue.keys())
|
|
||||||
{ // Check for any change for each value in the array.
|
|
||||||
if (this.valueDefinition.type.serializedPropertyHasChanged(originalValue[key], currentValue[key]))
|
|
||||||
// The value has changed, the array is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New array property definition.
|
* Type of an array of values.
|
||||||
* @param valueDefinition Array values type definition.
|
* @param valueType - Type of the array values.
|
||||||
*/
|
*/
|
||||||
export function array<SerializedValueType, SharkitekValueType>(valueDefinition: Definition<SerializedValueType, SharkitekValueType>): Definition<SerializedValueType[], SharkitekValueType[]>
|
export function SArray<SerializedValueType, SharkitekValueType>(valueType: Type<SerializedValueType, SharkitekValueType>)
|
||||||
{
|
{
|
||||||
return define(new ArrayType(valueDefinition));
|
return new ArrayType<SerializedValueType, SharkitekValueType>(valueType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
import {Type} from "./Type";
|
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of any boolean value.
|
|
||||||
*/
|
|
||||||
export class BoolType extends Type<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|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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* New boolean property definition.
|
|
||||||
*/
|
|
||||||
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,37 +1,22 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of dates.
|
* Type of dates.
|
||||||
*/
|
*/
|
||||||
export class DateType extends Type<string, Date>
|
export class DateType extends Type<string, Date>
|
||||||
{
|
{
|
||||||
deserialize(value: string|null|undefined): Date|null|undefined
|
deserialize(value: string): Date
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return new Date(value);
|
return new Date(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: Date|null|undefined): string|null|undefined
|
serialize(value: Date): string
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
return value.toISOString();
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return value?.toISOString();
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyHasChanged(originalValue: Date|null|undefined, currentValue: Date|null|undefined): boolean
|
|
||||||
{
|
|
||||||
return originalValue?.toISOString() != currentValue?.toISOString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New date property definition.
|
* Type of dates.
|
||||||
*/
|
*/
|
||||||
export function date(): Definition<string, Date>
|
export const SDate = new DateType();
|
||||||
{
|
|
||||||
return define(new DateType());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,32 +1,22 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of decimal numbers.
|
* Type of decimal numbers.
|
||||||
*/
|
*/
|
||||||
export class DecimalType extends Type<string, number>
|
export class DecimalType extends Type<string, number>
|
||||||
{
|
{
|
||||||
deserialize(value: string|null|undefined): number|null|undefined
|
deserialize(value: string): number
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return parseFloat(value);
|
return parseFloat(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: number|null|undefined): string|null|undefined
|
serialize(value: number): string
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
return value.toString();
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
return value?.toString();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New decimal property definition.
|
* Type of decimal numbers.
|
||||||
*/
|
*/
|
||||||
export function decimal(): Definition<string, number>
|
export const SDecimal = new DecimalType();
|
||||||
{
|
|
||||||
return define(new DecimalType());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,69 +1,55 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
import {Model} from "../Model";
|
||||||
import {ConstructorOf, Model, ModelShape, SerializedModel} from "../Model";
|
|
||||||
|
/**
|
||||||
|
* Type definition of the constructor of a specific type.
|
||||||
|
*/
|
||||||
|
export type ConstructorOf<T> = { new(): T; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of a Sharkitek model value.
|
* Type of a Sharkitek model value.
|
||||||
*/
|
*/
|
||||||
export class ModelType<Shape extends ModelShape> extends Type<SerializedModel<Shape>, Model<Shape>>
|
export class ModelType<M extends Model> extends Type<any, M>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Initialize a new model type of a Sharkitek model property.
|
* Constructs a new model type of a Sharkitek model property.
|
||||||
* @param modelConstructor Model constructor.
|
* @param modelConstructor - Constructor of the model.
|
||||||
*/
|
*/
|
||||||
constructor(protected modelConstructor: ConstructorOf<Model<Shape>>)
|
constructor(protected modelConstructor: ConstructorOf<M>)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: Model<Shape>|null|undefined): SerializedModel<Shape>|null|undefined
|
serialize(value: M|null): any
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
// Serializing the given model.
|
// Serializing the given model.
|
||||||
return value?.serialize();
|
return value ? value.serialize() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
deserialize(value: SerializedModel<Shape>|null|undefined): Model<Shape>|null|undefined
|
deserialize(value: any): M|null
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
// Deserializing the given object in the new model.
|
// Deserializing the given object in the new model.
|
||||||
return (new this.modelConstructor()).deserialize(value) as Model<Shape>;
|
return value ? (new this.modelConstructor()).deserialize(value) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDiff(value: Model<Shape>|null|undefined): Partial<SerializedModel<Shape>>|null|undefined
|
serializeDiff(value: M): any
|
||||||
{
|
{
|
||||||
if (value === undefined) return undefined;
|
|
||||||
if (value === null) return null;
|
|
||||||
|
|
||||||
// Serializing the given model.
|
// Serializing the given model.
|
||||||
return value?.serializeDiff();
|
return value ? value.serializeDiff() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
resetDiff(value: Model<Shape>|null|undefined): void
|
resetDiff(value: M): void
|
||||||
{
|
{
|
||||||
// Reset diff of the given model.
|
// Reset diff of the given model.
|
||||||
value?.resetDiff();
|
value?.resetDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
propertyHasChanged(originalValue: Model<Shape>|null|undefined, currentValue: Model<Shape>|null|undefined): boolean
|
|
||||||
{
|
|
||||||
if (originalValue === undefined) return currentValue !== undefined;
|
|
||||||
if (originalValue === null) return currentValue !== null;
|
|
||||||
|
|
||||||
// If the current value is dirty, property has changed.
|
|
||||||
return currentValue.isDirty();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New model property definition.
|
* Type of a Sharkitek model value.
|
||||||
* @param modelConstructor Model constructor.
|
* @param modelConstructor - Constructor of the model.
|
||||||
*/
|
*/
|
||||||
export function model<Shape extends ModelShape>(modelConstructor: ConstructorOf<Model<Shape>>): Definition<SerializedModel<Shape>, Model<Shape>>
|
export function SModel<M extends Model>(modelConstructor: ConstructorOf<M>)
|
||||||
{
|
{
|
||||||
return define(new ModelType(modelConstructor));
|
return new ModelType(modelConstructor);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,22 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of any numeric value.
|
* Type of any numeric value.
|
||||||
*/
|
*/
|
||||||
export class NumericType extends Type<number, number>
|
export class NumericType extends Type<number, number>
|
||||||
{
|
{
|
||||||
deserialize(value: number|null|undefined): number|null|undefined
|
deserialize(value: number): number
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: number|null|undefined): number|null|undefined
|
serialize(value: number): number
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New numeric property definition.
|
* Type of any numeric value.
|
||||||
*/
|
*/
|
||||||
export function numeric(): Definition<number, number>
|
export const SNumeric = new NumericType();
|
||||||
{
|
|
||||||
return define(new NumericType());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
import {Type} from "./Type";
|
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
import {ModelShape, PropertiesModel, SerializedModel, UnknownDefinition} from "../Model";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type of a custom object.
|
|
||||||
*/
|
|
||||||
export class ObjectType<Shape extends ModelShape> extends Type<SerializedModel<Shape>, PropertiesModel<Shape>>
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Initialize a new object type of a Sharkitek model property.
|
|
||||||
* @param shape
|
|
||||||
*/
|
|
||||||
constructor(protected readonly shape: Shape)
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 PropertiesModel<Shape>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 PropertiesModel<Shape>;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.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 PropertiesModel<Shape>;
|
|
||||||
}
|
|
||||||
|
|
||||||
resetDiff(value: PropertiesModel<Shape>|null|undefined)
|
|
||||||
{
|
|
||||||
// For each field, reset its diff.
|
|
||||||
(Object.entries(this.shape) as [keyof Shape, UnknownDefinition][]).forEach(([fieldName, fieldDefinition]) => {
|
|
||||||
// Reset diff of the current field.
|
|
||||||
fieldDefinition.type.resetDiff(value?.[fieldName]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
propertyHasChanged(originalValue: PropertiesModel<Shape>|null|undefined, currentValue: PropertiesModel<Shape>|null|undefined): boolean
|
|
||||||
{
|
|
||||||
// Get keys arrays.
|
|
||||||
const originalKeys = Object.keys(originalValue) as (keyof Shape)[];
|
|
||||||
const currentKeys = Object.keys(currentValue) as (keyof Shape)[];
|
|
||||||
|
|
||||||
if (originalKeys.join(",") != currentKeys.join(","))
|
|
||||||
// Keys have changed, objects are different.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (const key of originalKeys)
|
|
||||||
{ // Check for any change for each value in the object.
|
|
||||||
if (this.shape[key].type.propertyHasChanged(originalValue[key], currentValue[key]))
|
|
||||||
// The value has changed, the object is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
|
||||||
|
|
||||||
serializedPropertyHasChanged(originalValue: SerializedModel<Shape>|null|undefined, currentValue: SerializedModel<Shape>|null|undefined): boolean
|
|
||||||
{
|
|
||||||
// Get keys arrays.
|
|
||||||
const originalKeys = Object.keys(originalValue) as (keyof Shape)[];
|
|
||||||
const currentKeys = Object.keys(currentValue) as (keyof Shape)[];
|
|
||||||
|
|
||||||
if (originalKeys.join(",") != currentKeys.join(","))
|
|
||||||
// Keys have changed, objects are different.
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (const key of originalKeys)
|
|
||||||
{ // Check for any change for each value in the object.
|
|
||||||
if (this.shape[key].type.serializedPropertyHasChanged(originalValue[key], currentValue[key]))
|
|
||||||
// The value has changed, the object is different.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false; // No change detected.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* New object property definition.
|
|
||||||
* @param shape Shape of the object.
|
|
||||||
*/
|
|
||||||
export function object<Shape extends ModelShape>(shape: Shape): Definition<SerializedModel<Shape>, PropertiesModel<Shape>>
|
|
||||||
{
|
|
||||||
return define(new ObjectType(shape));
|
|
||||||
}
|
|
|
@ -1,26 +1,22 @@
|
||||||
import {Type} from "./Type";
|
import {Type} from "./Type";
|
||||||
import {define, Definition} from "../PropertyDefinition";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of any string value.
|
* Type of any string value.
|
||||||
*/
|
*/
|
||||||
export class StringType extends Type<string, string>
|
export class StringType extends Type<string, string>
|
||||||
{
|
{
|
||||||
deserialize(value: string|null|undefined): string|null|undefined
|
deserialize(value: string): string
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
serialize(value: string|null|undefined): string|null|undefined
|
serialize(value: string): string
|
||||||
{
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* New string property definition.
|
* Type of any string value.
|
||||||
*/
|
*/
|
||||||
export function string(): Definition<string, string>
|
export const SString = new StringType();
|
||||||
{
|
|
||||||
return define(new StringType());
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
/**
|
/**
|
||||||
* Abstract class of a Sharkitek model property type.
|
* Abstract class of a Sharkitek model property type.
|
||||||
*/
|
*/
|
||||||
export abstract class Type<SerializedType, ModelType>
|
export abstract class Type<SerializedType, SharkitekType>
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Serialize the given value of a Sharkitek model property.
|
* Serialize the given value of a Sharkitek model property.
|
||||||
* @param value Value to serialize.
|
* @param value - Value to serialize.
|
||||||
*/
|
*/
|
||||||
abstract serialize(value: ModelType|null|undefined): SerializedType|null|undefined;
|
abstract serialize(value: SharkitekType): SerializedType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize the given value of a serialized Sharkitek model.
|
* Deserialize the given value of a serialized Sharkitek model.
|
||||||
* @param value - Value to deserialize.
|
* @param value - Value to deserialize.
|
||||||
*/
|
*/
|
||||||
abstract deserialize(value: SerializedType|null|undefined): ModelType|null|undefined;
|
abstract deserialize(value: SerializedType): SharkitekType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize the given value only if it has changed.
|
* Serialize the given value only if it has changed.
|
||||||
* @param value - Value to deserialize.
|
* @param value - Value to deserialize.
|
||||||
*/
|
*/
|
||||||
serializeDiff(value: ModelType|null|undefined): Partial<SerializedType>|null|undefined
|
serializeDiff(value: SharkitekType): SerializedType|null
|
||||||
{
|
{
|
||||||
return this.serialize(value); // By default, nothing changes.
|
return this.serialize(value); // By default, nothing changes.
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ export abstract class Type<SerializedType, ModelType>
|
||||||
* Reset the difference between the original value and the current one.
|
* Reset the difference between the original value and the current one.
|
||||||
* @param value - Value for which reset diff data.
|
* @param value - Value for which reset diff data.
|
||||||
*/
|
*/
|
||||||
resetDiff(value: ModelType|null|undefined): void
|
resetDiff(value: SharkitekType): void
|
||||||
{
|
{
|
||||||
// By default, nothing to do.
|
// By default, nothing to do.
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@ export abstract class Type<SerializedType, ModelType>
|
||||||
* @param originalValue - Original property value.
|
* @param originalValue - Original property value.
|
||||||
* @param currentValue - Current property value.
|
* @param currentValue - Current property value.
|
||||||
*/
|
*/
|
||||||
propertyHasChanged(originalValue: ModelType|null|undefined, currentValue: ModelType|null|undefined): boolean
|
propertyHasChanged(originalValue: SharkitekType, currentValue: SharkitekType): boolean
|
||||||
{
|
{
|
||||||
return originalValue != currentValue;
|
return originalValue != currentValue;
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export abstract class Type<SerializedType, ModelType>
|
||||||
* @param originalValue - Original serialized property value.
|
* @param originalValue - Original serialized property value.
|
||||||
* @param currentValue - Current serialized property value.
|
* @param currentValue - Current serialized property value.
|
||||||
*/
|
*/
|
||||||
serializedPropertyHasChanged(originalValue: SerializedType|null|undefined, currentValue: SerializedType|null|undefined): boolean
|
serializedPropertyHasChanged(originalValue: SerializedType, currentValue: SerializedType): boolean
|
||||||
{
|
{
|
||||||
return originalValue != currentValue;
|
return originalValue != currentValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
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";
|
|
15
src/index.ts
15
src/index.ts
|
@ -1,4 +1,11 @@
|
||||||
import * as s from "./Model";
|
|
||||||
export * from "./Model";
|
|
||||||
export { s };
|
export * from "./Model/Model";
|
||||||
export default s;
|
|
||||||
|
export * from "./Model/Types/Type";
|
||||||
|
export * from "./Model/Types/ArrayType";
|
||||||
|
export * from "./Model/Types/DateType";
|
||||||
|
export * from "./Model/Types/DecimalType";
|
||||||
|
export * from "./Model/Types/ModelType";
|
||||||
|
export * from "./Model/Types/NumericType";
|
||||||
|
export * from "./Model/Types/StringType";
|
||||||
|
|
|
@ -1,24 +1,23 @@
|
||||||
import {s} from "../src";
|
import {SArray, SDecimal, SModel, SNumeric, SString, SDate, Identifier, Model, Property} from "../src";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Another test model.
|
* Another test model.
|
||||||
*/
|
*/
|
||||||
class Author extends s.model({
|
class Author extends Model
|
||||||
name: s.property.string(),
|
|
||||||
firstName: s.property.string(),
|
|
||||||
email: s.property.string(),
|
|
||||||
createdAt: s.property.date(),
|
|
||||||
active: s.property.bool(),
|
|
||||||
}).extends({
|
|
||||||
extension(): string
|
|
||||||
{
|
{
|
||||||
return this.name;
|
@Property(SString)
|
||||||
}
|
name: string = undefined;
|
||||||
})
|
|
||||||
{
|
|
||||||
active: boolean = true;
|
|
||||||
|
|
||||||
constructor(name: string = "", firstName: string = "", email: string = "", createdAt: Date = new Date())
|
@Property(SString)
|
||||||
|
firstName: string = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
email: string = undefined;
|
||||||
|
|
||||||
|
@Property(SDate)
|
||||||
|
createdAt: Date = undefined;
|
||||||
|
|
||||||
|
constructor(name: string = undefined, firstName: string = undefined, email: string = undefined, createdAt: Date = undefined)
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -32,27 +31,23 @@ class Author extends s.model({
|
||||||
/**
|
/**
|
||||||
* A test model.
|
* A test model.
|
||||||
*/
|
*/
|
||||||
class Article extends s.model({
|
class Article extends 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;
|
@Property(SNumeric)
|
||||||
title: string;
|
@Identifier
|
||||||
|
id: number = undefined;
|
||||||
|
|
||||||
|
@Property(SString)
|
||||||
|
title: string = undefined;
|
||||||
|
|
||||||
|
@Property(SArray(SModel(Author)))
|
||||||
authors: Author[] = [];
|
authors: Author[] = [];
|
||||||
text: string;
|
|
||||||
evaluation: number;
|
@Property(SString)
|
||||||
tags: {
|
text: string = undefined;
|
||||||
name: string;
|
|
||||||
}[];
|
@Property(SDecimal)
|
||||||
|
evaluation: number = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
it("deserialize", () => {
|
it("deserialize", () => {
|
||||||
|
@ -60,22 +55,20 @@ it("deserialize", () => {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", },
|
||||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", },
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [ {name: "test"}, {name: "foo"} ],
|
|
||||||
}).serialize()).toStrictEqual({
|
}).serialize()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", active: true, },
|
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: "2022-08-07T08:47:01.000Z", },
|
||||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", active: false, },
|
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: "2022-09-07T18:32:55.000Z", },
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [ {name: "test"}, {name: "foo"} ],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,9 +82,6 @@ it("create and check state then serialize", () => {
|
||||||
];
|
];
|
||||||
article.text = "this is a long test.";
|
article.text = "this is a long test.";
|
||||||
article.evaluation = 25.23;
|
article.evaluation = 25.23;
|
||||||
article.tags = [];
|
|
||||||
article.tags.push({name: "test"});
|
|
||||||
article.tags.push({name: "foo"});
|
|
||||||
|
|
||||||
expect(article.isNew()).toBeTruthy();
|
expect(article.isNew()).toBeTruthy();
|
||||||
expect(article.getIdentifier()).toStrictEqual(1);
|
expect(article.getIdentifier()).toStrictEqual(1);
|
||||||
|
@ -100,26 +90,24 @@ it("create and check state then serialize", () => {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: now.toISOString(), active: true, },
|
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: now.toISOString() },
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [ {name: "test"}, {name: "foo"} ],
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("deserialize then patch", () => {
|
it("deserialize then save", () => {
|
||||||
const article = (new Article()).deserialize({
|
const article = (new Article()).deserialize({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
|
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: new Date(), },
|
||||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date()).toISOString(), active: false, },
|
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: new Date(), },
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [ {name: "test"}, {name: "foo"} ],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(article.isNew()).toBeFalsy();
|
expect(article.isNew()).toBeFalsy();
|
||||||
|
@ -130,39 +118,34 @@ it("deserialize then patch", () => {
|
||||||
|
|
||||||
expect(article.isDirty()).toBeTruthy();
|
expect(article.isDirty()).toBeTruthy();
|
||||||
|
|
||||||
expect(article.patch()).toStrictEqual({
|
expect(article.save()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
text: "Modified text.",
|
text: "Modified text.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("patch with modified submodels", () => {
|
it("save with modified submodels", () => {
|
||||||
const article = (new Article()).deserialize({
|
const article = (new Article()).deserialize({
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "this is a test",
|
title: "this is a test",
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
|
{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: new Date(), },
|
||||||
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date("1997-09-09")).toISOString(), active: false, },
|
{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: new Date(), },
|
||||||
],
|
],
|
||||||
text: "this is a long test.",
|
text: "this is a long test.",
|
||||||
evaluation: "25.23",
|
evaluation: "25.23",
|
||||||
tags: [ {name: "test"}, {name: "foo"} ],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
article.authors[0].name = "TEST";
|
article.authors = article.authors.map((author) => {
|
||||||
article.authors[1].createdAt.setMonth(9);
|
author.name = "TEST";
|
||||||
|
return author;
|
||||||
|
});
|
||||||
|
|
||||||
expect(article.patch()).toStrictEqual({
|
expect(article.save()).toStrictEqual({
|
||||||
id: 1,
|
id: 1,
|
||||||
authors: [
|
authors: [
|
||||||
{ name: "TEST" },
|
{ name: "TEST", },
|
||||||
{ createdAt: (new Date("1997-10-09")).toISOString() }, //{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
{}, //{ name: "TEST", firstName: "Another", email: "another@test.test" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test author extension", () => {
|
|
||||||
const author = new Author();
|
|
||||||
author.name = "test name";
|
|
||||||
expect(author.extension()).toStrictEqual("test name");
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
{
|
{
|
||||||
"ts-node": {
|
"files": ["src/index.ts"],
|
||||||
"compilerOptions": {
|
|
||||||
"module": "ESNext",
|
|
||||||
"types": ["node"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./lib/",
|
"outDir": "./lib/",
|
||||||
"incremental": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitThis": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"resolveJsonModule": true,
|
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
"module": "ES6",
|
"module": "ES6",
|
||||||
"target": "ES6",
|
"moduleResolution": "Node",
|
||||||
"moduleResolution": "Bundler",
|
"target": "ES5",
|
||||||
|
|
||||||
"lib": [
|
"lib": [
|
||||||
"ESNext",
|
"ESNext",
|
||||||
"DOM"
|
"DOM"
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import {ConfigEnv, defineConfig, UserConfig} from "vite";
|
|
||||||
import dts from "vite-plugin-dts";
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
|
|
||||||
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
|
||||||
return ({
|
|
||||||
build: {
|
|
||||||
outDir: "lib",
|
|
||||||
sourcemap: true,
|
|
||||||
minify: "esbuild",
|
|
||||||
lib: {
|
|
||||||
entry: "src/index.ts",
|
|
||||||
formats: ["es"],
|
|
||||||
fileName: "index",
|
|
||||||
},
|
|
||||||
rollupOptions: {
|
|
||||||
external: ["reflect-metadata"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
dts({
|
|
||||||
insertTypesEntry: true,
|
|
||||||
rollupTypes: true,
|
|
||||||
exclude: ["node_modules"],
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
Loading…
Reference in a new issue