Compare commits
	
		
			No commits in common. "main" and "v3.0.2" have entirely different histories.
		
	
	
		
	
		
					 58 changed files with 987 additions and 9817 deletions
				
			
		| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
on: [push]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    runs-on: docker
 | 
			
		||||
    container:
 | 
			
		||||
      image: node:latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - run: corepack enable
 | 
			
		||||
      - uses: actions/setup-node@v4
 | 
			
		||||
        with:
 | 
			
		||||
          cache: "yarn"
 | 
			
		||||
      - run: yarn install
 | 
			
		||||
      - run: yarn lint
 | 
			
		||||
      - run: yarn coverage
 | 
			
		||||
							
								
								
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -13,3 +13,5 @@ lib/
 | 
			
		|||
yarn-error.log
 | 
			
		||||
.pnp*
 | 
			
		||||
node_modules/
 | 
			
		||||
 | 
			
		||||
yarn.lock
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
{
 | 
			
		||||
	"useTabs": true,
 | 
			
		||||
	"trailingComma": "all",
 | 
			
		||||
	"bracketSpacing": false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										277
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										277
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -19,194 +19,72 @@
 | 
			
		|||
</p>
 | 
			
		||||
 | 
			
		||||
<p align="center">
 | 
			
		||||
	<img alt="Tests status" src="https://code.zeptotech.net/Sharkitek/Core/badges/workflows/test.yaml/badge.svg?branch=main" />
 | 
			
		||||
	<a href="https://bundlephobia.com/package/@sharkitek/core" target="_blank">
 | 
			
		||||
		<img alt="Bundle size" src="https://badgen.net/bundlephobia/minzip/@sharkitek/core" />
 | 
			
		||||
	</a>
 | 
			
		||||
	<a href="https://www.npmjs.com/package/@sharkitek/core" target="_blank">
 | 
			
		||||
		<img alt="Latest release" src="https://badgen.net/npm/v/@sharkitek/core" />
 | 
			
		||||
	</a>
 | 
			
		||||
	<a href="https://bundlephobia.com/package/@sharkitek/core" target="_blank">
 | 
			
		||||
		<img alt="Bundle size" src="https://badgen.net/bundlephobia/dependency-count/@sharkitek/core" />
 | 
			
		||||
	</a>
 | 
			
		||||
	<img alt="Latest release" src="https://badgen.net/npm/types/@sharkitek/core" />
 | 
			
		||||
	<img alt="Version 3.0.2" src="https://img.shields.io/badge/version-3.0.2-blue" />
 | 
			
		||||
</p>
 | 
			
		||||
 | 
			
		||||
## Introduction
 | 
			
		||||
 | 
			
		||||
Sharkitek is a lightweight Javascript / TypeScript library designed to ease development of models.
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
yarn add @sharkitek/core
 | 
			
		||||
```
 | 
			
		||||
Sharkitek is a Javascript / TypeScript library designed to ease development of client-side models.
 | 
			
		||||
 | 
			
		||||
With Sharkitek, you define the architecture of your models by specifying their properties and their types.
 | 
			
		||||
Then, you can use the defined methods like `serialize`, `parse`, `patch` or `serializeDiff`.
 | 
			
		||||
Then, you can use the defined methods like `serialize`, `deserialize`, `save` or `serializeDiff`.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
class Example {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ExampleModel = defineModel({
 | 
			
		||||
	Class: Example,
 | 
			
		||||
	properties: {
 | 
			
		||||
class Example extends s.model({
 | 
			
		||||
	id: s.property.numeric(),
 | 
			
		||||
	name: s.property.string(),
 | 
			
		||||
	},
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
});
 | 
			
		||||
})
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Quick start
 | 
			
		||||
## Examples
 | 
			
		||||
 | 
			
		||||
**Note**: we usually define our models in a `{ModelName}Model` variable next to the model's class.
 | 
			
		||||
 | 
			
		||||
### Model definition
 | 
			
		||||
### Simple model definition
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
/**
 | 
			
		||||
 * A person.
 | 
			
		||||
 */
 | 
			
		||||
class Person {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
	email: string;
 | 
			
		||||
	createdAt: Date;
 | 
			
		||||
	active: boolean = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A person model manager.
 | 
			
		||||
 */
 | 
			
		||||
const PersonModel = defineModel({
 | 
			
		||||
	Class: Person,
 | 
			
		||||
	properties: {
 | 
			
		||||
class Person extends s.model({
 | 
			
		||||
	id: s.property.numeric(),
 | 
			
		||||
	name: s.property.string(),
 | 
			
		||||
	firstName: s.property.string(),
 | 
			
		||||
	email: s.property.string(),
 | 
			
		||||
	createdAt: s.property.date(),
 | 
			
		||||
	active: s.property.boolean(),
 | 
			
		||||
	},
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
});
 | 
			
		||||
}, "id")
 | 
			
		||||
{
 | 
			
		||||
	active: boolean = true;
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
/**
 | 
			
		||||
 * An article.
 | 
			
		||||
 */
 | 
			
		||||
class Article {
 | 
			
		||||
class Article extends s.model({
 | 
			
		||||
	id: s.property.numeric(),
 | 
			
		||||
	title: s.property.string(),
 | 
			
		||||
	authors: s.property.array(s.property.model(Author)),
 | 
			
		||||
	text: s.property.string(),
 | 
			
		||||
	evaluation: s.property.decimal(),
 | 
			
		||||
	tags: s.property.array(
 | 
			
		||||
		s.property.object({
 | 
			
		||||
			name: s.property.string(),
 | 
			
		||||
		})
 | 
			
		||||
	),
 | 
			
		||||
}, "id")
 | 
			
		||||
{
 | 
			
		||||
	id: number;
 | 
			
		||||
	title: string;
 | 
			
		||||
	authors: Person[] = [];
 | 
			
		||||
	authors: Author[] = [];
 | 
			
		||||
	text: string;
 | 
			
		||||
	evaluation: number;
 | 
			
		||||
	tags: {
 | 
			
		||||
		name: string;
 | 
			
		||||
	}[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An article model manager.
 | 
			
		||||
 */
 | 
			
		||||
const ArticleModel = defineModel({
 | 
			
		||||
	Class: Article,
 | 
			
		||||
	properties: {
 | 
			
		||||
		id: s.property.numeric(),
 | 
			
		||||
		title: s.property.string(),
 | 
			
		||||
		authors: s.property.array(s.property.model(PersonModel)),
 | 
			
		||||
		text: s.property.string(),
 | 
			
		||||
		evaluation: s.property.decimal(),
 | 
			
		||||
		tags: s.property.array(
 | 
			
		||||
			s.property.object({
 | 
			
		||||
				name: s.property.string(),
 | 
			
		||||
			}),
 | 
			
		||||
		),
 | 
			
		||||
	},
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
/**
 | 
			
		||||
 * A model with composite keys.
 | 
			
		||||
 */
 | 
			
		||||
class CompositeKeys {
 | 
			
		||||
	id1: number;
 | 
			
		||||
	id2: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A composite keys model manager.
 | 
			
		||||
 */
 | 
			
		||||
const CompositeKeysModel = defineModel({
 | 
			
		||||
	Class: CompositeKeys,
 | 
			
		||||
	properties: {
 | 
			
		||||
		id1: s.property.numeric(),
 | 
			
		||||
		id2: s.property.string(),
 | 
			
		||||
	},
 | 
			
		||||
	identifier: ["id1", "id2"],
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Model functions
 | 
			
		||||
 | 
			
		||||
#### Serialization
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const instance = new Person();
 | 
			
		||||
instance.id = 1;
 | 
			
		||||
instance.createdAt = new Date();
 | 
			
		||||
instance.name = "John Doe";
 | 
			
		||||
instance.email = "john@doe.test";
 | 
			
		||||
instance.active = true;
 | 
			
		||||
const serialized = PersonModel.model(instance).serialize();
 | 
			
		||||
console.log(serialized); // { id: 1, createdAt: "YYYY-MM-DDTHH:mm:ss.sssZ", name: "John Doe", email: "john@doe.test", active: true }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Deserialization
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const instance = PersonModel.parse({
 | 
			
		||||
	id: 1,
 | 
			
		||||
	createdAt: "2011-10-05T14:48:00.000Z",
 | 
			
		||||
	name: "John Doe",
 | 
			
		||||
	email: "john@doe.test",
 | 
			
		||||
	active: true,
 | 
			
		||||
});
 | 
			
		||||
console.log(instance instanceof Person); // true
 | 
			
		||||
console.log(instance.createdAt instanceof Date); // true
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Patch
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const instance = PersonModel.parse({
 | 
			
		||||
	id: 1,
 | 
			
		||||
	createdAt: "2011-10-05T14:48:00.000Z",
 | 
			
		||||
	name: "John Doe",
 | 
			
		||||
	email: "john@doe.test",
 | 
			
		||||
	active: true,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
instance.name = "Johnny";
 | 
			
		||||
 | 
			
		||||
// Patch serialized only changed properties and the identifier.
 | 
			
		||||
console.log(PersonModel.model(instance).patch()); // { id: 1, name: "Johnny" }
 | 
			
		||||
// If you run it one more time, already patched properties will not be included again.
 | 
			
		||||
console.log(PersonModel.model(instance).patch()); // { id: 1 }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Identifier
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const instance = new CompositeKeys();
 | 
			
		||||
instance.id1 = 5;
 | 
			
		||||
instance.id2 = "foo";
 | 
			
		||||
const instanceIdentifier = CompositeKeysModel.model(instance).getIdentifier();
 | 
			
		||||
console.log(instanceIdentifier); // [5, "foo"]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## API
 | 
			
		||||
| 
						 | 
				
			
			@ -217,92 +95,53 @@ Types are defined by a class extending `Type`.
 | 
			
		|||
 | 
			
		||||
Sharkitek defines some basic types by default, in these classes:
 | 
			
		||||
 | 
			
		||||
- `BooleanType`: boolean value in the model, boolean value in the serialized object.
 | 
			
		||||
- `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.
 | 
			
		||||
- `ObjectType`: object in the model, object in the serialized object.
 | 
			
		||||
- `MapType`: map in the model, record 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.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
class Example {
 | 
			
		||||
class Example extends s.model({
 | 
			
		||||
	foo: s.property.define(new StringType()),
 | 
			
		||||
})
 | 
			
		||||
{
 | 
			
		||||
	foo: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ExampleModel = defineModel({
 | 
			
		||||
	Class: Example,
 | 
			
		||||
	properties: {
 | 
			
		||||
		foo: s.property.define(new StringType()),
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
To ease the use of these classes and reduce read complexity, properties of each type are easily definable with a function for each type.
 | 
			
		||||
To ease the use of these classes and reduce read complexity,
 | 
			
		||||
properties of each type are easily definable with a function for each type.
 | 
			
		||||
 | 
			
		||||
- `BooleanType` => `s.property.boolean`
 | 
			
		||||
- `BoolType` => `s.property.boolean`
 | 
			
		||||
- `StringType` => `s.property.string`
 | 
			
		||||
- `NumericType` => `s.property.numeric`
 | 
			
		||||
- `DecimalType` => `s.property.decimal`
 | 
			
		||||
- `DateType` => `s.property.date`
 | 
			
		||||
- `ArrayType` => `s.property.array`
 | 
			
		||||
- `ObjectType` => `s.property.object`
 | 
			
		||||
- `MapType` => `s.property.map` or `s.property.stringMap`
 | 
			
		||||
- `ModelType` => `s.property.model`
 | 
			
		||||
 | 
			
		||||
Type implementers should provide a corresponding function for each defined type. They can even provide multiple functions or constants with predefined parameters. For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.
 | 
			
		||||
Type implementers should provide a corresponding function for each defined type. They can even provide
 | 
			
		||||
multiple functions or constants with predefined parameters.
 | 
			
		||||
(For example, we could define `s.property.stringArray()` which would be similar to `s.property.array(s.property.string())`.)
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
class Example {
 | 
			
		||||
class Example extends s.model({
 | 
			
		||||
	foo: s.property.string(),
 | 
			
		||||
})
 | 
			
		||||
{
 | 
			
		||||
	foo: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ExampleModel = defineModel({
 | 
			
		||||
	Class: Example,
 | 
			
		||||
	properties: {
 | 
			
		||||
		foo: s.property.string(),
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Models
 | 
			
		||||
 | 
			
		||||
#### `model(instance)`
 | 
			
		||||
 | 
			
		||||
Get a model class (which has all the sharkitek models' functions) from a model instance.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const model = definedModel.model(modelInstance);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `assign(object)`
 | 
			
		||||
 | 
			
		||||
Assign fields from a provided object to the model instance properties. Fields which are not properties of the target model are silently ignored.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const alteredModelInstance = definedModel.model(modelInstance).assign({
 | 
			
		||||
	anyProperty: "foo",
 | 
			
		||||
	anotherOne: true,
 | 
			
		||||
	not_a_property: "will be ignored",
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `from(object)`
 | 
			
		||||
 | 
			
		||||
Initialize a model instance and assign the provided fields to its properties. Fields which are not properties of the target model are silently ignored.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const newModelInstance = definedModel.from({
 | 
			
		||||
	anyProperty: "foo",
 | 
			
		||||
	anotherOne: true,
 | 
			
		||||
	not_a_property: "will be ignored",
 | 
			
		||||
});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `serialize()`
 | 
			
		||||
 | 
			
		||||
Serialize the model.
 | 
			
		||||
| 
						 | 
				
			
			@ -310,17 +149,17 @@ Serialize the model.
 | 
			
		|||
Example:
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const serializedObject = definedModel.model(modelInstance).serialize();
 | 
			
		||||
const serializedObject = model.serialize();
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `parse(serializedObject)`
 | 
			
		||||
#### `deserialize(serializedObject)`
 | 
			
		||||
 | 
			
		||||
Deserialize the model.
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const modelInstance = definedModel.parse({
 | 
			
		||||
const model = (new TestModel()).deserialize({
 | 
			
		||||
	id: 5,
 | 
			
		||||
	title: "Hello World!",
 | 
			
		||||
	users: [
 | 
			
		||||
| 
						 | 
				
			
			@ -339,7 +178,7 @@ Serialize the difference between current model state and original one.
 | 
			
		|||
Example:
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const modelInstance = definedModel.parse({
 | 
			
		||||
const model = (new TestModel()).deserialize({
 | 
			
		||||
	id: 5,
 | 
			
		||||
	title: "Hello World!",
 | 
			
		||||
	users: [
 | 
			
		||||
| 
						 | 
				
			
			@ -350,9 +189,9 @@ const modelInstance = definedModel.parse({
 | 
			
		|||
	],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
modelInstance.title = "A new title for a new world";
 | 
			
		||||
model.title = "A new title for a new world";
 | 
			
		||||
 | 
			
		||||
const result = definedModel.model(modelInstance).serializeDiff();
 | 
			
		||||
const result = model.serializeDiff();
 | 
			
		||||
// if `id` is defined as the model identifier:
 | 
			
		||||
// result = { id: 5, title: "A new title for a new world" }
 | 
			
		||||
// if `id` is not defined as the model identifier:
 | 
			
		||||
| 
						 | 
				
			
			@ -366,7 +205,7 @@ Set current properties values as original values.
 | 
			
		|||
Example:
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const modelInstance = definedModel.parse({
 | 
			
		||||
const model = (new TestModel()).deserialize({
 | 
			
		||||
	id: 5,
 | 
			
		||||
	title: "Hello World!",
 | 
			
		||||
	users: [
 | 
			
		||||
| 
						 | 
				
			
			@ -377,24 +216,24 @@ const modelInstance = definedModel.parse({
 | 
			
		|||
	],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
modelInstance.title = "A new title for a new world";
 | 
			
		||||
model.title = "A new title for a new world";
 | 
			
		||||
 | 
			
		||||
definedModel.model(modelInstance).resetDiff();
 | 
			
		||||
model.resetDiff();
 | 
			
		||||
 | 
			
		||||
const result = definedModel.model(modelInstance).serializeDiff();
 | 
			
		||||
const result = model.serializeDiff();
 | 
			
		||||
// if `id` is defined as the model identifier:
 | 
			
		||||
// result = { id: 5 }
 | 
			
		||||
// if `id` is not defined as the model identifier:
 | 
			
		||||
// result = {}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `patch()`
 | 
			
		||||
#### `save()`
 | 
			
		||||
 | 
			
		||||
Get difference between original values and current ones, then reset it.
 | 
			
		||||
Similar to call `serializeDiff()` then `resetDiff()`.
 | 
			
		||||
 | 
			
		||||
```typescript
 | 
			
		||||
const modelInstance = definedModel.parse({
 | 
			
		||||
const model = (new TestModel()).deserialize({
 | 
			
		||||
	id: 5,
 | 
			
		||||
	title: "Hello World!",
 | 
			
		||||
	users: [
 | 
			
		||||
| 
						 | 
				
			
			@ -405,9 +244,9 @@ const modelInstance = definedModel.parse({
 | 
			
		|||
	],
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
modelInstance.title = "A new title for a new world";
 | 
			
		||||
model.title = "A new title for a new world";
 | 
			
		||||
 | 
			
		||||
const result = definedModel.model(modelInstance).patch();
 | 
			
		||||
const result = model.save();
 | 
			
		||||
// if `id` is defined as the model identifier:
 | 
			
		||||
// result = { id: 5, title: "A new title for a new world" }
 | 
			
		||||
// if `id` is not defined as the model identifier:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,23 +0,0 @@
 | 
			
		|||
import js from "@eslint/js";
 | 
			
		||||
import globals from "globals";
 | 
			
		||||
import tseslint from "typescript-eslint";
 | 
			
		||||
import {defineConfig, globalIgnores} from "eslint/config";
 | 
			
		||||
 | 
			
		||||
export default defineConfig([
 | 
			
		||||
	globalIgnores([".yarn/**", "coverage/**", "lib/**"]),
 | 
			
		||||
	{
 | 
			
		||||
		files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
 | 
			
		||||
		plugins: {js},
 | 
			
		||||
		extends: ["js/recommended"],
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
 | 
			
		||||
		languageOptions: {globals: {...globals.browser, ...globals.node}},
 | 
			
		||||
	},
 | 
			
		||||
	tseslint.configs.recommended,
 | 
			
		||||
	{
 | 
			
		||||
		rules: {
 | 
			
		||||
			"@typescript-eslint/no-explicit-any": "off",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
]);
 | 
			
		||||
							
								
								
									
										10
									
								
								jest.config.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								jest.config.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  preset: "ts-jest",
 | 
			
		||||
  testEnvironment: "node",
 | 
			
		||||
 | 
			
		||||
  roots: [
 | 
			
		||||
    "./tests",
 | 
			
		||||
  ],
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										31
									
								
								package.json
									
										
									
									
									
								
							
							
						
						
									
										31
									
								
								package.json
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
	"name": "@sharkitek/core",
 | 
			
		||||
	"version": "4.1.0",
 | 
			
		||||
	"version": "3.0.2",
 | 
			
		||||
	"description": "TypeScript library for well-designed model architectures.",
 | 
			
		||||
	"keywords": [
 | 
			
		||||
		"deserialization",
 | 
			
		||||
| 
						 | 
				
			
			@ -16,7 +16,7 @@
 | 
			
		|||
	"repository": "https://code.zeptotech.net/Sharkitek/Core",
 | 
			
		||||
	"author": {
 | 
			
		||||
		"name": "Madeorsk",
 | 
			
		||||
		"email": "m@deor.sk"
 | 
			
		||||
		"email": "madeorsk@protonmail.com"
 | 
			
		||||
	},
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"publishConfig": {
 | 
			
		||||
| 
						 | 
				
			
			@ -24,31 +24,24 @@
 | 
			
		|||
	},
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"build": "tsc && vite build",
 | 
			
		||||
		"test": "vitest",
 | 
			
		||||
		"coverage": "vitest run --coverage",
 | 
			
		||||
		"format": "prettier . --write",
 | 
			
		||||
		"lint": "eslint"
 | 
			
		||||
		"test": "jest"
 | 
			
		||||
	},
 | 
			
		||||
	"type": "module",
 | 
			
		||||
	"source": "src/library.ts",
 | 
			
		||||
	"source": "src/index.ts",
 | 
			
		||||
	"types": "lib/index.d.ts",
 | 
			
		||||
	"main": "lib/index.js",
 | 
			
		||||
	"files": [
 | 
			
		||||
		"lib/**/*"
 | 
			
		||||
	],
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@eslint/js": "^9.30.0",
 | 
			
		||||
		"@types/node": "^24.0.3",
 | 
			
		||||
		"@vitest/coverage-v8": "^3.2.4",
 | 
			
		||||
		"eslint": "^9.30.0",
 | 
			
		||||
		"globals": "^16.2.0",
 | 
			
		||||
		"prettier": "^3.6.0",
 | 
			
		||||
		"@types/jest": "^29.5.13",
 | 
			
		||||
		"@types/node": "^22.7.4",
 | 
			
		||||
		"jest": "^29.7.0",
 | 
			
		||||
		"ts-jest": "^29.2.5",
 | 
			
		||||
		"ts-node": "^10.9.2",
 | 
			
		||||
		"typescript": "^5.8.3",
 | 
			
		||||
		"typescript-eslint": "^8.35.0",
 | 
			
		||||
		"vite": "^7.0.0",
 | 
			
		||||
		"vite-plugin-dts": "^4.5.4",
 | 
			
		||||
		"vitest": "^3.2.4"
 | 
			
		||||
		"typescript": "^5.6.2",
 | 
			
		||||
		"vite": "^5.4.8",
 | 
			
		||||
		"vite-plugin-dts": "^4.2.2"
 | 
			
		||||
	},
 | 
			
		||||
	"packageManager": "yarn@4.9.2"
 | 
			
		||||
	"packageManager": "yarn@4.5.0"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										236
									
								
								src/Model/Model.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								src/Model/Model.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,236 @@
 | 
			
		|||
import {Definition} from "./PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type definition of a model constructor.
 | 
			
		||||
 */
 | 
			
		||||
export type ConstructorOf<T extends object> = { new(): T; };
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unknown property definition.
 | 
			
		||||
 */
 | 
			
		||||
export type UnknownDefinition = Definition<unknown, unknown>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model shape.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelShape = Record<string, UnknownDefinition>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Properties of a model based on its shape.
 | 
			
		||||
 */
 | 
			
		||||
export type PropertiesModel<Shape extends ModelShape> = {
 | 
			
		||||
	[k in keyof Shape]: Shape[k]["_sharkitek"];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialized object type based on model shape.
 | 
			
		||||
 */
 | 
			
		||||
export type SerializedModel<Shape extends ModelShape> = {
 | 
			
		||||
	[k in keyof Shape]: Shape[k]["_serialized"];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a model object.
 | 
			
		||||
 */
 | 
			
		||||
export type Model<Shape extends ModelShape, IdentifierType = unknown> = ModelDefinition<Shape, IdentifierType> & PropertiesModel<Shape>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a model class.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelClass<Shape extends ModelShape, Identifier extends keyof Shape = any> = ConstructorOf<Model<Shape, IdentifierType<Shape, Identifier>>>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Identifier type.
 | 
			
		||||
 */
 | 
			
		||||
export type IdentifierType<Shape extends ModelShape, K extends keyof Shape> = Shape[K]["_sharkitek"];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface of a Sharkitek model definition.
 | 
			
		||||
 */
 | 
			
		||||
export interface ModelDefinition<Shape extends ModelShape, IdentifierType, ModelType extends Model<Shape, IdentifierType> = Model<Shape, IdentifierType>>
 | 
			
		||||
{
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get model identifier.
 | 
			
		||||
	 */
 | 
			
		||||
	getIdentifier(): IdentifierType;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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()`.
 | 
			
		||||
	 */
 | 
			
		||||
	save(): Partial<SerializedModel<Shape>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Define a Sharkitek model.
 | 
			
		||||
 * @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 keyof Shape = any>(
 | 
			
		||||
	shape: Shape,
 | 
			
		||||
	identifier?: Identifier,
 | 
			
		||||
): ConstructorOf<ModelType>
 | 
			
		||||
{
 | 
			
		||||
	// Get shape entries.
 | 
			
		||||
	const shapeEntries = Object.entries(shape) as [keyof Shape, UnknownDefinition][];
 | 
			
		||||
 | 
			
		||||
	return class GenericModel implements ModelDefinition<Shape, IdentifierType<Shape, Identifier>, ModelType>
 | 
			
		||||
	{
 | 
			
		||||
		constructor()
 | 
			
		||||
		{
 | 
			
		||||
			// Initialize properties to undefined.
 | 
			
		||||
			Object.assign(this,
 | 
			
		||||
				// Build empty properties model from shape entries.
 | 
			
		||||
				Object.fromEntries(shapeEntries.map(([key]) => [key, undefined])) as PropertiesModel<Shape>
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * Calling a function for each defined property.
 | 
			
		||||
		 * @param callback - The function to call.
 | 
			
		||||
		 * @protected
 | 
			
		||||
		 */
 | 
			
		||||
		protected forEachModelProperty<ReturnType>(callback: (propertyName: keyof Shape, propertyDefinition: UnknownDefinition) => ReturnType): ReturnType
 | 
			
		||||
		{
 | 
			
		||||
			for (const [propertyName, propertyDefinition] of shapeEntries)
 | 
			
		||||
			{ // For each property, checking that its type is defined and calling the callback with its type.
 | 
			
		||||
				// If the property is defined, calling the function with the property name and definition.
 | 
			
		||||
				const result = callback(propertyName, propertyDefinition);
 | 
			
		||||
				// If there is a return value, returning it directly (loop is broken).
 | 
			
		||||
				if (typeof result !== "undefined") return result;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * The original properties values.
 | 
			
		||||
		 * @protected
 | 
			
		||||
		 */
 | 
			
		||||
		protected _originalProperties: Partial<PropertiesModel<Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * The original (serialized) object.
 | 
			
		||||
		 * @protected
 | 
			
		||||
		 */
 | 
			
		||||
		protected _originalObject: SerializedModel<Shape>|null = null;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		getIdentifier(): IdentifierType<Shape, Identifier>
 | 
			
		||||
		{
 | 
			
		||||
			return (this as PropertiesModel<Shape>)?.[identifier];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		serialize(): SerializedModel<Shape>
 | 
			
		||||
		{
 | 
			
		||||
			// Creating an empty (=> partial) serialized object.
 | 
			
		||||
			const serializedObject: Partial<SerializedModel<Shape>> = {};
 | 
			
		||||
 | 
			
		||||
			this.forEachModelProperty((propertyName, propertyDefinition) => {
 | 
			
		||||
				// For each defined model property, adding it to the serialized object.
 | 
			
		||||
				serializedObject[propertyName] = propertyDefinition.type.serialize((this as PropertiesModel<Shape>)?.[propertyName]);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return serializedObject as SerializedModel<Shape>; // Returning the serialized object.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		deserialize(obj: SerializedModel<Shape>): 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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		isNew(): boolean
 | 
			
		||||
		{
 | 
			
		||||
			return !this._originalObject;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		isDirty(): boolean
 | 
			
		||||
		{
 | 
			
		||||
			return this.forEachModelProperty((propertyName, propertyDefinition) => (
 | 
			
		||||
				// For each property, checking if it is different.
 | 
			
		||||
				propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as PropertiesModel<Shape>)[propertyName])
 | 
			
		||||
					// There is a difference, we should return false.
 | 
			
		||||
					? true
 | 
			
		||||
					// There is no difference, returning nothing.
 | 
			
		||||
					: undefined
 | 
			
		||||
			)) === true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		serializeDiff(): Partial<SerializedModel<Shape>>
 | 
			
		||||
		{
 | 
			
		||||
			// Creating an empty (=> partial) serialized object.
 | 
			
		||||
			const serializedObject: Partial<SerializedModel<Shape>> = {};
 | 
			
		||||
 | 
			
		||||
			this.forEachModelProperty((propertyName, propertyDefinition) => {
 | 
			
		||||
				// For each defined model property, adding it to the serialized object if it has changed or if it is the identifier.
 | 
			
		||||
				if (
 | 
			
		||||
					identifier == propertyName ||
 | 
			
		||||
					propertyDefinition.type.propertyHasChanged(this._originalProperties[propertyName], (this as PropertiesModel<Shape>)[propertyName])
 | 
			
		||||
				) // Adding the current property to the serialized object if it is the identifier or its value has changed.
 | 
			
		||||
					serializedObject[propertyName] = propertyDefinition.type.serializeDiff((this as PropertiesModel<Shape>)?.[propertyName]);
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			return serializedObject; // Returning the serialized object.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		resetDiff(): void
 | 
			
		||||
		{
 | 
			
		||||
			this.forEachModelProperty((propertyName, propertyDefinition) => {
 | 
			
		||||
				// For each property, set its original value to its current property value.
 | 
			
		||||
				this._originalProperties[propertyName] = (this as PropertiesModel<Shape>)[propertyName];
 | 
			
		||||
				propertyDefinition.type.resetDiff((this as PropertiesModel<Shape>)[propertyName]);
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		save(): Partial<SerializedModel<Shape>>
 | 
			
		||||
		{
 | 
			
		||||
			// Get the difference.
 | 
			
		||||
			const diff = this.serializeDiff();
 | 
			
		||||
 | 
			
		||||
			// Once the difference has been obtained, reset it.
 | 
			
		||||
			this.resetDiff();
 | 
			
		||||
 | 
			
		||||
			return diff; // Return the difference.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	} as unknown as ConstructorOf<ModelType>;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/Model/Properties.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/Model/Properties.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,10 @@
 | 
			
		|||
export {define} from "./PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
export {array} from "./Types/ArrayType";
 | 
			
		||||
export {bool, boolean} from "./Types/BoolType";
 | 
			
		||||
export {date} from "./Types/DateType";
 | 
			
		||||
export {decimal} from "./Types/DecimalType";
 | 
			
		||||
export {model} from "./Types/ModelType";
 | 
			
		||||
export {numeric} from "./Types/NumericType";
 | 
			
		||||
export {object} from "./Types/ObjectType";
 | 
			
		||||
export {string} from "./Types/StringType";
 | 
			
		||||
							
								
								
									
										26
									
								
								src/Model/PropertyDefinition.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Model/PropertyDefinition.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import {Type} from "./Types/Type";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Property definition class.
 | 
			
		||||
 */
 | 
			
		||||
export class Definition<SerializedType, ModelType>
 | 
			
		||||
{
 | 
			
		||||
	readonly _sharkitek: ModelType;
 | 
			
		||||
	readonly _serialized: SerializedType;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a property definer instance.
 | 
			
		||||
	 * @param type Property type.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(public readonly type: Type<SerializedType, ModelType>)
 | 
			
		||||
	{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New definition of a property of the given type.
 | 
			
		||||
 * @param type Type of the property to define.
 | 
			
		||||
 */
 | 
			
		||||
export function define<SerializedType, ModelType>(type: Type<SerializedType, ModelType>): Definition<SerializedType, ModelType>
 | 
			
		||||
{
 | 
			
		||||
	return new Definition(type);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								src/Model/Types/ArrayType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Model/Types/ArrayType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,66 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of an array of values.
 | 
			
		||||
 */
 | 
			
		||||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<SerializedValueType[], SharkitekValueType[]>
 | 
			
		||||
{
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new array type of a Sharkitek model property.
 | 
			
		||||
	 * @param valueDefinition Definition the array values.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(protected valueDefinition: Definition<SerializedValueType, SharkitekValueType>)
 | 
			
		||||
	{
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return value.map((value) => (
 | 
			
		||||
			// Serializing each value of the array.
 | 
			
		||||
			this.valueDefinition.type.serialize(value)
 | 
			
		||||
		));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deserialize(value: SerializedValueType[]|null|undefined): SharkitekValueType[]|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return value.map((serializedValue) => (
 | 
			
		||||
			// Deserializing each value of the array.
 | 
			
		||||
			this.valueDefinition.type.deserialize(serializedValue)
 | 
			
		||||
		));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializeDiff(value: SharkitekValueType[]|null|undefined): SerializedValueType[]|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		// Serializing diff of all elements.
 | 
			
		||||
		return value.map((value) => this.valueDefinition.type.serializeDiff(value) as SerializedValueType);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resetDiff(value: SharkitekValueType[]|null|undefined): void
 | 
			
		||||
	{
 | 
			
		||||
		// Do nothing if it is not an array.
 | 
			
		||||
		if (!Array.isArray(value)) return;
 | 
			
		||||
 | 
			
		||||
		// Reset diff of all elements.
 | 
			
		||||
		value.forEach((value) => this.valueDefinition.type.resetDiff(value));
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New array property definition.
 | 
			
		||||
 * @param valueDefinition Array values type definition.
 | 
			
		||||
 */
 | 
			
		||||
export function array<SerializedValueType, SharkitekValueType>(valueDefinition: Definition<SerializedValueType, SharkitekValueType>): Definition<SerializedValueType[], SharkitekValueType[]>
 | 
			
		||||
{
 | 
			
		||||
	return define(new ArrayType(valueDefinition));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								src/Model/Types/BoolType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/Model/Types/BoolType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,42 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any boolean value.
 | 
			
		||||
 */
 | 
			
		||||
export class BoolType extends Type<boolean, boolean>
 | 
			
		||||
{
 | 
			
		||||
	deserialize(value: boolean|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();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/Model/Types/DateType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Model/Types/DateType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of dates.
 | 
			
		||||
 */
 | 
			
		||||
export class DateType extends Type<string, Date>
 | 
			
		||||
{
 | 
			
		||||
	deserialize(value: string|null|undefined): Date|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return new Date(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: Date|null|undefined): string|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return value?.toISOString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New date property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function date(): Definition<string, Date>
 | 
			
		||||
{
 | 
			
		||||
	return define(new DateType());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/Model/Types/DecimalType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/Model/Types/DecimalType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,32 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of decimal numbers.
 | 
			
		||||
 */
 | 
			
		||||
export class DecimalType extends Type<string, number>
 | 
			
		||||
{
 | 
			
		||||
	deserialize(value: string|null|undefined): number|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return parseFloat(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: number|null|undefined): string|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return value?.toString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New decimal property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function decimal(): Definition<string, number>
 | 
			
		||||
{
 | 
			
		||||
	return define(new DecimalType());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								src/Model/Types/ModelType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/Model/Types/ModelType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,60 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
import {ConstructorOf, Model, ModelShape, SerializedModel} from "../Model";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a Sharkitek model value.
 | 
			
		||||
 */
 | 
			
		||||
export class ModelType<Shape extends ModelShape> extends Type<SerializedModel<Shape>, Model<Shape>>
 | 
			
		||||
{
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new model type of a Sharkitek model property.
 | 
			
		||||
	 * @param modelConstructor Model constructor.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(protected modelConstructor: ConstructorOf<Model<Shape>>)
 | 
			
		||||
	{
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: Model<Shape>|null|undefined): SerializedModel<Shape>|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		// Serializing the given model.
 | 
			
		||||
		return value?.serialize();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deserialize(value: SerializedModel<Shape>|null|undefined): Model<Shape>|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		// Deserializing the given object in the new model.
 | 
			
		||||
		return (new this.modelConstructor()).deserialize(value) as Model<Shape>;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializeDiff(value: Model<Shape>|null|undefined): Partial<SerializedModel<Shape>>|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		// Serializing the given model.
 | 
			
		||||
		return value?.serializeDiff();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resetDiff(value: Model<Shape>|null|undefined): void
 | 
			
		||||
	{
 | 
			
		||||
		// Reset diff of the given model.
 | 
			
		||||
		value?.resetDiff();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New model property definition.
 | 
			
		||||
 * @param modelConstructor Model constructor.
 | 
			
		||||
 */
 | 
			
		||||
export function model<Shape extends ModelShape>(modelConstructor: ConstructorOf<Model<Shape>>): Definition<SerializedModel<Shape>, Model<Shape>>
 | 
			
		||||
{
 | 
			
		||||
	return define(new ModelType(modelConstructor));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/Model/Types/NumericType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Model/Types/NumericType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any numeric value.
 | 
			
		||||
 */
 | 
			
		||||
export class NumericType extends Type<number, number>
 | 
			
		||||
{
 | 
			
		||||
	deserialize(value: number|null|undefined): number|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: number|null|undefined): number|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New numeric property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function numeric(): Definition<number, number>
 | 
			
		||||
{
 | 
			
		||||
	return define(new NumericType());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								src/Model/Types/ObjectType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/Model/Types/ObjectType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,78 @@
 | 
			
		|||
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]);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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));
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/Model/Types/StringType.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/Model/Types/StringType.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
import {Type} from "./Type";
 | 
			
		||||
import {define, Definition} from "../PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any string value.
 | 
			
		||||
 */
 | 
			
		||||
export class StringType extends Type<string, string>
 | 
			
		||||
{
 | 
			
		||||
	deserialize(value: string|null|undefined): string|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: string|null|undefined): string|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New string property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function string(): Definition<string, string>
 | 
			
		||||
{
 | 
			
		||||
	return define(new StringType());
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/Model/Types/Type.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/Model/Types/Type.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,55 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Abstract class of a Sharkitek model property type.
 | 
			
		||||
 */
 | 
			
		||||
export abstract class Type<SerializedType, ModelType>
 | 
			
		||||
{
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the given value of a Sharkitek model property.
 | 
			
		||||
	 * @param value Value to serialize.
 | 
			
		||||
	 */
 | 
			
		||||
	abstract serialize(value: ModelType|null|undefined): SerializedType|null|undefined;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deserialize the given value of a serialized Sharkitek model.
 | 
			
		||||
	 * @param value - Value to deserialize.
 | 
			
		||||
	 */
 | 
			
		||||
	abstract deserialize(value: SerializedType|null|undefined): ModelType|null|undefined;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the given value only if it has changed.
 | 
			
		||||
	 * @param value - Value to deserialize.
 | 
			
		||||
	 */
 | 
			
		||||
	serializeDiff(value: ModelType|null|undefined): Partial<SerializedType>|null|undefined
 | 
			
		||||
	{
 | 
			
		||||
		return this.serialize(value); // By default, nothing changes.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Reset the difference between the original value and the current one.
 | 
			
		||||
	 * @param value - Value for which reset diff data.
 | 
			
		||||
	 */
 | 
			
		||||
	resetDiff(value: ModelType|null|undefined): void
 | 
			
		||||
	{
 | 
			
		||||
		// By default, nothing to do.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine if the property value has changed.
 | 
			
		||||
	 * @param originalValue - Original property value.
 | 
			
		||||
	 * @param currentValue - Current property value.
 | 
			
		||||
	 */
 | 
			
		||||
	propertyHasChanged(originalValue: ModelType|null|undefined, currentValue: ModelType|null|undefined): boolean
 | 
			
		||||
	{
 | 
			
		||||
		return originalValue != currentValue;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine if the serialized property value has changed.
 | 
			
		||||
	 * @param originalValue - Original serialized property value.
 | 
			
		||||
	 * @param currentValue - Current serialized property value.
 | 
			
		||||
	 */
 | 
			
		||||
	serializedPropertyHasChanged(originalValue: SerializedType|null|undefined, currentValue: SerializedType|null|undefined): boolean
 | 
			
		||||
	{
 | 
			
		||||
		return originalValue != currentValue;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								src/Model/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Model/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
import * as property from "./Properties";
 | 
			
		||||
export { property };
 | 
			
		||||
 | 
			
		||||
export * from "./Model";
 | 
			
		||||
export {Definition} from "./PropertyDefinition";
 | 
			
		||||
 | 
			
		||||
export {ArrayType} from "./Types/ArrayType";
 | 
			
		||||
export {BoolType} from "./Types/BoolType";
 | 
			
		||||
export {DateType} from "./Types/DateType";
 | 
			
		||||
export {DecimalType} from "./Types/DecimalType";
 | 
			
		||||
export {ModelType} from "./Types/ModelType";
 | 
			
		||||
export {NumericType} from "./Types/NumericType";
 | 
			
		||||
export {ObjectType} from "./Types/ObjectType";
 | 
			
		||||
export {StringType} from "./Types/StringType";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +0,0 @@
 | 
			
		|||
export * from "./sharkitek-error";
 | 
			
		||||
export * from "./type-error";
 | 
			
		||||
export * from "./invalid-type-value-error";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,18 +0,0 @@
 | 
			
		|||
import {TypeError} from "./type-error";
 | 
			
		||||
import {Type} from "../model/types/type";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Sharkitek type error when the passed value is invalid.
 | 
			
		||||
 */
 | 
			
		||||
export class InvalidTypeValueError<SerializedType, ModelType> extends TypeError<
 | 
			
		||||
	SerializedType,
 | 
			
		||||
	ModelType
 | 
			
		||||
> {
 | 
			
		||||
	constructor(
 | 
			
		||||
		public type: Type<SerializedType, ModelType>,
 | 
			
		||||
		public value: any,
 | 
			
		||||
		message?: string,
 | 
			
		||||
	) {
 | 
			
		||||
		super(type, message ?? `${JSON.stringify(value)} is an invalid value`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * A Sharkitek error.
 | 
			
		||||
 */
 | 
			
		||||
export class SharkitekError extends Error {}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
import {SharkitekError} from "./sharkitek-error";
 | 
			
		||||
import {Type} from "../model/types/type";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Sharkitek type error.
 | 
			
		||||
 */
 | 
			
		||||
export class TypeError<SerializedType, ModelType> extends SharkitekError {
 | 
			
		||||
	constructor(
 | 
			
		||||
		public type: Type<SerializedType, ModelType>,
 | 
			
		||||
		message?: string,
 | 
			
		||||
	) {
 | 
			
		||||
		super(
 | 
			
		||||
			`Error in type ${type.constructor.name}${message ? `: ${message}` : ""}`,
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										4
									
								
								src/index.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/index.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
import * as s from "./Model";
 | 
			
		||||
export * from "./Model";
 | 
			
		||||
export { s };
 | 
			
		||||
export default s;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +0,0 @@
 | 
			
		|||
import * as s from "./model";
 | 
			
		||||
export * from "./model";
 | 
			
		||||
export * from "./errors";
 | 
			
		||||
export {s};
 | 
			
		||||
export default s;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,77 +0,0 @@
 | 
			
		|||
import {
 | 
			
		||||
	defineModel,
 | 
			
		||||
	IdentifierDefinition,
 | 
			
		||||
	ModelDefinition,
 | 
			
		||||
	ModelShape,
 | 
			
		||||
} from "./model";
 | 
			
		||||
import {ConstructorOf} from "../utils";
 | 
			
		||||
import {Definition} from "./property-definition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Model definition builder.
 | 
			
		||||
 */
 | 
			
		||||
export class ModelBuilder<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The built model definition.
 | 
			
		||||
	 */
 | 
			
		||||
	definition: ModelDefinition<T, Shape, Identifier>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Define a new property.
 | 
			
		||||
	 * @param name The new property name.
 | 
			
		||||
	 * @param definition The new property definition.
 | 
			
		||||
	 */
 | 
			
		||||
	property<
 | 
			
		||||
		SerializedType,
 | 
			
		||||
		PropertyName extends Exclude<keyof T, keyof Shape>,
 | 
			
		||||
		PropertyDefinition extends Definition<SerializedType, T[PropertyName]>,
 | 
			
		||||
	>(name: PropertyName, definition: PropertyDefinition) {
 | 
			
		||||
		(this.definition.properties[name] as Definition<unknown, T[typeof name]>) =
 | 
			
		||||
			definition;
 | 
			
		||||
 | 
			
		||||
		return this as unknown as ModelBuilder<
 | 
			
		||||
			T,
 | 
			
		||||
			Shape & {[k in PropertyName]: PropertyDefinition},
 | 
			
		||||
			Identifier
 | 
			
		||||
		>;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set the model identifier.
 | 
			
		||||
	 * @param identifier The new model identifier.
 | 
			
		||||
	 */
 | 
			
		||||
	identifier<NewIdentifier extends IdentifierDefinition<T, Shape>>(
 | 
			
		||||
		identifier: NewIdentifier,
 | 
			
		||||
	) {
 | 
			
		||||
		(this.definition.identifier as unknown) = identifier;
 | 
			
		||||
		return this as unknown as ModelBuilder<T, Shape, NewIdentifier>;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Define a model using the current model definition.
 | 
			
		||||
	 */
 | 
			
		||||
	define() {
 | 
			
		||||
		return defineModel(this.definition);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Initialize a model builder for the provided class.
 | 
			
		||||
 * @param Class The class for which to build a model.
 | 
			
		||||
 */
 | 
			
		||||
export function newModel<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T> = object,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape> = never,
 | 
			
		||||
>(Class: ConstructorOf<T>): ModelBuilder<T, Shape, Identifier> {
 | 
			
		||||
	const builder = new ModelBuilder<T, Shape, Identifier>();
 | 
			
		||||
	builder.definition = {
 | 
			
		||||
		Class,
 | 
			
		||||
		properties: {} as Shape,
 | 
			
		||||
	};
 | 
			
		||||
	return builder;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
export * as property from "./properties";
 | 
			
		||||
 | 
			
		||||
export * from "./model";
 | 
			
		||||
export {Definition} from "./property-definition";
 | 
			
		||||
export {newModel, ModelBuilder} from "./builder";
 | 
			
		||||
 | 
			
		||||
export {ArrayType} from "./types/array";
 | 
			
		||||
export {BooleanType} from "./types/boolean";
 | 
			
		||||
export {DateType} from "./types/date";
 | 
			
		||||
export {DecimalType} from "./types/decimal";
 | 
			
		||||
export {ModelType} from "./types/model";
 | 
			
		||||
export {NumericType} from "./types/numeric";
 | 
			
		||||
export {ObjectType} from "./types/object";
 | 
			
		||||
export {StringType} from "./types/string";
 | 
			
		||||
 | 
			
		||||
export {circular} from "./types/model";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,667 +0,0 @@
 | 
			
		|||
import {Definition, UnknownDefinition} from "./property-definition";
 | 
			
		||||
import {ConstructorOf, Modify} from "../utils";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model shape.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelShape<T extends object> = Partial<{
 | 
			
		||||
	[k in keyof T]: Definition<unknown, T[k]>;
 | 
			
		||||
}>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Properties values of a model based on its shape.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelPropertiesValues<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = {
 | 
			
		||||
	[k in keyof Shape]: Shape[k]["_sharkitek"];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Serialized object type based on model shape.
 | 
			
		||||
 */
 | 
			
		||||
export type SerializedModel<T extends object, Shape extends ModelShape<T>> = {
 | 
			
		||||
	[k in keyof Shape]?: Shape[k]["_serialized"];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This is an experimental serialized model type declaration.
 | 
			
		||||
 * @deprecated
 | 
			
		||||
 */
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
type ExperimentalSerializedModel<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = Omit<
 | 
			
		||||
	ExperimentalSerializedModelBase<T, Shape>,
 | 
			
		||||
	ExperimentalSerializedModelOptionalKeys<T, Shape>
 | 
			
		||||
> &
 | 
			
		||||
	Pick<
 | 
			
		||||
		Partial<ExperimentalSerializedModelBase<T, Shape>>,
 | 
			
		||||
		ExperimentalSerializedModelOptionalKeys<T, Shape>
 | 
			
		||||
	>;
 | 
			
		||||
type ExperimentalSerializedModelBase<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = {
 | 
			
		||||
	[k in keyof Shape]: Shape[k]["_serialized"];
 | 
			
		||||
};
 | 
			
		||||
type ExperimentalSerializedModelOptionalKeys<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = {
 | 
			
		||||
	[k in keyof Shape]: Shape[k]["_serialized"] extends undefined ? k : never;
 | 
			
		||||
}[keyof Shape];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A sharkitek model instance, with internal model state.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelInstance<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> = T & {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The Sharkitek model state.
 | 
			
		||||
	 */
 | 
			
		||||
	_sharkitek: Model<T, Shape, Identifier>;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Identifier definition type.
 | 
			
		||||
 */
 | 
			
		||||
export type IdentifierDefinition<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = keyof Shape | (keyof Shape)[];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Identifier type.
 | 
			
		||||
 */
 | 
			
		||||
export type IdentifierType<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> = Identifier extends keyof Shape
 | 
			
		||||
	? Shape[Identifier]["_sharkitek"]
 | 
			
		||||
	: {
 | 
			
		||||
			[K in keyof Identifier]: Identifier[K] extends keyof Shape
 | 
			
		||||
				? Shape[Identifier[K]]["_sharkitek"]
 | 
			
		||||
				: unknown;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model definition object.
 | 
			
		||||
 */
 | 
			
		||||
export interface ModelDefinition<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Model class.
 | 
			
		||||
	 */
 | 
			
		||||
	Class: ConstructorOf<T>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Properties names of the model identifier.
 | 
			
		||||
	 * Can be a single of a composite identifier.
 | 
			
		||||
	 */
 | 
			
		||||
	identifier?: Identifier;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Model properties definition.
 | 
			
		||||
	 * Set properties types (serialized and deserialized).
 | 
			
		||||
	 */
 | 
			
		||||
	properties: Shape;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model property.
 | 
			
		||||
 */
 | 
			
		||||
export interface ModelProperty<T extends object, Shape extends ModelShape<T>> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Property name.
 | 
			
		||||
	 */
 | 
			
		||||
	name: keyof Shape;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Property definition.
 | 
			
		||||
	 */
 | 
			
		||||
	definition: UnknownDefinition;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set if the property is part of the identifier or not.
 | 
			
		||||
	 */
 | 
			
		||||
	identifier: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Model properties iterator object.
 | 
			
		||||
 */
 | 
			
		||||
export type ModelProperties<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
> = ModelProperty<T, Shape>[];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Sharkitek model state.
 | 
			
		||||
 */
 | 
			
		||||
export class Model<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * The model manager instance.
 | 
			
		||||
	 */
 | 
			
		||||
	readonly manager: ModelManager<T, Shape, Identifier>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The model definition object.
 | 
			
		||||
	 */
 | 
			
		||||
	readonly definition: ModelDefinition<T, Shape, Identifier>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Iterable properties.
 | 
			
		||||
	 */
 | 
			
		||||
	readonly properties: ModelProperties<T, Shape>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The actual instance of the model.
 | 
			
		||||
	 */
 | 
			
		||||
	instance: ModelInstance<T, Shape, Identifier>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Original values, to keep track of the changes on the model properties.
 | 
			
		||||
	 * @protected
 | 
			
		||||
	 */
 | 
			
		||||
	protected original: {
 | 
			
		||||
		/**
 | 
			
		||||
		 * The original properties values.
 | 
			
		||||
		 */
 | 
			
		||||
		properties: Partial<ModelPropertiesValues<T, Shape>>;
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * The original serialized object, if there is one.
 | 
			
		||||
		 */
 | 
			
		||||
		serialized: SerializedModel<T, Shape> | null;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new model state with the defined properties.
 | 
			
		||||
	 * @param manager The model manager.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(manager: ModelManager<T, Shape, Identifier>) {
 | 
			
		||||
		this.manager = manager;
 | 
			
		||||
		this.definition = manager.definition;
 | 
			
		||||
		this.properties = manager.properties;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize the Sharkitek model state for a new instance.
 | 
			
		||||
	 */
 | 
			
		||||
	initInstance(): this {
 | 
			
		||||
		return this.fromInstance(
 | 
			
		||||
			// Initialize a new model instance.
 | 
			
		||||
			new this.definition.Class(),
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize the Sharkitek model state for the provided instance.
 | 
			
		||||
	 * @param instance The model instance.
 | 
			
		||||
	 */
 | 
			
		||||
	fromInstance(instance: T): this {
 | 
			
		||||
		// Initialize the sharkitek model instance.
 | 
			
		||||
		const sharkitekInstance = instance as ModelInstance<T, Shape, Identifier>;
 | 
			
		||||
 | 
			
		||||
		// Keep the original instance, if it exists.
 | 
			
		||||
		const originalInstance = sharkitekInstance._sharkitek;
 | 
			
		||||
 | 
			
		||||
		// Set references to instance / model state.
 | 
			
		||||
		sharkitekInstance._sharkitek = this;
 | 
			
		||||
		this.instance = sharkitekInstance;
 | 
			
		||||
 | 
			
		||||
		if (originalInstance)
 | 
			
		||||
			// Share the same original values object.
 | 
			
		||||
			this.original = originalInstance.original;
 | 
			
		||||
		else {
 | 
			
		||||
			// Initialize a new original values object, based on the current values of the instance.
 | 
			
		||||
			this.original = {
 | 
			
		||||
				properties: undefined,
 | 
			
		||||
				serialized: null,
 | 
			
		||||
			};
 | 
			
		||||
			this.resetDiff();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deserialize provided data to a new model instance.
 | 
			
		||||
	 * @param serialized Serialized model.
 | 
			
		||||
	 */
 | 
			
		||||
	deserialize(serialized: SerializedModel<T, Shape>): this {
 | 
			
		||||
		// Initialize a new model instance.
 | 
			
		||||
		this.initInstance();
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined model property, assigning its deserialized value.
 | 
			
		||||
			(this.instance[property.name as keyof T] as any) =
 | 
			
		||||
				property.definition.type.deserialize(serialized[property.name]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Reset original property values.
 | 
			
		||||
		this.resetDiff();
 | 
			
		||||
		// Store the original serialized object.
 | 
			
		||||
		this.original.serialized = serialized;
 | 
			
		||||
 | 
			
		||||
		return this;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get current model instance identifier.
 | 
			
		||||
	 */
 | 
			
		||||
	getIdentifier(): IdentifierType<T, Shape, Identifier> {
 | 
			
		||||
		if (Array.isArray(this.definition.identifier)) {
 | 
			
		||||
			// The identifier is composite, make an array of properties values.
 | 
			
		||||
			return this.definition.identifier.map(
 | 
			
		||||
				(identifier) => this.instance?.[identifier as keyof T],
 | 
			
		||||
			) as IdentifierType<T, Shape, Identifier>;
 | 
			
		||||
		} else {
 | 
			
		||||
			// The identifier is a simple property, get its value.
 | 
			
		||||
			return this.instance?.[
 | 
			
		||||
				this.definition.identifier as keyof Shape as keyof T
 | 
			
		||||
			] as IdentifierType<T, Shape, Identifier>;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get current model instance properties.
 | 
			
		||||
	 */
 | 
			
		||||
	getInstanceProperties(): ModelPropertiesValues<T, Shape> {
 | 
			
		||||
		// Initialize an empty model properties object.
 | 
			
		||||
		const instanceProperties: Partial<ModelPropertiesValues<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined model property, adding it to the properties object.
 | 
			
		||||
			instanceProperties[property.name] =
 | 
			
		||||
				this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return instanceProperties as ModelPropertiesValues<T, Shape>; // Returning the properties object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the model instance.
 | 
			
		||||
	 */
 | 
			
		||||
	serialize(): SerializedModel<T, Shape> {
 | 
			
		||||
		// Creating an empty serialized object.
 | 
			
		||||
		const serializedObject: Partial<SerializedModel<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined model property, adding it to the serialized object.
 | 
			
		||||
			serializedObject[property.name] = property.definition.type.serialize(
 | 
			
		||||
				// keyof Shape is a subset of keyof T.
 | 
			
		||||
				this.instance?.[property.name as keyof T],
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Examine if the model is new (never deserialized) or not.
 | 
			
		||||
	 */
 | 
			
		||||
	isNew(): boolean {
 | 
			
		||||
		return !this.original.serialized;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Examine if the model is dirty or not.
 | 
			
		||||
	 */
 | 
			
		||||
	isDirty(): boolean {
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each property, check if it is different.
 | 
			
		||||
			if (
 | 
			
		||||
				property.definition.type.hasChanged(
 | 
			
		||||
					this.original.properties?.[property.name],
 | 
			
		||||
					this.instance?.[property.name as keyof T],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				// There is a difference: the model is dirty.
 | 
			
		||||
				return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// No difference.
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the difference between current model state and the original one.
 | 
			
		||||
	 */
 | 
			
		||||
	serializeDiff(): Partial<SerializedModel<T, Shape>> {
 | 
			
		||||
		// Creating an empty serialized object.
 | 
			
		||||
		const serializedObject: Partial<SerializedModel<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined model property, adding it to the serialized object if it has changed or if it is in the identifier.
 | 
			
		||||
			const instancePropValue = this.instance?.[property.name as keyof T]; // keyof Shape is a subset of keyof T.
 | 
			
		||||
			if (
 | 
			
		||||
				property.identifier ||
 | 
			
		||||
				property.definition.type.hasChanged(
 | 
			
		||||
					this.original.properties?.[property.name],
 | 
			
		||||
					instancePropValue,
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				// The property is part of the identifier or its value has changed.
 | 
			
		||||
				serializedObject[property.name] =
 | 
			
		||||
					property.definition.type.serializeDiff(instancePropValue);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return serializedObject; // Returning the serialized object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Set current model properties as original values.
 | 
			
		||||
	 */
 | 
			
		||||
	resetDiff(): void {
 | 
			
		||||
		this.original.properties = {};
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each property, set its original value to the current property value.
 | 
			
		||||
			const instancePropValue = this.instance?.[property.name as keyof T];
 | 
			
		||||
			this.original.properties[property.name] =
 | 
			
		||||
				property.definition.type.clone(instancePropValue);
 | 
			
		||||
			property.definition.type.resetDiff(instancePropValue);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get difference between original values and current ones, then reset it.
 | 
			
		||||
	 * Similar to call `serializeDiff()` then `resetDiff()`.
 | 
			
		||||
	 */
 | 
			
		||||
	patch(): Partial<SerializedModel<T, Shape>> {
 | 
			
		||||
		// Get the difference.
 | 
			
		||||
		const diff = this.serializeDiff();
 | 
			
		||||
 | 
			
		||||
		// Once the difference has been obtained, reset it.
 | 
			
		||||
		this.resetDiff();
 | 
			
		||||
 | 
			
		||||
		return diff; // Return the difference.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Clone the model instance.
 | 
			
		||||
	 */
 | 
			
		||||
	clone(): ModelInstance<T, Shape, Identifier> {
 | 
			
		||||
		// Initialize a new instance for the clone.
 | 
			
		||||
		const cloned = this.manager.model();
 | 
			
		||||
 | 
			
		||||
		// Clone every value of the model instance.
 | 
			
		||||
		for (const [key, value] of Object.entries(this.instance) as [
 | 
			
		||||
			keyof T,
 | 
			
		||||
			unknown,
 | 
			
		||||
		][]) {
 | 
			
		||||
			// For each [key, value], clone the value and put it in the cloned instance.
 | 
			
		||||
 | 
			
		||||
			// Do not clone ourselves.
 | 
			
		||||
			if (key == "_sharkitek") continue;
 | 
			
		||||
 | 
			
		||||
			if (this.definition.properties[key]) {
 | 
			
		||||
				// The current key is a defined property, clone using the defined type.
 | 
			
		||||
				(cloned.instance[key] as any) = (
 | 
			
		||||
					this.definition.properties[key] as UnknownDefinition
 | 
			
		||||
				).type.clone(value);
 | 
			
		||||
			} else {
 | 
			
		||||
				// Not a property, cloning the raw value.
 | 
			
		||||
				(cloned.instance[key] as any) = structuredClone(value);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Clone original properties.
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each property, clone its original value.
 | 
			
		||||
			cloned.original.properties[property.name] =
 | 
			
		||||
				property.definition.type.clone(this.original.properties[property.name]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Clone original serialized.
 | 
			
		||||
		cloned.original.serialized = structuredClone(this.original.serialized);
 | 
			
		||||
 | 
			
		||||
		return cloned.instance; // Returning the cloned instance.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Assign the provided fields to existing properties.
 | 
			
		||||
	 * Fields that cannot be matched to existing properties are silently ignored.
 | 
			
		||||
	 * @param fields The fields to assign to the model.
 | 
			
		||||
	 */
 | 
			
		||||
	assign(
 | 
			
		||||
		fields: Partial<ModelPropertiesValues<T, Shape>> & {[field: string]: any},
 | 
			
		||||
	): ModelInstance<T, Shape, Identifier> {
 | 
			
		||||
		for (const field in fields) {
 | 
			
		||||
			// For each field, if it's a property, assign its value.
 | 
			
		||||
			if ((this.definition.properties as any)?.[field])
 | 
			
		||||
				// Set the instance value.
 | 
			
		||||
				this.instance[field as keyof T] = fields[field];
 | 
			
		||||
		}
 | 
			
		||||
		return this.instance;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Apply a patch to the model instance. All known fields will be deserialized and assigned to the properties.
 | 
			
		||||
	 * @param patch The patch object to apply.
 | 
			
		||||
	 * @param updateOriginals Indicates if the original properties values must be updated or not. By default, they are reset.
 | 
			
		||||
	 */
 | 
			
		||||
	applyPatch(
 | 
			
		||||
		patch: SerializedModel<T, Shape>,
 | 
			
		||||
		updateOriginals: boolean = true,
 | 
			
		||||
	): ModelInstance<T, Shape, Identifier> {
 | 
			
		||||
		if (updateOriginals) {
 | 
			
		||||
			// If serialized original is null and we need to update it, initialize it.
 | 
			
		||||
			this.original.serialized = this.serialize();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (const serializedField in patch) {
 | 
			
		||||
			// For each field, if it's a property, assign its value.
 | 
			
		||||
			// Get the property definition.
 | 
			
		||||
			const property =
 | 
			
		||||
				this.definition.properties[serializedField as keyof Shape];
 | 
			
		||||
			if (property) {
 | 
			
		||||
				// Found a matching model property, assigning its deserialized value.
 | 
			
		||||
				(this.instance[serializedField as keyof Shape as keyof T] as any) = (
 | 
			
		||||
					property as UnknownDefinition
 | 
			
		||||
				).type.applyPatch(
 | 
			
		||||
					this.instance[serializedField as keyof Shape as keyof T],
 | 
			
		||||
					patch[serializedField],
 | 
			
		||||
					updateOriginals,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				if (updateOriginals) {
 | 
			
		||||
					// Update original values.
 | 
			
		||||
					// Set original property value.
 | 
			
		||||
					(this.original.properties[serializedField] as any) = (
 | 
			
		||||
						property as UnknownDefinition
 | 
			
		||||
					).type.clone(
 | 
			
		||||
						this.instance[serializedField as keyof Shape as keyof T],
 | 
			
		||||
					);
 | 
			
		||||
					// Set original serialized value.
 | 
			
		||||
					this.original.serialized[serializedField] = patch[serializedField];
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return this.instance;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model manager, created from a model definition.
 | 
			
		||||
 */
 | 
			
		||||
export class ModelManager<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Defined properties.
 | 
			
		||||
	 */
 | 
			
		||||
	properties: ModelProperties<T, Shape>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a model manager from a model definition.
 | 
			
		||||
	 * @param definition The model definition.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(
 | 
			
		||||
		public readonly definition: ModelDefinition<T, Shape, Identifier>,
 | 
			
		||||
	) {
 | 
			
		||||
		this.initProperties();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize properties iterator from current definition.
 | 
			
		||||
	 * @protected
 | 
			
		||||
	 */
 | 
			
		||||
	protected initProperties(): void {
 | 
			
		||||
		// Build an array of model properties from the definition.
 | 
			
		||||
		this.properties = [];
 | 
			
		||||
		for (const propertyName in this.definition.properties) {
 | 
			
		||||
			// For each property, build a model property object.
 | 
			
		||||
			this.properties.push({
 | 
			
		||||
				name: propertyName,
 | 
			
		||||
				definition: this.definition.properties[propertyName],
 | 
			
		||||
				// Find out if the current property is part of the identifier.
 | 
			
		||||
				identifier: Array.isArray(this.definition.identifier)
 | 
			
		||||
					? // The identifier is an array, the property must be in the array.
 | 
			
		||||
						this.definition.identifier.includes(
 | 
			
		||||
							propertyName as keyof Shape as keyof T,
 | 
			
		||||
						)
 | 
			
		||||
					: // The identifier is a single string, the property must be the defined identifier.
 | 
			
		||||
						this.definition.identifier == (propertyName as keyof Shape),
 | 
			
		||||
			} as ModelProperty<T, Shape>);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get the model state of the provided model instance.
 | 
			
		||||
	 * @param instance The model instance for which to get its state. NULL or undefined to create a new one.
 | 
			
		||||
	 */
 | 
			
		||||
	model(
 | 
			
		||||
		instance: T | ModelInstance<T, Shape, Identifier> | null = null,
 | 
			
		||||
	): Model<T, Shape, Identifier> {
 | 
			
		||||
		// Get the instance model state if there is one, or initialize a new one.
 | 
			
		||||
		if (instance)
 | 
			
		||||
			// There is an instance, create a model from it.
 | 
			
		||||
			return (
 | 
			
		||||
				(instance as ModelInstance<T, Shape, Identifier>)?._sharkitek ??
 | 
			
		||||
				new Model<T, Shape, Identifier>(this)
 | 
			
		||||
			).fromInstance(instance);
 | 
			
		||||
		else
 | 
			
		||||
			// No instance, initialize a new one.
 | 
			
		||||
			return new Model<T, Shape, Identifier>(this).initInstance();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new model instance with the provided object properties values.
 | 
			
		||||
	 * Fields that cannot be matched to existing properties are silently ignored.
 | 
			
		||||
	 * @param fields
 | 
			
		||||
	 */
 | 
			
		||||
	from(
 | 
			
		||||
		fields: Partial<ModelPropertiesValues<T, Shape>> & {[field: string]: any},
 | 
			
		||||
	): ModelInstance<T, Shape, Identifier> {
 | 
			
		||||
		return this.model().assign(fields);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Parse the serialized model object to a new model instance.
 | 
			
		||||
	 * @param serialized The serialized model object.
 | 
			
		||||
	 */
 | 
			
		||||
	parse(
 | 
			
		||||
		serialized: SerializedModel<T, Shape>,
 | 
			
		||||
	): ModelInstance<T, Shape, Identifier> {
 | 
			
		||||
		return this.model().deserialize(serialized).instance;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A model manager extension is a mixin, building a new model manager class with extended capabilities.
 | 
			
		||||
 * @see https://www.typescriptlang.org/docs/handbook/mixins.html
 | 
			
		||||
 */
 | 
			
		||||
export type ModelManagerExtension<
 | 
			
		||||
	Extension extends object,
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> = (
 | 
			
		||||
	modelManager: ModelManager<T, Shape, Identifier>,
 | 
			
		||||
) => ModelManager<T, Shape, Identifier> & Extension;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Define a new model.
 | 
			
		||||
 * @param definition The model definition object.
 | 
			
		||||
 */
 | 
			
		||||
export function defineModel<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
>(definition: ModelDefinition<T, Shape, Identifier>) {
 | 
			
		||||
	return new ModelManager<T, Shape, Identifier>(definition);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Define a new model, extending an existing one.
 | 
			
		||||
 * @param extendedModel The extended model manager instance.
 | 
			
		||||
 * @param definition The extension of the model definition object.
 | 
			
		||||
 */
 | 
			
		||||
export function extend<
 | 
			
		||||
	ExtT extends object,
 | 
			
		||||
	ExtShape extends ModelShape<ExtT>,
 | 
			
		||||
	ExtIdentifier extends IdentifierDefinition<ExtT, ExtShape>,
 | 
			
		||||
	T extends ExtT,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
	ResIdentifier extends IdentifierDefinition<T, ResShape>,
 | 
			
		||||
	ResShape extends ModelShape<T> = Modify<ExtShape, Shape>,
 | 
			
		||||
>(
 | 
			
		||||
	extendedModel: ModelManager<ExtT, ExtShape, ExtIdentifier>,
 | 
			
		||||
	definition: ModelDefinition<T, Shape, Identifier>,
 | 
			
		||||
) {
 | 
			
		||||
	const {properties: extendedProperties, ...overridableDefinition} =
 | 
			
		||||
		extendedModel.definition;
 | 
			
		||||
	const {properties: propertiesExtension, ...definitionExtension} = definition;
 | 
			
		||||
	return new ModelManager({
 | 
			
		||||
		...overridableDefinition,
 | 
			
		||||
		...definitionExtension,
 | 
			
		||||
		properties: {
 | 
			
		||||
			...extendedProperties,
 | 
			
		||||
			...propertiesExtension,
 | 
			
		||||
		},
 | 
			
		||||
	}) as unknown as ModelManager<T, ResShape, ResIdentifier>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A generic model manager for a provided model type, to use in circular dependencies.
 | 
			
		||||
 */
 | 
			
		||||
export type GenericModelManager<T extends object> = ModelManager<
 | 
			
		||||
	T,
 | 
			
		||||
	ModelShape<T>,
 | 
			
		||||
	IdentifierDefinition<T, ModelShape<T>>
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function to get a model manager lazily.
 | 
			
		||||
 */
 | 
			
		||||
export type LazyModelManager<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> = () => ModelManager<T, Shape, Identifier>;
 | 
			
		||||
/**
 | 
			
		||||
 * A model manager definition that can be lazy.
 | 
			
		||||
 */
 | 
			
		||||
export type DeclaredModelManager<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> = ModelManager<T, Shape, Identifier> | LazyModelManager<T, Shape, Identifier>;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,12 +0,0 @@
 | 
			
		|||
export {define} from "./property-definition";
 | 
			
		||||
 | 
			
		||||
export {array} from "./types/array";
 | 
			
		||||
export {bool, boolean} from "./types/boolean";
 | 
			
		||||
export {date} from "./types/date";
 | 
			
		||||
export {decimal} from "./types/decimal";
 | 
			
		||||
export {model} from "./types/model";
 | 
			
		||||
export {numeric} from "./types/numeric";
 | 
			
		||||
export {object} from "./types/object";
 | 
			
		||||
export {string} from "./types/string";
 | 
			
		||||
export {map} from "./types/map";
 | 
			
		||||
export {stringMap} from "./types/map";
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +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>) {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Unknown property definition.
 | 
			
		||||
 */
 | 
			
		||||
export type UnknownDefinition = Definition<unknown, unknown>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Any property definition.
 | 
			
		||||
 */
 | 
			
		||||
export type AnyDefinition = Definition<any, any>;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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,187 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of an array of values.
 | 
			
		||||
 */
 | 
			
		||||
export class ArrayType<SerializedValueType, SharkitekValueType> extends Type<
 | 
			
		||||
	SerializedValueType[],
 | 
			
		||||
	SharkitekValueType[]
 | 
			
		||||
> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new array type of a Sharkitek model property.
 | 
			
		||||
	 * @param valueDefinition Definition the array values.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(
 | 
			
		||||
		protected valueDefinition: Definition<
 | 
			
		||||
			SerializedValueType,
 | 
			
		||||
			SharkitekValueType
 | 
			
		||||
		>,
 | 
			
		||||
	) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(
 | 
			
		||||
		value: SharkitekValueType[] | null | undefined,
 | 
			
		||||
	): SerializedValueType[] | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an array");
 | 
			
		||||
 | 
			
		||||
		return value.map((value) =>
 | 
			
		||||
			// Serializing each value of the array.
 | 
			
		||||
			this.valueDefinition.type.serialize(value),
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deserialize(
 | 
			
		||||
		value: SerializedValueType[] | null | undefined,
 | 
			
		||||
	): SharkitekValueType[] | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an array");
 | 
			
		||||
 | 
			
		||||
		return value.map((serializedValue) =>
 | 
			
		||||
			// Deserializing each value of the array.
 | 
			
		||||
			this.valueDefinition.type.deserialize(serializedValue),
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializeDiff(
 | 
			
		||||
		value: SharkitekValueType[] | null | undefined,
 | 
			
		||||
	): SerializedValueType[] | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an array");
 | 
			
		||||
 | 
			
		||||
		// Serializing diff of all elements.
 | 
			
		||||
		return value.map(
 | 
			
		||||
			(value) =>
 | 
			
		||||
				this.valueDefinition.type.serializeDiff(value) as SerializedValueType,
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resetDiff(value: SharkitekValueType[] | null | undefined): void {
 | 
			
		||||
		// Do nothing if it is not an array.
 | 
			
		||||
		if (!Array.isArray(value)) return;
 | 
			
		||||
 | 
			
		||||
		// Reset diff of all elements.
 | 
			
		||||
		value.forEach((value) => this.valueDefinition.type.resetDiff(value));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasChanged(
 | 
			
		||||
		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 super.hasChanged(originalValue, currentValue);
 | 
			
		||||
 | 
			
		||||
		for (const key of originalValue.keys()) {
 | 
			
		||||
			// Check for any change for each value in the array.
 | 
			
		||||
			if (
 | 
			
		||||
				this.valueDefinition.type.hasChanged(
 | 
			
		||||
					originalValue[key],
 | 
			
		||||
					currentValue[key],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				// The value has changed, the array is different.
 | 
			
		||||
				return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return false; // No change detected.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializedHasChanged(
 | 
			
		||||
		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 super.serializedHasChanged(originalValue, currentValue);
 | 
			
		||||
 | 
			
		||||
		for (const key of originalValue.keys()) {
 | 
			
		||||
			// Check for any change for each value in the array.
 | 
			
		||||
			if (
 | 
			
		||||
				this.valueDefinition.type.serializedHasChanged(
 | 
			
		||||
					originalValue[key],
 | 
			
		||||
					currentValue[key],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				// The value has changed, the array is different.
 | 
			
		||||
				return true;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return false; // No change detected.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clone<T extends SharkitekValueType[]>(array: T | null | undefined): T {
 | 
			
		||||
		// Handle NULL / undefined array.
 | 
			
		||||
		if (!array) return super.clone(array);
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(array))
 | 
			
		||||
			throw new InvalidTypeValueError(this, array, "value must be an array");
 | 
			
		||||
 | 
			
		||||
		// Initialize an empty array.
 | 
			
		||||
		const cloned = [] as T;
 | 
			
		||||
 | 
			
		||||
		for (const value of array) {
 | 
			
		||||
			// Clone each value of the array.
 | 
			
		||||
			cloned.push(this.valueDefinition.type.clone(value));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return cloned; // Returning cloned array.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	applyPatch<T extends SharkitekValueType[]>(
 | 
			
		||||
		currentValue: T | null | undefined,
 | 
			
		||||
		patchValue: SerializedValueType[] | null | undefined,
 | 
			
		||||
		updateOriginals: boolean,
 | 
			
		||||
	): T | null | undefined {
 | 
			
		||||
		if (patchValue === undefined) return undefined;
 | 
			
		||||
		if (patchValue === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!Array.isArray(patchValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				patchValue,
 | 
			
		||||
				"value must be an array",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		currentValue = Array.isArray(currentValue) ? currentValue : ([] as T);
 | 
			
		||||
 | 
			
		||||
		for (let i = 0; i < patchValue.length; i++) {
 | 
			
		||||
			// Apply the patch to all values of the array.
 | 
			
		||||
			const patchedElement = this.valueDefinition.type.applyPatch(
 | 
			
		||||
				currentValue?.[i],
 | 
			
		||||
				patchValue[i],
 | 
			
		||||
				updateOriginals,
 | 
			
		||||
			);
 | 
			
		||||
			if (i < currentValue.length) currentValue[i] = patchedElement;
 | 
			
		||||
			else currentValue.push(patchedElement);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return currentValue;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New array property definition.
 | 
			
		||||
 * @param valueDefinition Array values type definition.
 | 
			
		||||
 */
 | 
			
		||||
export function array<SerializedValueType, SharkitekValueType>(
 | 
			
		||||
	valueDefinition: Definition<SerializedValueType, SharkitekValueType>,
 | 
			
		||||
): Definition<SerializedValueType[], SharkitekValueType[]> {
 | 
			
		||||
	return define(new ArrayType(valueDefinition));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,37 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any boolean value.
 | 
			
		||||
 */
 | 
			
		||||
export class BooleanType 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 boolean(): Definition<boolean, boolean> {
 | 
			
		||||
	return define(new BooleanType());
 | 
			
		||||
}
 | 
			
		||||
/**
 | 
			
		||||
 * New boolean property definition.
 | 
			
		||||
 * Alias of boolean.
 | 
			
		||||
 */
 | 
			
		||||
export function bool(): ReturnType<typeof boolean> {
 | 
			
		||||
	return boolean();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,51 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of dates.
 | 
			
		||||
 */
 | 
			
		||||
export class DateType extends Type<string, Date> {
 | 
			
		||||
	deserialize(value: string | null | undefined): Date | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return new Date(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: Date | null | undefined): string | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
		if (!(value instanceof Date))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be a date");
 | 
			
		||||
		if (isNaN(value?.valueOf())) return value?.toString();
 | 
			
		||||
 | 
			
		||||
		return value?.toISOString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasChanged(
 | 
			
		||||
		originalValue: Date | null | undefined,
 | 
			
		||||
		currentValue: Date | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		if (originalValue instanceof Date && currentValue instanceof Date) {
 | 
			
		||||
			// Compare dates.
 | 
			
		||||
			const originalTime = originalValue.getTime();
 | 
			
		||||
			const currentTime = currentValue.getTime();
 | 
			
		||||
 | 
			
		||||
			// The two values are not numbers, nothing has changed.
 | 
			
		||||
			if (isNaN(originalTime) && isNaN(currentTime)) return false;
 | 
			
		||||
 | 
			
		||||
			// Timestamps need to be exactly the same.
 | 
			
		||||
			return originalValue.getTime() !== currentValue.getTime();
 | 
			
		||||
		} else
 | 
			
		||||
			// Compare undefined or null values.
 | 
			
		||||
			return originalValue !== currentValue;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New date property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function date(): Definition<string, Date> {
 | 
			
		||||
	return define(new DateType());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of decimal numbers.
 | 
			
		||||
 */
 | 
			
		||||
export class DecimalType extends Type<string, number> {
 | 
			
		||||
	deserialize(value: string | null | undefined): number | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return parseFloat(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: number | null | undefined): string | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
		if (typeof value !== "number" && typeof value !== "string")
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be a number");
 | 
			
		||||
 | 
			
		||||
		return value?.toString();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New decimal property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function decimal(): Definition<string, number> {
 | 
			
		||||
	return define(new DecimalType());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,264 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
import {string} from "./string";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a key-value map.
 | 
			
		||||
 */
 | 
			
		||||
export class MapType<
 | 
			
		||||
    KeyType,
 | 
			
		||||
    ValueType,
 | 
			
		||||
    SerializedValueType,
 | 
			
		||||
    SerializedMapType extends Record<string, SerializedValueType> = Record<
 | 
			
		||||
        string,
 | 
			
		||||
        SerializedValueType
 | 
			
		||||
    >,
 | 
			
		||||
> extends Type<SerializedMapType, Map<KeyType, ValueType>> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Initialize a new map type of a Sharkitek model property.
 | 
			
		||||
     * @param keyDefinition Definition of the map keys.
 | 
			
		||||
     * @param valueDefinition Definition of the map values.
 | 
			
		||||
     */
 | 
			
		||||
    constructor(
 | 
			
		||||
        protected keyDefinition: Definition<string, KeyType>,
 | 
			
		||||
        protected valueDefinition: Definition<SerializedValueType, ValueType>,
 | 
			
		||||
    ) {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    serialize(
 | 
			
		||||
        value: Map<KeyType, ValueType> | null | undefined,
 | 
			
		||||
    ): SerializedMapType | null | undefined {
 | 
			
		||||
        if (value === undefined) return undefined;
 | 
			
		||||
        if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
        if (!(value instanceof Map))
 | 
			
		||||
            throw new InvalidTypeValueError(
 | 
			
		||||
                this,
 | 
			
		||||
                value,
 | 
			
		||||
                "value must be an instance of map",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        return Object.fromEntries(
 | 
			
		||||
            // Serializing each key-value pair of the map.
 | 
			
		||||
            value
 | 
			
		||||
                .entries()
 | 
			
		||||
                .map(([key, value]) => [
 | 
			
		||||
                    this.keyDefinition.type.serialize(key),
 | 
			
		||||
                    this.valueDefinition.type.serialize(value),
 | 
			
		||||
                ]),
 | 
			
		||||
        ) as SerializedMapType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    deserialize(
 | 
			
		||||
        value: SerializedMapType | null | undefined,
 | 
			
		||||
    ): Map<KeyType, ValueType> | null | undefined {
 | 
			
		||||
        if (value === undefined) return undefined;
 | 
			
		||||
        if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
        if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
            throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
        const map = new Map<KeyType, ValueType>();
 | 
			
		||||
        for (const [serializedKey, serializedValue] of Object.entries(value)) {
 | 
			
		||||
            // Deserializing each key-value pair of the map.
 | 
			
		||||
            map.set(
 | 
			
		||||
                this.keyDefinition.type.deserialize(serializedKey),
 | 
			
		||||
                this.valueDefinition.type.deserialize(serializedValue),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    serializeDiff(
 | 
			
		||||
        value: Map<KeyType, ValueType> | null | undefined,
 | 
			
		||||
    ): SerializedMapType | null | undefined {
 | 
			
		||||
        if (value === undefined) return undefined;
 | 
			
		||||
        if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
        if (!(value instanceof Map))
 | 
			
		||||
            throw new InvalidTypeValueError(
 | 
			
		||||
                this,
 | 
			
		||||
                value,
 | 
			
		||||
                "value must be an instance of map",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        return Object.fromEntries(
 | 
			
		||||
            // Serializing the diff of each key-value pair of the map.
 | 
			
		||||
            value
 | 
			
		||||
                .entries()
 | 
			
		||||
                .map(([key, value]) => [
 | 
			
		||||
                    this.keyDefinition.type.serializeDiff(key),
 | 
			
		||||
                    this.valueDefinition.type.serializeDiff(value),
 | 
			
		||||
                ]),
 | 
			
		||||
        ) as SerializedMapType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    resetDiff(value: Map<KeyType, ValueType> | null | undefined): void {
 | 
			
		||||
        // Do nothing if it is not a map.
 | 
			
		||||
        if (!(value instanceof Map)) return;
 | 
			
		||||
 | 
			
		||||
        // Reset diff of all key-value pairs.
 | 
			
		||||
        value.forEach((value, key) => {
 | 
			
		||||
            this.keyDefinition.type.resetDiff(key);
 | 
			
		||||
            this.valueDefinition.type.resetDiff(value);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    hasChanged(
 | 
			
		||||
        originalValue: Map<KeyType, ValueType> | null | undefined,
 | 
			
		||||
        currentValue: Map<KeyType, ValueType> | null | undefined,
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        // If any map size is different, maps are different.
 | 
			
		||||
        if (originalValue?.size != currentValue?.size) return true;
 | 
			
		||||
        // If size is undefined, values are probably not maps.
 | 
			
		||||
        if (originalValue?.size == undefined)
 | 
			
		||||
            return super.hasChanged(originalValue, currentValue);
 | 
			
		||||
 | 
			
		||||
        for (const [key, value] of originalValue.entries()) {
 | 
			
		||||
            // Check for any change for each key-value in the map.
 | 
			
		||||
            if (this.valueDefinition.type.hasChanged(value, currentValue.get(key)))
 | 
			
		||||
                // The value has changed, the map is different.
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false; // No change detected.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    serializedHasChanged(
 | 
			
		||||
        originalValue: SerializedMapType | null | undefined,
 | 
			
		||||
        currentValue: SerializedMapType | null | undefined,
 | 
			
		||||
    ): boolean {
 | 
			
		||||
        // If any value is not a defined object, use the default comparison function.
 | 
			
		||||
        if (
 | 
			
		||||
            !originalValue ||
 | 
			
		||||
            !currentValue ||
 | 
			
		||||
            typeof originalValue !== "object" ||
 | 
			
		||||
            typeof currentValue !== "object"
 | 
			
		||||
        )
 | 
			
		||||
            return super.serializedHasChanged(originalValue, currentValue);
 | 
			
		||||
 | 
			
		||||
        // If any object size is different, objects are different.
 | 
			
		||||
        if (Object.keys(originalValue)?.length != Object.keys(currentValue)?.length)
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        for (const [key, value] of Object.entries(originalValue)) {
 | 
			
		||||
            // Check for any change for each key-value pair in the object.
 | 
			
		||||
            if (
 | 
			
		||||
                this.valueDefinition.type.serializedHasChanged(value, currentValue[key])
 | 
			
		||||
            )
 | 
			
		||||
                // The value has changed, the object is different.
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false; // No change detected.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    clone<T extends Map<KeyType, ValueType>>(map: T | null | undefined): T {
 | 
			
		||||
        // Handle NULL / undefined map.
 | 
			
		||||
        if (!map) return super.clone(map);
 | 
			
		||||
 | 
			
		||||
        if (!(map instanceof Map))
 | 
			
		||||
            throw new InvalidTypeValueError(
 | 
			
		||||
                this,
 | 
			
		||||
                map,
 | 
			
		||||
                "value must be an instance of map",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        // Initialize an empty map.
 | 
			
		||||
        const cloned = new Map<KeyType, ValueType>() as T;
 | 
			
		||||
 | 
			
		||||
        for (const [key, value] of map.entries()) {
 | 
			
		||||
            // Clone each value of the map.
 | 
			
		||||
            cloned.set(
 | 
			
		||||
                this.keyDefinition.type.clone(key),
 | 
			
		||||
                this.valueDefinition.type.clone(value),
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return cloned; // Returning cloned map.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    applyPatch<T extends Map<KeyType, ValueType>>(
 | 
			
		||||
        currentValue: T | null | undefined,
 | 
			
		||||
        patchValue: SerializedMapType | null | undefined,
 | 
			
		||||
        updateOriginals: boolean,
 | 
			
		||||
    ): T | null | undefined {
 | 
			
		||||
        if (patchValue === undefined) return undefined;
 | 
			
		||||
        if (patchValue === null) return null;
 | 
			
		||||
 | 
			
		||||
        if (typeof patchValue !== "object")
 | 
			
		||||
            throw new InvalidTypeValueError(
 | 
			
		||||
                this,
 | 
			
		||||
                patchValue,
 | 
			
		||||
                "value must be an object",
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        currentValue =
 | 
			
		||||
            currentValue instanceof Map
 | 
			
		||||
                ? currentValue
 | 
			
		||||
                : (new Map<KeyType, ValueType>() as T);
 | 
			
		||||
 | 
			
		||||
        for (const [key, value] of Object.entries(patchValue)) {
 | 
			
		||||
            // Apply the patch to all values of the map.
 | 
			
		||||
            const patchedKey = this.keyDefinition.type.deserialize(key);
 | 
			
		||||
            const patchedElement = this.valueDefinition.type.applyPatch(
 | 
			
		||||
                currentValue.get(patchedKey),
 | 
			
		||||
                value,
 | 
			
		||||
                updateOriginals,
 | 
			
		||||
            );
 | 
			
		||||
            currentValue.set(patchedKey, patchedElement);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return currentValue;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New map property definition.
 | 
			
		||||
 * @param keyDefinition Definition of the map keys.
 | 
			
		||||
 * @param valueDefinition Definition of the map values.
 | 
			
		||||
 */
 | 
			
		||||
export function map<
 | 
			
		||||
    KeyType,
 | 
			
		||||
    ValueType,
 | 
			
		||||
    SerializedValueType,
 | 
			
		||||
    SerializedMapType extends Record<string, SerializedValueType> = Record<
 | 
			
		||||
        string,
 | 
			
		||||
        SerializedValueType
 | 
			
		||||
    >,
 | 
			
		||||
>(
 | 
			
		||||
    keyDefinition: Definition<string, KeyType>,
 | 
			
		||||
    valueDefinition: Definition<SerializedValueType, ValueType>,
 | 
			
		||||
): Definition<SerializedMapType, Map<KeyType, ValueType>> {
 | 
			
		||||
    return define(
 | 
			
		||||
        new MapType<KeyType, ValueType, SerializedValueType, SerializedMapType>(
 | 
			
		||||
            keyDefinition,
 | 
			
		||||
            valueDefinition,
 | 
			
		||||
        ),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New map property definition, with string as index.
 | 
			
		||||
 * @param valueDefinition Definition of the map values.
 | 
			
		||||
 */
 | 
			
		||||
export function stringMap<
 | 
			
		||||
    ValueType,
 | 
			
		||||
    SerializedValueType,
 | 
			
		||||
    SerializedMapType extends Record<string, SerializedValueType> = Record<
 | 
			
		||||
        string,
 | 
			
		||||
        SerializedValueType
 | 
			
		||||
    >,
 | 
			
		||||
>(
 | 
			
		||||
    valueDefinition: Definition<SerializedValueType, ValueType>,
 | 
			
		||||
): Definition<SerializedMapType, Map<string, ValueType>> {
 | 
			
		||||
    return define(
 | 
			
		||||
        new MapType<string, ValueType, SerializedValueType, SerializedMapType>(
 | 
			
		||||
            string(),
 | 
			
		||||
            valueDefinition,
 | 
			
		||||
        ),
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,225 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {
 | 
			
		||||
	GenericModelManager,
 | 
			
		||||
	IdentifierDefinition,
 | 
			
		||||
	DeclaredModelManager,
 | 
			
		||||
	ModelInstance,
 | 
			
		||||
	ModelManager,
 | 
			
		||||
	ModelShape,
 | 
			
		||||
	SerializedModel,
 | 
			
		||||
} from "../model";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a Sharkitek model value.
 | 
			
		||||
 */
 | 
			
		||||
export class ModelType<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
> extends Type<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new model type of a Sharkitek model property.
 | 
			
		||||
	 * @param declaredModelManager Model manager.
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(
 | 
			
		||||
		protected declaredModelManager: DeclaredModelManager<T, Shape, Identifier>,
 | 
			
		||||
	) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Resolve the defined model using the declared model, that can be defined lazily.
 | 
			
		||||
	 */
 | 
			
		||||
	get definedModel(): ModelManager<T, Shape, Identifier> {
 | 
			
		||||
		return typeof this.declaredModelManager == "object"
 | 
			
		||||
			? this.declaredModelManager
 | 
			
		||||
			: this.declaredModelManager();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(
 | 
			
		||||
		value: ModelInstance<T, Shape, Identifier> | null | undefined,
 | 
			
		||||
	): SerializedModel<T, Shape> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!(value instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				value,
 | 
			
		||||
				`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// Serializing the given model.
 | 
			
		||||
		return this.definedModel.model(value).serialize();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deserialize(
 | 
			
		||||
		value: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
	): ModelInstance<T, Shape, Identifier> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// Parse the given object in the new model.
 | 
			
		||||
		return this.definedModel.parse(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializeDiff(
 | 
			
		||||
		value: ModelInstance<T, Shape, Identifier> | null | undefined,
 | 
			
		||||
	): Partial<SerializedModel<T, Shape>> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (!(value instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				value,
 | 
			
		||||
				`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// Serializing the given model.
 | 
			
		||||
		return this.definedModel.model(value).serializeDiff();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resetDiff(
 | 
			
		||||
		value: ModelInstance<T, Shape, Identifier> | null | undefined,
 | 
			
		||||
	): void {
 | 
			
		||||
		if (value === undefined) return;
 | 
			
		||||
		if (value === null) return;
 | 
			
		||||
 | 
			
		||||
		if (!(value instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				value,
 | 
			
		||||
				`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// Reset diff of the given model.
 | 
			
		||||
		this.definedModel.model(value).resetDiff();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasChanged(
 | 
			
		||||
		originalValue: ModelInstance<T, Shape, Identifier> | null | undefined,
 | 
			
		||||
		currentValue: ModelInstance<T, Shape, Identifier> | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		if (originalValue === undefined) return currentValue !== undefined;
 | 
			
		||||
		if (originalValue === null) return currentValue !== null;
 | 
			
		||||
		if (currentValue === undefined) return true; // Original value is not undefined.
 | 
			
		||||
		if (currentValue === null) return true; // Original value is not null.
 | 
			
		||||
 | 
			
		||||
		if (!(originalValue instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				originalValue,
 | 
			
		||||
				`value must be a compatible model (given ${originalValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
		if (!(currentValue instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				currentValue,
 | 
			
		||||
				`value must be a compatible model (given ${currentValue.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// If the current value is dirty, it has changed.
 | 
			
		||||
		return this.definedModel.model(currentValue).isDirty();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializedHasChanged(
 | 
			
		||||
		originalValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
		currentValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		if (originalValue === undefined) return currentValue !== undefined;
 | 
			
		||||
		if (originalValue === null) return currentValue !== null;
 | 
			
		||||
		if (currentValue === undefined) return true; // Original value is not undefined.
 | 
			
		||||
		if (currentValue === null) return true; // Original value is not null.
 | 
			
		||||
 | 
			
		||||
		if (typeof originalValue !== "object" || Array.isArray(originalValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				originalValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
		if (typeof currentValue !== "object" || Array.isArray(currentValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				currentValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// If any property has changed, the value has changed.
 | 
			
		||||
		for (const property of this.definedModel.properties)
 | 
			
		||||
			if (
 | 
			
		||||
				property.definition.type.serializedHasChanged(
 | 
			
		||||
					originalValue?.[property.name],
 | 
			
		||||
					currentValue?.[property.name],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				return true;
 | 
			
		||||
 | 
			
		||||
		return false; // No change detected.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clone<Type extends ModelInstance<T, Shape, Identifier>>(
 | 
			
		||||
		value: Type | null | undefined,
 | 
			
		||||
	): Type {
 | 
			
		||||
		// Handle NULL / undefined values.
 | 
			
		||||
		if (!value) return super.clone(value);
 | 
			
		||||
 | 
			
		||||
		if (!(value instanceof this.definedModel.definition.Class))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				value,
 | 
			
		||||
				`value must be a compatible model (given ${value.constructor.name}, expected ${this.definedModel.definition.Class.name})`,
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		return this.definedModel.model(value).clone() as Type;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	applyPatch<Type extends ModelInstance<T, Shape, Identifier>>(
 | 
			
		||||
		currentValue: Type | null | undefined,
 | 
			
		||||
		patchValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
		updateOriginals: boolean,
 | 
			
		||||
	): Type | null | undefined {
 | 
			
		||||
		if (patchValue === undefined) return undefined;
 | 
			
		||||
		if (patchValue === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof patchValue !== "object" || Array.isArray(patchValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				patchValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		return this.definedModel
 | 
			
		||||
			.model(currentValue)
 | 
			
		||||
			.applyPatch(patchValue, updateOriginals) as Type;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New model property definition.
 | 
			
		||||
 * @param definedModel Model manager.
 | 
			
		||||
 */
 | 
			
		||||
export function model<
 | 
			
		||||
	T extends object,
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	Identifier extends IdentifierDefinition<T, Shape>,
 | 
			
		||||
>(
 | 
			
		||||
	definedModel: DeclaredModelManager<T, Shape, Identifier>,
 | 
			
		||||
): Definition<SerializedModel<T, Shape>, ModelInstance<T, Shape, Identifier>> {
 | 
			
		||||
	return define(new ModelType(definedModel));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Utility function to fix circular dependencies issues.
 | 
			
		||||
 * @param definedModel A function returning the model to use.
 | 
			
		||||
 */
 | 
			
		||||
export function circular<T extends object>(
 | 
			
		||||
	definedModel: () => any,
 | 
			
		||||
): () => GenericModelManager<T> {
 | 
			
		||||
	return definedModel;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any numeric value.
 | 
			
		||||
 */
 | 
			
		||||
export class NumericType extends Type<number, number> {
 | 
			
		||||
	deserialize(value: number | null | undefined): number | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "number")
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be a number");
 | 
			
		||||
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: number | null | undefined): number | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "number")
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be a number");
 | 
			
		||||
 | 
			
		||||
		return value;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New numeric property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function numeric(): Definition<number, number> {
 | 
			
		||||
	return define(new NumericType());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,267 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition, UnknownDefinition} from "../property-definition";
 | 
			
		||||
import {
 | 
			
		||||
	ModelProperties,
 | 
			
		||||
	ModelPropertiesValues,
 | 
			
		||||
	ModelProperty,
 | 
			
		||||
	ModelShape,
 | 
			
		||||
	SerializedModel,
 | 
			
		||||
} from "../model";
 | 
			
		||||
import {InvalidTypeValueError} from "../../errors";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of a custom object.
 | 
			
		||||
 */
 | 
			
		||||
export class ObjectType<
 | 
			
		||||
	Shape extends ModelShape<T>,
 | 
			
		||||
	T extends object,
 | 
			
		||||
> extends Type<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Defined properties.
 | 
			
		||||
	 */
 | 
			
		||||
	properties: ModelProperties<T, Shape>;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize a new object type of a Sharkitek model property.
 | 
			
		||||
	 * @param shape
 | 
			
		||||
	 */
 | 
			
		||||
	constructor(readonly shape: Shape) {
 | 
			
		||||
		super();
 | 
			
		||||
		this.initProperties();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initialize properties iterator from the object shape.
 | 
			
		||||
	 * @protected
 | 
			
		||||
	 */
 | 
			
		||||
	protected initProperties(): void {
 | 
			
		||||
		// Build an array of model properties from the object shape.
 | 
			
		||||
		this.properties = [];
 | 
			
		||||
		for (const propertyName in this.shape) {
 | 
			
		||||
			// For each property, build a model property object.
 | 
			
		||||
			this.properties.push({
 | 
			
		||||
				name: propertyName,
 | 
			
		||||
				definition: this.shape[propertyName],
 | 
			
		||||
				identifier: false,
 | 
			
		||||
			} as ModelProperty<T, Shape>);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deserialize(
 | 
			
		||||
		value: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
	): ModelPropertiesValues<T, Shape> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// Initialize an empty object.
 | 
			
		||||
		const obj: Partial<ModelPropertiesValues<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined property, deserialize its value according to its type.
 | 
			
		||||
			(obj[property.name as keyof T] as any) =
 | 
			
		||||
				property.definition.type.deserialize(value?.[property.name]);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return obj as ModelPropertiesValues<T, Shape>; // Returning serialized object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(
 | 
			
		||||
		value: ModelPropertiesValues<T, Shape> | null | undefined,
 | 
			
		||||
	): SerializedModel<T, Shape> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// Creating an empty serialized object.
 | 
			
		||||
		const serializedObject: Partial<SerializedModel<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each property, adding it to the serialized object.
 | 
			
		||||
			serializedObject[property.name] = property.definition.type.serialize(
 | 
			
		||||
				// keyof Shape is a subset of keyof T.
 | 
			
		||||
				value?.[property.name as keyof T],
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializeDiff(
 | 
			
		||||
		value: ModelPropertiesValues<T, Shape> | null | undefined,
 | 
			
		||||
	): Partial<SerializedModel<T, Shape>> | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// Creating an empty serialized object.
 | 
			
		||||
		const serializedObject: Partial<SerializedModel<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each property, adding it to the serialized object.
 | 
			
		||||
			serializedObject[property.name] = property.definition.type.serializeDiff(
 | 
			
		||||
				// keyof Shape is a subset of keyof T.
 | 
			
		||||
				value?.[property.name as keyof T],
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return serializedObject as SerializedModel<T, Shape>; // Returning the serialized object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resetDiff(value: ModelPropertiesValues<T, Shape> | null | undefined) {
 | 
			
		||||
		if (value === undefined) return;
 | 
			
		||||
		if (value === null) return;
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// For each property, reset its diff.
 | 
			
		||||
		// keyof Shape is a subset of keyof T.
 | 
			
		||||
		for (const property of this.properties)
 | 
			
		||||
			property.definition.type.resetDiff(value?.[property.name as keyof T]);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hasChanged(
 | 
			
		||||
		originalValue: ModelPropertiesValues<T, Shape> | null | undefined,
 | 
			
		||||
		currentValue: ModelPropertiesValues<T, Shape> | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		if (originalValue === undefined) return currentValue !== undefined;
 | 
			
		||||
		if (originalValue === null) return currentValue !== null;
 | 
			
		||||
		if (currentValue === undefined) return true; // Original value is not undefined.
 | 
			
		||||
		if (currentValue === null) return true; // Original value is not null.
 | 
			
		||||
 | 
			
		||||
		if (typeof originalValue !== "object" || Array.isArray(originalValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				originalValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
		if (typeof currentValue !== "object" || Array.isArray(currentValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				currentValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// If any property has changed, the value has changed.
 | 
			
		||||
		for (const property of this.properties)
 | 
			
		||||
			if (
 | 
			
		||||
				property.definition.type.hasChanged(
 | 
			
		||||
					originalValue?.[property.name as keyof T],
 | 
			
		||||
					currentValue?.[property.name as keyof T],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				return true;
 | 
			
		||||
 | 
			
		||||
		return false; // No change detected.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serializedHasChanged(
 | 
			
		||||
		originalValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
		currentValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		if (originalValue === undefined) return currentValue !== undefined;
 | 
			
		||||
		if (originalValue === null) return currentValue !== null;
 | 
			
		||||
		if (currentValue === undefined) return true; // Original value is not undefined.
 | 
			
		||||
		if (currentValue === null) return true; // Original value is not null.
 | 
			
		||||
 | 
			
		||||
		if (typeof originalValue !== "object" || Array.isArray(originalValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				originalValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
		if (typeof currentValue !== "object" || Array.isArray(currentValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				currentValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		// If any property has changed, the value has changed.
 | 
			
		||||
		for (const property of this.properties)
 | 
			
		||||
			if (
 | 
			
		||||
				property.definition.type.serializedHasChanged(
 | 
			
		||||
					originalValue?.[property.name],
 | 
			
		||||
					currentValue?.[property.name],
 | 
			
		||||
				)
 | 
			
		||||
			)
 | 
			
		||||
				return true;
 | 
			
		||||
 | 
			
		||||
		return false; // No change detected.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clone<Type extends ModelPropertiesValues<T, Shape>>(
 | 
			
		||||
		value: Type | null | undefined,
 | 
			
		||||
	): Type {
 | 
			
		||||
		// Handle NULL / undefined object.
 | 
			
		||||
		if (!value) return super.clone(value);
 | 
			
		||||
 | 
			
		||||
		if (typeof value !== "object" || Array.isArray(value))
 | 
			
		||||
			throw new InvalidTypeValueError(this, value, "value must be an object");
 | 
			
		||||
 | 
			
		||||
		// Initialize an empty object.
 | 
			
		||||
		const cloned: Partial<ModelPropertiesValues<T, Shape>> = {};
 | 
			
		||||
 | 
			
		||||
		for (const property of this.properties) {
 | 
			
		||||
			// For each defined property, clone it.
 | 
			
		||||
			cloned[property.name as keyof T] = property.definition.type.clone(
 | 
			
		||||
				value?.[property.name],
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return cloned as Type; // Returning cloned object.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	applyPatch<Type extends ModelPropertiesValues<T, Shape>>(
 | 
			
		||||
		currentValue: Type | null | undefined,
 | 
			
		||||
		patchValue: SerializedModel<T, Shape> | null | undefined,
 | 
			
		||||
		updateOriginals: boolean,
 | 
			
		||||
	): Type | null | undefined {
 | 
			
		||||
		if (patchValue === undefined) return undefined;
 | 
			
		||||
		if (patchValue === null) return null;
 | 
			
		||||
 | 
			
		||||
		if (typeof patchValue !== "object" || Array.isArray(patchValue))
 | 
			
		||||
			throw new InvalidTypeValueError(
 | 
			
		||||
				this,
 | 
			
		||||
				patchValue,
 | 
			
		||||
				"value must be an object",
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
		const patchedValue: Partial<Type> =
 | 
			
		||||
			typeof currentValue === "object" && currentValue !== null
 | 
			
		||||
				? currentValue
 | 
			
		||||
				: {};
 | 
			
		||||
 | 
			
		||||
		for (const key in patchValue) {
 | 
			
		||||
			// Apply the patch to each property of the patch value.
 | 
			
		||||
			const propertyDef = this.shape[key];
 | 
			
		||||
			if (propertyDef)
 | 
			
		||||
				patchedValue[key as keyof Type] = (
 | 
			
		||||
					propertyDef as UnknownDefinition
 | 
			
		||||
				).type.applyPatch(
 | 
			
		||||
					currentValue?.[key as keyof Type],
 | 
			
		||||
					patchValue[key],
 | 
			
		||||
					updateOriginals,
 | 
			
		||||
				);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return patchedValue as Type;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New object property definition.
 | 
			
		||||
 * @param shape Shape of the object.
 | 
			
		||||
 */
 | 
			
		||||
export function object<Shape extends ModelShape<T>, T extends object>(
 | 
			
		||||
	shape: Shape,
 | 
			
		||||
): Definition<SerializedModel<T, Shape>, ModelPropertiesValues<T, Shape>> {
 | 
			
		||||
	return define(new ObjectType(shape));
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,28 +0,0 @@
 | 
			
		|||
import {Type} from "./type";
 | 
			
		||||
import {define, Definition} from "../property-definition";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type of any string value.
 | 
			
		||||
 */
 | 
			
		||||
export class StringType extends Type<string, string> {
 | 
			
		||||
	deserialize(value: string | null | undefined): string | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return String(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serialize(value: string | null | undefined): string | null | undefined {
 | 
			
		||||
		if (value === undefined) return undefined;
 | 
			
		||||
		if (value === null) return null;
 | 
			
		||||
 | 
			
		||||
		return String(value);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * New string property definition.
 | 
			
		||||
 */
 | 
			
		||||
export function string(): Definition<string, string> {
 | 
			
		||||
	return define(new StringType());
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,88 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Abstract class of a Sharkitek model property type.
 | 
			
		||||
 */
 | 
			
		||||
export abstract class Type<SerializedType, ModelType> {
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the given value of a Sharkitek model property.
 | 
			
		||||
	 * @param value Value to serialize.
 | 
			
		||||
	 */
 | 
			
		||||
	abstract serialize(
 | 
			
		||||
		value: ModelType | null | undefined,
 | 
			
		||||
	): SerializedType | null | undefined;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Deserialize the given value of a serialized Sharkitek model.
 | 
			
		||||
	 * @param value Value to deserialize.
 | 
			
		||||
	 */
 | 
			
		||||
	abstract deserialize(
 | 
			
		||||
		value: SerializedType | null | undefined,
 | 
			
		||||
	): ModelType | null | undefined;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Serialize the given value only if it has changed.
 | 
			
		||||
	 * @param value Value to deserialize.
 | 
			
		||||
	 */
 | 
			
		||||
	serializeDiff(
 | 
			
		||||
		value: ModelType | null | undefined,
 | 
			
		||||
	): Partial<SerializedType> | null | undefined {
 | 
			
		||||
		return this.serialize(value); // By default, nothing changes.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Reset the difference between the original value and the current one.
 | 
			
		||||
	 * @param value Value for which reset diff data.
 | 
			
		||||
	 */
 | 
			
		||||
	resetDiff(
 | 
			
		||||
		// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
		value: ModelType | null | undefined,
 | 
			
		||||
	): void {
 | 
			
		||||
		// By default, nothing to do.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine if the value has changed.
 | 
			
		||||
	 * @param originalValue Original value.
 | 
			
		||||
	 * @param currentValue Current value.
 | 
			
		||||
	 */
 | 
			
		||||
	hasChanged(
 | 
			
		||||
		originalValue: ModelType | null | undefined,
 | 
			
		||||
		currentValue: ModelType | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		return originalValue !== currentValue;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Determine if the serialized value has changed.
 | 
			
		||||
	 * @param originalValue Original serialized value.
 | 
			
		||||
	 * @param currentValue Current serialized value.
 | 
			
		||||
	 */
 | 
			
		||||
	serializedHasChanged(
 | 
			
		||||
		originalValue: SerializedType | null | undefined,
 | 
			
		||||
		currentValue: SerializedType | null | undefined,
 | 
			
		||||
	): boolean {
 | 
			
		||||
		return originalValue !== currentValue;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Clone the provided value.
 | 
			
		||||
	 * @param value The to clone.
 | 
			
		||||
	 */
 | 
			
		||||
	clone<T extends ModelType>(value: T | null | undefined): T {
 | 
			
		||||
		return structuredClone(value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Apply the patch value.
 | 
			
		||||
	 * @param currentValue The current property value. Its value can be mutated directly.
 | 
			
		||||
	 * @param patchValue The serialized patch value.
 | 
			
		||||
	 * @param updateOriginals Indicates if the original properties values must be updated or not.
 | 
			
		||||
	 */
 | 
			
		||||
	applyPatch<T extends ModelType>(
 | 
			
		||||
		currentValue: T | null | undefined,
 | 
			
		||||
		patchValue: SerializedType | null | undefined,
 | 
			
		||||
		// eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
			
		||||
		updateOriginals: boolean,
 | 
			
		||||
	): T | null | undefined {
 | 
			
		||||
		return this.deserialize(patchValue) as T;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								src/utils.ts
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								src/utils.ts
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,10 +0,0 @@
 | 
			
		|||
/**
 | 
			
		||||
 * Type definition of a class constructor.
 | 
			
		||||
 */
 | 
			
		||||
export type ConstructorOf<T extends object> = {new (): T};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Type definition of an original object overridden by another.
 | 
			
		||||
 */
 | 
			
		||||
export type Modify<Original, Override> = Omit<Original, keyof Override> &
 | 
			
		||||
	Override;
 | 
			
		||||
							
								
								
									
										159
									
								
								tests/Model.test.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								tests/Model.test.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,159 @@
 | 
			
		|||
import {s} from "../src";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Another test model.
 | 
			
		||||
 */
 | 
			
		||||
class Author extends s.model({
 | 
			
		||||
	name: s.property.string(),
 | 
			
		||||
	firstName: s.property.string(),
 | 
			
		||||
	email: s.property.string(),
 | 
			
		||||
	createdAt: s.property.date(),
 | 
			
		||||
	active: s.property.bool(),
 | 
			
		||||
})
 | 
			
		||||
{
 | 
			
		||||
	active: boolean = true;
 | 
			
		||||
 | 
			
		||||
	constructor(name: string = "", firstName: string = "", email: string = "", createdAt: Date = new Date())
 | 
			
		||||
	{
 | 
			
		||||
		super();
 | 
			
		||||
 | 
			
		||||
		this.name = name;
 | 
			
		||||
		this.firstName = firstName;
 | 
			
		||||
		this.email = email;
 | 
			
		||||
		this.createdAt = createdAt;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A test model.
 | 
			
		||||
 */
 | 
			
		||||
class Article extends s.model({
 | 
			
		||||
	id: s.property.numeric(),
 | 
			
		||||
	title: s.property.string(),
 | 
			
		||||
	authors: s.property.array(s.property.model(Author)),
 | 
			
		||||
	text: s.property.string(),
 | 
			
		||||
	evaluation: s.property.decimal(),
 | 
			
		||||
	tags: s.property.array(
 | 
			
		||||
		s.property.object({
 | 
			
		||||
			name: s.property.string(),
 | 
			
		||||
		})
 | 
			
		||||
	),
 | 
			
		||||
}, "id")
 | 
			
		||||
{
 | 
			
		||||
	id: number;
 | 
			
		||||
	title: string;
 | 
			
		||||
	authors: Author[] = [];
 | 
			
		||||
	text: string;
 | 
			
		||||
	evaluation: number;
 | 
			
		||||
	tags: {
 | 
			
		||||
		name: string;
 | 
			
		||||
	}[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
it("deserialize", () => {
 | 
			
		||||
	expect((new Article()).deserialize({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		title: "this is a test",
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ 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",
 | 
			
		||||
		tags: [ {name: "test"}, {name: "foo"} ],
 | 
			
		||||
	}).serialize()).toStrictEqual({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		title: "this is a test",
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ 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",
 | 
			
		||||
		tags: [ {name: "test"}, {name: "foo"} ],
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
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", now),
 | 
			
		||||
	];
 | 
			
		||||
	article.text = "this is a long test.";
 | 
			
		||||
	article.evaluation = 25.23;
 | 
			
		||||
	article.tags = [];
 | 
			
		||||
	article.tags.push({name: "test"});
 | 
			
		||||
	article.tags.push({name: "foo"});
 | 
			
		||||
 | 
			
		||||
	expect(article.isNew()).toBeTruthy();
 | 
			
		||||
	expect(article.getIdentifier()).toStrictEqual(1);
 | 
			
		||||
 | 
			
		||||
	expect(article.serialize()).toStrictEqual({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		title: "this is a test",
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: now.toISOString(), active: true, },
 | 
			
		||||
		],
 | 
			
		||||
		text: "this is a long test.",
 | 
			
		||||
		evaluation: "25.23",
 | 
			
		||||
		tags: [ {name: "test"}, {name: "foo"} ],
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
it("deserialize then save", () => {
 | 
			
		||||
	const article = (new Article()).deserialize({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		title: "this is a test",
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
 | 
			
		||||
			{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date()).toISOString(), active: false, },
 | 
			
		||||
		],
 | 
			
		||||
		text: "this is a long test.",
 | 
			
		||||
		evaluation: "25.23",
 | 
			
		||||
		tags: [ {name: "test"}, {name: "foo"} ],
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	expect(article.isNew()).toBeFalsy();
 | 
			
		||||
	expect(article.isDirty()).toBeFalsy();
 | 
			
		||||
	expect(article.evaluation).toStrictEqual(25.23);
 | 
			
		||||
 | 
			
		||||
	article.text = "Modified text.";
 | 
			
		||||
 | 
			
		||||
	expect(article.isDirty()).toBeTruthy();
 | 
			
		||||
 | 
			
		||||
	expect(article.save()).toStrictEqual({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		text: "Modified text.",
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
it("save with modified submodels", () => {
 | 
			
		||||
	const article = (new Article()).deserialize({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		title: "this is a test",
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ name: "DOE", firstName: "John", email: "test@test.test", createdAt: (new Date()).toISOString(), active: true, },
 | 
			
		||||
			{ name: "TEST", firstName: "Another", email: "another@test.test", createdAt: (new Date()).toISOString(), active: false, },
 | 
			
		||||
		],
 | 
			
		||||
		text: "this is a long test.",
 | 
			
		||||
		evaluation: "25.23",
 | 
			
		||||
		tags: [ {name: "test"}, {name: "foo"} ],
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	article.authors = article.authors.map((author) => {
 | 
			
		||||
		author.name = "TEST";
 | 
			
		||||
		return author;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	expect(article.save()).toStrictEqual({
 | 
			
		||||
		id: 1,
 | 
			
		||||
		authors: [
 | 
			
		||||
			{ name: "TEST", },
 | 
			
		||||
			{}, //{ name: "TEST", firstName: "Another", email: "another@test.test" },
 | 
			
		||||
		],
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,35 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
 | 
			
		||||
import {defineModel, newModel, s} from "../src/library";
 | 
			
		||||
 | 
			
		||||
class Testest {
 | 
			
		||||
	foo: string;
 | 
			
		||||
	bar: number;
 | 
			
		||||
	baz: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("model builder", () => {
 | 
			
		||||
	const modelBuilder = newModel(Testest)
 | 
			
		||||
		.property("foo", s.property.string())
 | 
			
		||||
		.property("bar", s.property.decimal())
 | 
			
		||||
		.property("baz", s.property.boolean())
 | 
			
		||||
		.identifier("foo");
 | 
			
		||||
 | 
			
		||||
	test("build model definition", () => {
 | 
			
		||||
		expect(modelBuilder.definition).toStrictEqual({
 | 
			
		||||
			Class: Testest,
 | 
			
		||||
			identifier: "foo",
 | 
			
		||||
			properties: {
 | 
			
		||||
				foo: s.property.string(),
 | 
			
		||||
				bar: s.property.decimal(),
 | 
			
		||||
				baz: s.property.boolean(),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("build a model", () => {
 | 
			
		||||
		expect(modelBuilder.define()).toStrictEqual(
 | 
			
		||||
			defineModel(modelBuilder.definition),
 | 
			
		||||
		);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +0,0 @@
 | 
			
		|||
import {describe, expect, it} from "vitest";
 | 
			
		||||
import {InvalidTypeValueError, TypeError} from "../src/errors";
 | 
			
		||||
import {s} from "../src/library";
 | 
			
		||||
 | 
			
		||||
describe("errors", () => {
 | 
			
		||||
	it("tests type error", () => {
 | 
			
		||||
		expect(new TypeError(s.property.string().type).message).toBe(
 | 
			
		||||
			"Error in type StringType",
 | 
			
		||||
		);
 | 
			
		||||
		expect(new TypeError(s.property.string().type, "test").message).toBe(
 | 
			
		||||
			"Error in type StringType: test",
 | 
			
		||||
		);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("tests invalid type value error", () => {
 | 
			
		||||
		expect(
 | 
			
		||||
			new InvalidTypeValueError(s.property.decimal().type, ["value"]).message,
 | 
			
		||||
		).toBe('Error in type DecimalType: ["value"] is an invalid value');
 | 
			
		||||
		expect(
 | 
			
		||||
			new InvalidTypeValueError(s.property.decimal().type, ["value"], "test")
 | 
			
		||||
				.message,
 | 
			
		||||
		).toBe("Error in type DecimalType: test");
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,587 +0,0 @@
 | 
			
		|||
import {describe, expect, it} from "vitest";
 | 
			
		||||
import {circular, defineModel, s} from "../src/library";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test class of an account.
 | 
			
		||||
 */
 | 
			
		||||
class Account {
 | 
			
		||||
	id: number;
 | 
			
		||||
	createdAt: Date;
 | 
			
		||||
	name: string;
 | 
			
		||||
	email: string;
 | 
			
		||||
	active: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const AccountModel = s.defineModel({
 | 
			
		||||
	Class: Account,
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
	properties: {
 | 
			
		||||
		id: s.property.numeric(),
 | 
			
		||||
		createdAt: s.property.date(),
 | 
			
		||||
		name: s.property.string(),
 | 
			
		||||
		email: s.property.string(),
 | 
			
		||||
		active: s.property.boolean(),
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test class of an article.
 | 
			
		||||
 */
 | 
			
		||||
class Article {
 | 
			
		||||
	id: number;
 | 
			
		||||
	title: string;
 | 
			
		||||
	authors: Account[];
 | 
			
		||||
	text: string;
 | 
			
		||||
	evaluation: number;
 | 
			
		||||
	tags: {name: string}[];
 | 
			
		||||
	comments: ArticleComment[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ArticleModel = s.defineModel({
 | 
			
		||||
	Class: Article,
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
	properties: {
 | 
			
		||||
		id: s.property.numeric(),
 | 
			
		||||
		title: s.property.string(),
 | 
			
		||||
		authors: s.property.array(s.property.model(() => AccountModel)),
 | 
			
		||||
		text: s.property.string(),
 | 
			
		||||
		evaluation: s.property.decimal(),
 | 
			
		||||
		tags: s.property.array(s.property.object({name: s.property.string()})),
 | 
			
		||||
		comments: s.property.array(s.property.model(() => ArticleCommentModel)),
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Test class of a comment on an article.
 | 
			
		||||
 */
 | 
			
		||||
class ArticleComment {
 | 
			
		||||
	id: number;
 | 
			
		||||
	article?: Article;
 | 
			
		||||
	author: Account;
 | 
			
		||||
	message: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ArticleCommentModel = s.defineModel({
 | 
			
		||||
	Class: ArticleComment,
 | 
			
		||||
	identifier: "id",
 | 
			
		||||
	properties: {
 | 
			
		||||
		id: s.property.numeric(),
 | 
			
		||||
		article: s.property.model(circular<Article>(() => ArticleModel)),
 | 
			
		||||
		author: s.property.model(() => AccountModel),
 | 
			
		||||
		message: s.property.string(),
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Get a test account instance.
 | 
			
		||||
 */
 | 
			
		||||
function getTestAccount(): Account {
 | 
			
		||||
	const account = new Account();
 | 
			
		||||
	account.id = 52;
 | 
			
		||||
	account.createdAt = new Date();
 | 
			
		||||
	account.name = "John Doe";
 | 
			
		||||
	account.email = "john@doe.test";
 | 
			
		||||
	account.active = true;
 | 
			
		||||
	return account;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getTestArticle(): Article {
 | 
			
		||||
	const article = new Article();
 | 
			
		||||
	article.id = 1;
 | 
			
		||||
	article.title = "this is a test";
 | 
			
		||||
	article.text = "this is a long test.";
 | 
			
		||||
	article.evaluation = 25.23;
 | 
			
		||||
	article.tags = [{name: "test"}, {name: "foo"}];
 | 
			
		||||
	article.authors = [getTestAccount()];
 | 
			
		||||
	article.comments = [];
 | 
			
		||||
	return article;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("model", () => {
 | 
			
		||||
	it("defines a new model, extending an existing one", () => {
 | 
			
		||||
		class ExtendedAccount extends Account {
 | 
			
		||||
			extendedProperty: string;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const ExtendedAccountModel = s.extend(AccountModel, {
 | 
			
		||||
			Class: ExtendedAccount,
 | 
			
		||||
			properties: {
 | 
			
		||||
				extendedProperty: s.property.string(),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(ExtendedAccountModel.definition).toEqual({
 | 
			
		||||
			Class: ExtendedAccount,
 | 
			
		||||
			identifier: "id",
 | 
			
		||||
			properties: {
 | 
			
		||||
				id: s.property.numeric(),
 | 
			
		||||
				createdAt: s.property.date(),
 | 
			
		||||
				name: s.property.string(),
 | 
			
		||||
				email: s.property.string(),
 | 
			
		||||
				active: s.property.boolean(),
 | 
			
		||||
				extendedProperty: s.property.string(),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
	it("initializes a new model", () => {
 | 
			
		||||
		const article = getTestArticle();
 | 
			
		||||
		const newModel = ArticleModel.model(article);
 | 
			
		||||
		expect(newModel.instance).toBe(article);
 | 
			
		||||
	});
 | 
			
		||||
	it("gets a model state from its instance", () => {
 | 
			
		||||
		const article = getTestArticle();
 | 
			
		||||
		expect(ArticleModel.model(article).isNew()).toBeTruthy();
 | 
			
		||||
		expect(ArticleModel.model(article).isDirty()).toBeFalsy();
 | 
			
		||||
	});
 | 
			
		||||
	it("gets a model identifier value", () => {
 | 
			
		||||
		const article = getTestArticle();
 | 
			
		||||
		expect(ArticleModel.model(article).getIdentifier()).toBe(1);
 | 
			
		||||
	});
 | 
			
		||||
	it("gets a model composite identifier value", () => {
 | 
			
		||||
		class CompositeModel {
 | 
			
		||||
			static model = s.defineModel({
 | 
			
		||||
				Class: CompositeModel,
 | 
			
		||||
				properties: {
 | 
			
		||||
					firstId: s.property.numeric(),
 | 
			
		||||
					secondId: s.property.numeric(),
 | 
			
		||||
					label: s.property.string(),
 | 
			
		||||
				},
 | 
			
		||||
				identifier: ["firstId", "secondId"],
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			firstId: number;
 | 
			
		||||
			secondId: number;
 | 
			
		||||
			label: string;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		expect(
 | 
			
		||||
			CompositeModel.model
 | 
			
		||||
				.model(
 | 
			
		||||
					Object.assign(new CompositeModel(), {
 | 
			
		||||
						firstId: 5,
 | 
			
		||||
						secondId: 6,
 | 
			
		||||
						label: "test",
 | 
			
		||||
					}),
 | 
			
		||||
				)
 | 
			
		||||
				.getIdentifier(),
 | 
			
		||||
		).toStrictEqual([5, 6]);
 | 
			
		||||
	});
 | 
			
		||||
	it("checks model dirtiness when altered, then reset diff", () => {
 | 
			
		||||
		const article = getTestArticle();
 | 
			
		||||
		expect(ArticleModel.model(article).isDirty()).toBeFalsy();
 | 
			
		||||
		article.title = "new title";
 | 
			
		||||
		expect(ArticleModel.model(article).isDirty()).toBeTruthy();
 | 
			
		||||
		ArticleModel.model(article).resetDiff();
 | 
			
		||||
		expect(ArticleModel.model(article).isDirty()).toBeFalsy();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("deserializes a model from a serialized form", () => {
 | 
			
		||||
		const expectedArticle = Object.assign(new Article(), {
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				Object.assign(new Account(), {
 | 
			
		||||
					id: 52,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: new Date("2022-08-07T08:47:01.000Z"),
 | 
			
		||||
					active: true,
 | 
			
		||||
				}),
 | 
			
		||||
				Object.assign(new Account(), {
 | 
			
		||||
					id: 4,
 | 
			
		||||
					name: "Tester",
 | 
			
		||||
					email: "another@test.test",
 | 
			
		||||
					createdAt: new Date("2022-09-07T18:32:55.000Z"),
 | 
			
		||||
					active: false,
 | 
			
		||||
				}),
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: 8.52,
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			comments: [
 | 
			
		||||
				Object.assign(new ArticleComment(), {
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: Object.assign(new Account(), {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "John Doe",
 | 
			
		||||
						email: "test@test.test",
 | 
			
		||||
						createdAt: new Date("2022-08-07T08:47:01.000Z"),
 | 
			
		||||
						active: true,
 | 
			
		||||
					}),
 | 
			
		||||
					message: "comment content",
 | 
			
		||||
				}),
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const deserializedArticle = ArticleModel.parse({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 52,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
					active: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					id: 4,
 | 
			
		||||
					name: "Tester",
 | 
			
		||||
					email: "another@test.test",
 | 
			
		||||
					createdAt: "2022-09-07T18:32:55.000Z",
 | 
			
		||||
					active: false,
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: "8.52",
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			comments: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "John Doe",
 | 
			
		||||
						email: "test@test.test",
 | 
			
		||||
						createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
						active: true,
 | 
			
		||||
					},
 | 
			
		||||
					message: "comment content",
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		const deserializedArticleProperties =
 | 
			
		||||
			ArticleModel.model(deserializedArticle).getInstanceProperties();
 | 
			
		||||
		delete deserializedArticleProperties.authors[0]._sharkitek;
 | 
			
		||||
		delete deserializedArticleProperties.authors[1]._sharkitek;
 | 
			
		||||
		delete deserializedArticleProperties.comments[0]._sharkitek;
 | 
			
		||||
		delete (deserializedArticleProperties.comments[0].author as any)._sharkitek;
 | 
			
		||||
		const expectedArticleProperties =
 | 
			
		||||
			ArticleModel.model(expectedArticle).getInstanceProperties();
 | 
			
		||||
		delete expectedArticleProperties.authors[0]._sharkitek;
 | 
			
		||||
		delete expectedArticleProperties.authors[1]._sharkitek;
 | 
			
		||||
		delete expectedArticleProperties.comments[0]._sharkitek;
 | 
			
		||||
		delete (expectedArticleProperties.comments[0].author as any)._sharkitek;
 | 
			
		||||
		expect(deserializedArticleProperties).toEqual(expectedArticleProperties);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("serializes an initialized model", () => {
 | 
			
		||||
		const article = getTestArticle();
 | 
			
		||||
		expect(ArticleModel.model(article).serialize()).toEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: "25.23",
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			authors: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 52,
 | 
			
		||||
					createdAt: article.authors[0].createdAt.toISOString(),
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "john@doe.test",
 | 
			
		||||
					active: true,
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			comments: [],
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("deserializes, changes and patches", () => {
 | 
			
		||||
		const deserializedArticle = ArticleModel.parse({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 52,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
					active: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					id: 4,
 | 
			
		||||
					name: "Tester",
 | 
			
		||||
					email: "another@test.test",
 | 
			
		||||
					createdAt: "2022-09-07T18:32:55.000Z",
 | 
			
		||||
					active: false,
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: "8.52",
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			comments: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "John Doe",
 | 
			
		||||
						email: "test@test.test",
 | 
			
		||||
						createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
						active: true,
 | 
			
		||||
					},
 | 
			
		||||
					message: "comment content",
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deserializedArticle.text = "A new text for a new life!";
 | 
			
		||||
 | 
			
		||||
		expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			text: "A new text for a new life!",
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deserializedArticle.evaluation = 5.24;
 | 
			
		||||
 | 
			
		||||
		expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			evaluation: "5.24",
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("patches with modified submodels", () => {
 | 
			
		||||
		const deserializedArticle = ArticleModel.parse({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 52,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
					active: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					id: 4,
 | 
			
		||||
					name: "Tester",
 | 
			
		||||
					email: "another@test.test",
 | 
			
		||||
					createdAt: "2022-09-07T18:32:55.000Z",
 | 
			
		||||
					active: false,
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: "8.52",
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			comments: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "John Doe",
 | 
			
		||||
						email: "test@test.test",
 | 
			
		||||
						createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
						active: true,
 | 
			
		||||
					},
 | 
			
		||||
					message: "comment content",
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deserializedArticle.authors[1].active = true;
 | 
			
		||||
 | 
			
		||||
		expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			authors: [{id: 52}, {id: 4, active: true}],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deserializedArticle.comments[0].author.name = "Johnny";
 | 
			
		||||
 | 
			
		||||
		expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			comments: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "Johnny",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("deserializes and patches with fields that are not properties", () => {
 | 
			
		||||
		class TestModel {
 | 
			
		||||
			static model = defineModel({
 | 
			
		||||
				Class: TestModel,
 | 
			
		||||
				properties: {
 | 
			
		||||
					id: s.property.numeric(),
 | 
			
		||||
					label: s.property.string(),
 | 
			
		||||
				},
 | 
			
		||||
				identifier: "id",
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			id: number;
 | 
			
		||||
			label: string;
 | 
			
		||||
 | 
			
		||||
			notAProperty: {hello: string} = {hello: "world"};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const deserializedModel = TestModel.model.parse({
 | 
			
		||||
			id: 5,
 | 
			
		||||
			label: "testing",
 | 
			
		||||
		});
 | 
			
		||||
		expect(deserializedModel.id).toBe(5);
 | 
			
		||||
		expect(deserializedModel.label).toBe("testing");
 | 
			
		||||
		expect(deserializedModel.notAProperty?.hello).toBe("world");
 | 
			
		||||
 | 
			
		||||
		const clonedDeserializedModel = TestModel.model
 | 
			
		||||
			.model(deserializedModel)
 | 
			
		||||
			.clone();
 | 
			
		||||
 | 
			
		||||
		deserializedModel.label = "new!";
 | 
			
		||||
		expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({
 | 
			
		||||
			id: 5,
 | 
			
		||||
			label: "new!",
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		deserializedModel.notAProperty.hello = "monster";
 | 
			
		||||
		expect(TestModel.model.model(deserializedModel).patch()).toStrictEqual({
 | 
			
		||||
			id: 5,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(TestModel.model.model(deserializedModel).serialize()).toStrictEqual({
 | 
			
		||||
			id: 5,
 | 
			
		||||
			label: "new!",
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(
 | 
			
		||||
			TestModel.model.model(clonedDeserializedModel).serialize(),
 | 
			
		||||
		).toStrictEqual({id: 5, label: "testing"});
 | 
			
		||||
		expect(clonedDeserializedModel.notAProperty.hello).toEqual("world");
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("assigns properties, ignoring fields which are not properties", () => {
 | 
			
		||||
		const deserializedArticle = ArticleModel.parse({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 52,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
					active: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					id: 4,
 | 
			
		||||
					name: "Tester",
 | 
			
		||||
					email: "another@test.test",
 | 
			
		||||
					createdAt: "2022-09-07T18:32:55.000Z",
 | 
			
		||||
					active: false,
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long test.",
 | 
			
		||||
			evaluation: "8.52",
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
			comments: [
 | 
			
		||||
				{
 | 
			
		||||
					id: 542,
 | 
			
		||||
					author: {
 | 
			
		||||
						id: 52,
 | 
			
		||||
						name: "John Doe",
 | 
			
		||||
						email: "test@test.test",
 | 
			
		||||
						createdAt: "2022-08-07T08:47:01.000Z",
 | 
			
		||||
						active: true,
 | 
			
		||||
					},
 | 
			
		||||
					message: "comment content",
 | 
			
		||||
				},
 | 
			
		||||
			],
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Assign title and text, html is silently ignored.
 | 
			
		||||
		ArticleModel.model(deserializedArticle).assign({
 | 
			
		||||
			title: "something else",
 | 
			
		||||
			text: "fully new text! yes!",
 | 
			
		||||
			html: "<p>fully new text! yes!</p>",
 | 
			
		||||
		});
 | 
			
		||||
		expect((deserializedArticle as any)?.html).toBeUndefined();
 | 
			
		||||
		expect(ArticleModel.model(deserializedArticle).patch()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "something else",
 | 
			
		||||
			text: "fully new text! yes!",
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("initializes a model from properties values", () => {
 | 
			
		||||
		const testArticle = ArticleModel.from({
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				AccountModel.from({
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: new Date(),
 | 
			
		||||
					active: true,
 | 
			
		||||
				}),
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long text",
 | 
			
		||||
			evaluation: 8.52,
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
 | 
			
		||||
			unknownField: true,
 | 
			
		||||
			anotherOne: "test",
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		expect(testArticle.title).toBe("this is a test");
 | 
			
		||||
		expect(testArticle.text).toBe("this is a long text");
 | 
			
		||||
		expect(testArticle.evaluation).toBe(8.52);
 | 
			
		||||
		expect(testArticle.authors).toHaveLength(1);
 | 
			
		||||
		expect(testArticle.authors[0]?.name).toBe("John Doe");
 | 
			
		||||
		expect((testArticle as any).unknownField).toBeUndefined();
 | 
			
		||||
		expect((testArticle as any).anotherOne).toBeUndefined();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	it("applies patches to an existing model", () => {
 | 
			
		||||
		const testArticle = ArticleModel.from({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			title: "this is a test",
 | 
			
		||||
			authors: [
 | 
			
		||||
				AccountModel.from({
 | 
			
		||||
					id: 55,
 | 
			
		||||
					name: "John Doe",
 | 
			
		||||
					email: "test@test.test",
 | 
			
		||||
					createdAt: new Date(),
 | 
			
		||||
					active: true,
 | 
			
		||||
				}),
 | 
			
		||||
			],
 | 
			
		||||
			text: "this is a long text",
 | 
			
		||||
			evaluation: 8.52,
 | 
			
		||||
			tags: [{name: "test"}, {name: "foo"}],
 | 
			
		||||
 | 
			
		||||
			unknownField: true,
 | 
			
		||||
			anotherOne: "test",
 | 
			
		||||
		});
 | 
			
		||||
		ArticleModel.model(testArticle).resetDiff();
 | 
			
		||||
 | 
			
		||||
		// Test simple patch.
 | 
			
		||||
		ArticleModel.model(testArticle).applyPatch({
 | 
			
		||||
			title: "new title",
 | 
			
		||||
		});
 | 
			
		||||
		expect(testArticle.title).toBe("new title");
 | 
			
		||||
		expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Test originals update propagation.
 | 
			
		||||
		ArticleModel.model(testArticle).applyPatch({
 | 
			
		||||
			authors: [{email: "john@test.test"}],
 | 
			
		||||
		});
 | 
			
		||||
		expect(testArticle.authors[0].email).toBe("john@test.test");
 | 
			
		||||
		expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		// Test without originals update.
 | 
			
		||||
		ArticleModel.model(testArticle).applyPatch(
 | 
			
		||||
			{
 | 
			
		||||
				authors: [{name: "Johnny"}],
 | 
			
		||||
			},
 | 
			
		||||
			false,
 | 
			
		||||
		);
 | 
			
		||||
		expect(testArticle.authors[0].name).toBe("Johnny");
 | 
			
		||||
		expect(ArticleModel.model(testArticle).serializeDiff()).toStrictEqual({
 | 
			
		||||
			id: 1,
 | 
			
		||||
			authors: [{id: 55, name: "Johnny"}],
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,440 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {ArrayType, InvalidTypeValueError, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
class TestModel {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
	price: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("array type", () => {
 | 
			
		||||
	const testModel = s.defineModel({
 | 
			
		||||
		Class: TestModel,
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: s.property.numeric(),
 | 
			
		||||
			name: s.property.string(),
 | 
			
		||||
			price: s.property.decimal(),
 | 
			
		||||
		},
 | 
			
		||||
		identifier: "id",
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const arrayType = s.property.array(s.property.model(testModel));
 | 
			
		||||
		expect(arrayType.type).toBeInstanceOf(ArrayType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const testProperty = s.property.array(s.property.decimal());
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(testProperty.type.serialize([12.547, 8, -52.11])).toEqual([
 | 
			
		||||
				"12.547",
 | 
			
		||||
				"8",
 | 
			
		||||
				"-52.11",
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(testProperty.type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(testProperty.type.deserialize(["12.547", "8", "-52.11"])).toEqual([
 | 
			
		||||
				12.547, 8, -52.11,
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(testProperty.type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.deserialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			{
 | 
			
		||||
				// Try to serialize the difference of an array with one changed model.
 | 
			
		||||
				const propertyValue = [
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 2,
 | 
			
		||||
							name: "another",
 | 
			
		||||
							price: 12.55,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
				];
 | 
			
		||||
				propertyValue[0].name = "new";
 | 
			
		||||
				expect(
 | 
			
		||||
					s.property
 | 
			
		||||
						.array(s.property.model(testModel))
 | 
			
		||||
						.type.serializeDiff(propertyValue),
 | 
			
		||||
				).toEqual([{id: 1, name: "new"}, {id: 2}]);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(testProperty.type.serializeDiff(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8, -52.11]),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(null, [12.547, 8, -52.11]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(undefined, [12.547, 8, -52.11]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8, -52.11], null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8, -52.11], undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, -52.11, 8]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, -52.11, 8], [12.547, 8, -52.11]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8, -52.11], [12.547, 8]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged([12.547, 8], [12.547, 8, -52.11]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(testProperty.type.hasChanged({} as any, {} as any)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
				),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, ["12.547", "8", "-52.11"]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, [
 | 
			
		||||
					"12.547",
 | 
			
		||||
					"8",
 | 
			
		||||
					"-52.11",
 | 
			
		||||
				]),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(["12.547", "8", "-52.11"], null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
					undefined,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
					["12.547", "-52.11", "8"],
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "-52.11", "8"],
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
					["12.547", "8"],
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					["12.547", "8"],
 | 
			
		||||
					["12.547", "8", "-52.11"],
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			{
 | 
			
		||||
				// Try to reset the difference of an array with one changed model.
 | 
			
		||||
				const propertyValue = [
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 2,
 | 
			
		||||
							name: "another",
 | 
			
		||||
							price: 12.55,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
				];
 | 
			
		||||
				propertyValue[0].name = "new";
 | 
			
		||||
				expect(
 | 
			
		||||
					s.property
 | 
			
		||||
						.array(s.property.model(testModel))
 | 
			
		||||
						.type.serializeDiff(propertyValue),
 | 
			
		||||
				).toEqual([{id: 1, name: "new"}, {id: 2}]);
 | 
			
		||||
				s.property
 | 
			
		||||
					.array(s.property.model(testModel))
 | 
			
		||||
					.type.resetDiff(propertyValue);
 | 
			
		||||
				expect(
 | 
			
		||||
					s.property
 | 
			
		||||
						.array(s.property.model(testModel))
 | 
			
		||||
						.type.serializeDiff(propertyValue),
 | 
			
		||||
				).toEqual([{id: 1}, {id: 2}]);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			testProperty.type.resetDiff(undefined);
 | 
			
		||||
			testProperty.type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.resetDiff({} as any)).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("clone", () => {
 | 
			
		||||
			{
 | 
			
		||||
				// Test that values are cloned in a different array.
 | 
			
		||||
				const propertyValue = [12.547, 8, -52.11];
 | 
			
		||||
				const clonedPropertyValue = testProperty.type.clone(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).toEqual(propertyValue);
 | 
			
		||||
			}
 | 
			
		||||
			{
 | 
			
		||||
				// Test that values are cloned recursively.
 | 
			
		||||
				const propertyValue = [
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 2,
 | 
			
		||||
							name: "another",
 | 
			
		||||
							price: 12.55,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
				];
 | 
			
		||||
 | 
			
		||||
				// The arrays are different.
 | 
			
		||||
				const clonedPropertyValue = s.property
 | 
			
		||||
					.array(s.property.model(testModel))
 | 
			
		||||
					.type.clone(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
 | 
			
		||||
				// Array values must be different objects but have the same values.
 | 
			
		||||
				expect(clonedPropertyValue[0]).not.toBe(propertyValue[0]);
 | 
			
		||||
				expect(clonedPropertyValue[1]).not.toBe(propertyValue[1]);
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(clonedPropertyValue[0]).getInstanceProperties(),
 | 
			
		||||
				).toEqual(testModel.model(propertyValue[0]).getInstanceProperties());
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(clonedPropertyValue[1]).getInstanceProperties(),
 | 
			
		||||
				).toEqual(testModel.model(propertyValue[1]).getInstanceProperties());
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.clone(undefined)).toBe(undefined);
 | 
			
		||||
			expect(testProperty.type.clone(null)).toBe(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.clone({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		{
 | 
			
		||||
			// Test simple patch.
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					[12.547, 8, -52.11],
 | 
			
		||||
					["12.547", "444.34", "-52.11"],
 | 
			
		||||
					true,
 | 
			
		||||
				),
 | 
			
		||||
			).toEqual([12.547, 444.34, -52.11]);
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					undefined,
 | 
			
		||||
					["12.547", "444.34", "-52.11"],
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toEqual([12.547, 444.34, -52.11]);
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					null,
 | 
			
		||||
					["12.547", "444.34", "-52.11"],
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toEqual([12.547, 444.34, -52.11]);
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch([12.547, 8, -52.11], undefined, false),
 | 
			
		||||
			).toBeUndefined();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch([12.547, 8, -52.11], null, false),
 | 
			
		||||
			).toBeNull();
 | 
			
		||||
		}
 | 
			
		||||
		{
 | 
			
		||||
			// Invalid patch.
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.applyPatch([12.547, 8, -52.11], {} as any, false),
 | 
			
		||||
			).toThrow(InvalidTypeValueError);
 | 
			
		||||
		}
 | 
			
		||||
		{
 | 
			
		||||
			// Test recursive patch.
 | 
			
		||||
			const propertyValue = [
 | 
			
		||||
				testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
 | 
			
		||||
				).instance,
 | 
			
		||||
				testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {
 | 
			
		||||
						id: 2,
 | 
			
		||||
						name: "another",
 | 
			
		||||
						price: 12.55,
 | 
			
		||||
					}),
 | 
			
		||||
				).instance,
 | 
			
		||||
			];
 | 
			
		||||
 | 
			
		||||
			const patched = s.property
 | 
			
		||||
				.array(s.property.model(testModel))
 | 
			
		||||
				.type.applyPatch(
 | 
			
		||||
					propertyValue,
 | 
			
		||||
					[
 | 
			
		||||
						{
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "new",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							id: 2,
 | 
			
		||||
							price: "13.65",
 | 
			
		||||
						},
 | 
			
		||||
					],
 | 
			
		||||
					true,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			// Check applied patch.
 | 
			
		||||
			expect(patched).toEqual([
 | 
			
		||||
				testModel.parse({id: 1, name: "new", price: "22"}),
 | 
			
		||||
				testModel.parse({id: 2, name: "another", price: "13.65"}),
 | 
			
		||||
			]);
 | 
			
		||||
 | 
			
		||||
			// Check that originals have been updated.
 | 
			
		||||
			expect(testModel.model(patched[0]).serializeDiff()).toEqual({id: 1});
 | 
			
		||||
			patched[0].name = "test";
 | 
			
		||||
			expect(testModel.model(patched[0]).serializeDiff()).toEqual({
 | 
			
		||||
				id: 1,
 | 
			
		||||
				name: "test",
 | 
			
		||||
			});
 | 
			
		||||
			expect(testModel.model(patched[1]).serializeDiff()).toEqual({id: 2});
 | 
			
		||||
			patched[1].price = 12.55;
 | 
			
		||||
			expect(testModel.model(patched[1]).serializeDiff()).toEqual({
 | 
			
		||||
				id: 2,
 | 
			
		||||
				price: "12.55",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
		{
 | 
			
		||||
			// Test recursive patch without originals update.-
 | 
			
		||||
			const propertyValue = [
 | 
			
		||||
				testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {id: 1, name: "test", price: 22}),
 | 
			
		||||
				).instance,
 | 
			
		||||
				testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {
 | 
			
		||||
						id: 2,
 | 
			
		||||
						name: "another",
 | 
			
		||||
						price: 12.55,
 | 
			
		||||
					}),
 | 
			
		||||
				).instance,
 | 
			
		||||
			];
 | 
			
		||||
 | 
			
		||||
			const patched = s.property
 | 
			
		||||
				.array(s.property.model(testModel))
 | 
			
		||||
				.type.applyPatch(
 | 
			
		||||
					propertyValue,
 | 
			
		||||
					[
 | 
			
		||||
						{
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "new",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							id: 2,
 | 
			
		||||
							price: "13.65",
 | 
			
		||||
						},
 | 
			
		||||
					],
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			// Check that originals haven't been updated.
 | 
			
		||||
			expect(testModel.model(patched[0]).serializeDiff()).toEqual({
 | 
			
		||||
				id: 1,
 | 
			
		||||
				name: "new",
 | 
			
		||||
			});
 | 
			
		||||
			expect(testModel.model(patched[1]).serializeDiff()).toEqual({
 | 
			
		||||
				id: 2,
 | 
			
		||||
				price: "13.65",
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,175 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {BooleanType, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("boolean type", () => {
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		{
 | 
			
		||||
			const booleanType = s.property.boolean();
 | 
			
		||||
			expect(booleanType.type).toBeInstanceOf(BooleanType);
 | 
			
		||||
		}
 | 
			
		||||
		{
 | 
			
		||||
			const boolType = s.property.bool();
 | 
			
		||||
			expect(boolType.type).toBeInstanceOf(BooleanType);
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(s.property.boolean().type.serialize(false)).toBe(false);
 | 
			
		||||
			expect(s.property.boolean().type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.boolean().type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.boolean().type.serialize(1 as any)).toBeTruthy();
 | 
			
		||||
			expect(s.property.boolean().type.serialize(0 as any)).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(s.property.boolean().type.deserialize(false)).toBe(false);
 | 
			
		||||
			expect(s.property.boolean().type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.boolean().type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.boolean().type.deserialize(1 as any)).toBeTruthy();
 | 
			
		||||
			expect(s.property.boolean().type.deserialize(0 as any)).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(s.property.boolean().type.serializeDiff(true)).toBe(true);
 | 
			
		||||
			expect(s.property.boolean().type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(s.property.boolean().type.serializeDiff(undefined)).toBe(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.boolean().type.serializeDiff(1 as any)).toBeTruthy();
 | 
			
		||||
			expect(s.property.boolean().type.serializeDiff(0 as any)).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(s.property.boolean().type.hasChanged(true, true)).toBeFalsy();
 | 
			
		||||
			expect(s.property.boolean().type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.boolean().type.hasChanged(null, false)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(undefined, false),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.boolean().type.hasChanged(false, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(false, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(false, false),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(null, false),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(undefined, false),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(false, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged(false, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.boolean().type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.boolean()
 | 
			
		||||
					.type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			s.property.boolean().type.resetDiff(false);
 | 
			
		||||
			s.property.boolean().type.resetDiff(undefined);
 | 
			
		||||
			s.property.boolean().type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.boolean().type.resetDiff({} as any),
 | 
			
		||||
			).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.boolean().type.clone({} as any)).toStrictEqual({});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(false, true, true),
 | 
			
		||||
		).toBeTruthy();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(false, true, false),
 | 
			
		||||
		).toBeTruthy();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(true, false, false),
 | 
			
		||||
		).toBeFalsy();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(false, undefined, false),
 | 
			
		||||
		).toBeUndefined();
 | 
			
		||||
		expect(s.property.boolean().type.applyPatch(false, null, false)).toBeNull();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(undefined, null, false),
 | 
			
		||||
		).toBeNull();
 | 
			
		||||
		expect(s.property.boolean().type.applyPatch(null, null, false)).toBeNull();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.boolean().type.applyPatch(null, false, false),
 | 
			
		||||
		).toBeFalsy();
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,232 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {DateType, InvalidTypeValueError, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("date type", () => {
 | 
			
		||||
	const testDate = new Date();
 | 
			
		||||
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const dateType = s.property.date();
 | 
			
		||||
		expect(dateType.type).toBeInstanceOf(DateType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(s.property.date().type.serialize(testDate)).toBe(
 | 
			
		||||
				testDate.toISOString(),
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.date().type.serialize(new Date(NaN))).toBe(
 | 
			
		||||
				new Date(NaN).toString(),
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.date().type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.date().type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => s.property.date().type.serialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.deserialize(testDate.toISOString())?.getTime(),
 | 
			
		||||
			).toBe(testDate.getTime());
 | 
			
		||||
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.deserialize("2565152-2156121-256123121 5121544175:21515612")
 | 
			
		||||
					.valueOf(),
 | 
			
		||||
			).toBeNaN();
 | 
			
		||||
 | 
			
		||||
			expect(s.property.date().type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.date().type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.deserialize({} as any)
 | 
			
		||||
					.getTime(),
 | 
			
		||||
			).toBe(NaN);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(s.property.date().type.serializeDiff(new Date(testDate))).toBe(
 | 
			
		||||
				testDate.toISOString(),
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.date().type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(s.property.date().type.serializeDiff(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.date().type.serializeDiff({} as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(testDate, new Date(testDate)),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(s.property.date().type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(s.property.date().type.hasChanged(null, undefined)).toBeTruthy();
 | 
			
		||||
			expect(s.property.date().type.hasChanged(undefined, null)).toBeTruthy();
 | 
			
		||||
			expect(s.property.date().type.hasChanged(null, testDate)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(undefined, testDate),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.date().type.hasChanged(testDate, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(new Date(NaN), null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(new Date(NaN), undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(new Date(NaN), new Date(NaN)),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						testDate.toISOString(),
 | 
			
		||||
						new Date(testDate).toISOString(),
 | 
			
		||||
					),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(null, testDate.toISOString()),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(undefined, testDate.toISOString()),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(testDate.toISOString(), null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(new Date(NaN).toString(), null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(new Date(NaN).toString(), undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.date()
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						new Date(NaN).toString(),
 | 
			
		||||
						new Date(NaN).toString(),
 | 
			
		||||
					),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.date().type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(s.property.date().type.clone({} as any)).toStrictEqual({});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			s.property.date().type.resetDiff(testDate);
 | 
			
		||||
			s.property.date().type.resetDiff(undefined);
 | 
			
		||||
			s.property.date().type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => s.property.date().type.resetDiff({} as any)).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("clone", () => {
 | 
			
		||||
		// Test that the date is cloned in a different object.
 | 
			
		||||
		const propertyValue = new Date();
 | 
			
		||||
		const clonedPropertyValue = s.property.date().type.clone(propertyValue);
 | 
			
		||||
		expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
		expect(clonedPropertyValue).toEqual(propertyValue);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property
 | 
			
		||||
				.date()
 | 
			
		||||
				.type.applyPatch(new Date("2022-02-22"), testDate.toISOString(), false)
 | 
			
		||||
				?.getTime(),
 | 
			
		||||
		).toBe(testDate.getTime());
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property
 | 
			
		||||
				.date()
 | 
			
		||||
				.type.applyPatch(null, testDate.toISOString(), true)
 | 
			
		||||
				?.getTime(),
 | 
			
		||||
		).toBe(testDate.getTime());
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property
 | 
			
		||||
				.date()
 | 
			
		||||
				.type.applyPatch(
 | 
			
		||||
					undefined,
 | 
			
		||||
					"2565152-2156121-256123121 5121544175:21515612",
 | 
			
		||||
					false,
 | 
			
		||||
				)
 | 
			
		||||
				.valueOf(),
 | 
			
		||||
		).toBeNaN();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.date().type.applyPatch(new Date(), undefined, false),
 | 
			
		||||
		).toBeUndefined();
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.date().type.applyPatch(new Date(), null, false),
 | 
			
		||||
		).toBeNull();
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,164 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {DecimalType, InvalidTypeValueError, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("decimal type", () => {
 | 
			
		||||
	test("decimal type definition", () => {
 | 
			
		||||
		const decimalType = s.property.decimal();
 | 
			
		||||
		expect(decimalType.type).toBeInstanceOf(DecimalType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(s.property.decimal().type.serialize(5.257)).toBe("5.257");
 | 
			
		||||
 | 
			
		||||
			expect(s.property.decimal().type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.decimal().type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => s.property.decimal().type.serialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(s.property.decimal().type.deserialize("5.257")).toBe(5.257);
 | 
			
		||||
			expect(s.property.decimal().type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.decimal().type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
 | 
			
		||||
			expect(s.property.decimal().type.deserialize({} as any)).toBe(NaN);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(s.property.decimal().type.serializeDiff(542)).toBe("542");
 | 
			
		||||
 | 
			
		||||
			expect(s.property.decimal().type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(s.property.decimal().type.serializeDiff(undefined)).toBe(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.decimal().type.serializeDiff({} as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			s.property.decimal().type.resetDiff(5.257);
 | 
			
		||||
			s.property.decimal().type.resetDiff(undefined);
 | 
			
		||||
			s.property.decimal().type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.decimal().type.resetDiff({} as any),
 | 
			
		||||
			).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(s.property.decimal().type.hasChanged(5.257, 5.257)).toBeFalsy();
 | 
			
		||||
			expect(s.property.decimal().type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.decimal().type.hasChanged(null, 5.257)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(undefined, 5.257),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.decimal().type.hasChanged(5.257, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(5.257, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged("5.257", "5.257"),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(null, "5.257"),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged(undefined, "5.257"),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged("5.257", null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged("5.257", undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.decimal().type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.decimal()
 | 
			
		||||
					.type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.decimal().type.clone({} as any)).toStrictEqual({});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		expect(s.property.decimal().type.applyPatch(1, "5.257", false)).toBe(5.257);
 | 
			
		||||
		expect(s.property.decimal().type.applyPatch(undefined, "5.257", true)).toBe(
 | 
			
		||||
			5.257,
 | 
			
		||||
		);
 | 
			
		||||
		expect(s.property.decimal().type.applyPatch(null, "5.257", false)).toBe(
 | 
			
		||||
			5.257,
 | 
			
		||||
		);
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.decimal().type.applyPatch(5.257, undefined, false),
 | 
			
		||||
		).toBeUndefined();
 | 
			
		||||
		expect(s.property.decimal().type.applyPatch(5.257, null, false)).toBeNull();
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,283 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {InvalidTypeValueError, s} from "../../../src/library";
 | 
			
		||||
import {MapType} from "../../../src/model/types/map";
 | 
			
		||||
 | 
			
		||||
describe("map type", () => {
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const mapType = s.property.map(s.property.string(), s.property.numeric());
 | 
			
		||||
		expect(mapType.type).toBeInstanceOf(MapType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const testProperty = s.property.map(
 | 
			
		||||
		s.property.string(),
 | 
			
		||||
		s.property.decimal(),
 | 
			
		||||
	);
 | 
			
		||||
	const testMapValue = new Map<string, number>();
 | 
			
		||||
	testMapValue.set("test", 1.52);
 | 
			
		||||
	testMapValue.set("another", 55);
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(testProperty.type.serialize(testMapValue)).toEqual({
 | 
			
		||||
				test: "1.52",
 | 
			
		||||
				another: "55",
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serialize(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.serialize(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serialize(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serialize([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.deserialize({
 | 
			
		||||
					test: "1.52",
 | 
			
		||||
					another: "55",
 | 
			
		||||
				}),
 | 
			
		||||
			).toEqual(testMapValue);
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.deserialize(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.deserialize(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.deserialize(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.deserialize([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(testProperty.type.serializeDiff(testMapValue)).toEqual({
 | 
			
		||||
				test: "1.52",
 | 
			
		||||
				another: "55",
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serializeDiff(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.serializeDiff(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			const anotherTestMapValue = new Map<string, number>();
 | 
			
		||||
			anotherTestMapValue.set("test", 1.52);
 | 
			
		||||
			anotherTestMapValue.set("another", 55);
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			anotherTestMapValue.set("test", 1.521);
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			anotherTestMapValue.delete("test");
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(testMapValue, anotherTestMapValue),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, testMapValue)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(undefined, testMapValue),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(testMapValue, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(testMapValue, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
					{test: "1.521", another: "55"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
					{another: "55"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, {
 | 
			
		||||
					test: "1.52",
 | 
			
		||||
					another: "55",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, {
 | 
			
		||||
					test: "1.52",
 | 
			
		||||
					another: "55",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
					null,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "1.52", another: "55"},
 | 
			
		||||
					undefined,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			testProperty.type.resetDiff(testMapValue);
 | 
			
		||||
			testProperty.type.resetDiff(undefined);
 | 
			
		||||
			testProperty.type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("clone", () => {
 | 
			
		||||
			{
 | 
			
		||||
				// Test that keys and values are cloned in a different map.
 | 
			
		||||
				const clonedTestMapValue = testProperty.type.clone(testMapValue);
 | 
			
		||||
				expect(clonedTestMapValue).not.toBe(testMapValue);
 | 
			
		||||
				expect(clonedTestMapValue).toEqual(testMapValue);
 | 
			
		||||
			}
 | 
			
		||||
			{
 | 
			
		||||
				// Test that values are cloned in a different object.
 | 
			
		||||
				const propertyValue = new Map();
 | 
			
		||||
				propertyValue.set("test", [12, 11]);
 | 
			
		||||
				const clonedPropertyValue = s.property
 | 
			
		||||
					.stringMap(s.property.array(s.property.numeric()))
 | 
			
		||||
					.type.clone(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).toEqual(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue.get("test")).not.toBe(
 | 
			
		||||
					propertyValue.get("test"),
 | 
			
		||||
				);
 | 
			
		||||
				expect(clonedPropertyValue.get("test")).toEqual(
 | 
			
		||||
					propertyValue.get("test"),
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
			expect(testProperty.type.clone(undefined)).toBe(undefined);
 | 
			
		||||
			expect(testProperty.type.clone(null)).toBe(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.clone(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.clone([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.clone({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch with undefined / NULL values.
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(testMapValue, undefined, false),
 | 
			
		||||
			).toBeUndefined();
 | 
			
		||||
			expect(testProperty.type.applyPatch(testMapValue, null, true)).toBeNull();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Invalid patch.
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.applyPatch(testMapValue, 5416 as any, false),
 | 
			
		||||
			).toThrow(InvalidTypeValueError);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch.
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					testMapValue,
 | 
			
		||||
					{test: "1.521"},
 | 
			
		||||
					true,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				const expectedMapValue = new Map<string, number>();
 | 
			
		||||
				expectedMapValue.set("test", 1.521);
 | 
			
		||||
				expectedMapValue.set("another", 55);
 | 
			
		||||
				expect(objectInstance).toStrictEqual(expectedMapValue);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					undefined,
 | 
			
		||||
					{test: "1.52"},
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				const expectedMapValue = new Map<string, number>();
 | 
			
		||||
				expectedMapValue.set("test", 1.52);
 | 
			
		||||
				expect(objectInstance).toStrictEqual(expectedMapValue);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					null,
 | 
			
		||||
					{test: "1.52"},
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				const expectedMapValue = new Map<string, number>();
 | 
			
		||||
				expectedMapValue.set("test", 1.52);
 | 
			
		||||
				expect(objectInstance).toStrictEqual(expectedMapValue);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,582 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {InvalidTypeValueError, ModelType, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
class TestModel {
 | 
			
		||||
	id: number;
 | 
			
		||||
	name: string;
 | 
			
		||||
	price: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
describe("model type", () => {
 | 
			
		||||
	const testModel = s.defineModel({
 | 
			
		||||
		Class: TestModel,
 | 
			
		||||
		properties: {
 | 
			
		||||
			id: s.property.numeric(),
 | 
			
		||||
			name: s.property.string(),
 | 
			
		||||
			price: s.property.decimal(),
 | 
			
		||||
		},
 | 
			
		||||
		identifier: "id",
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const modelType = s.property.model(testModel);
 | 
			
		||||
		expect(modelType.type).toBeInstanceOf(ModelType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			const testModelInstance = testModel.model(
 | 
			
		||||
				Object.assign(new TestModel(), {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).instance;
 | 
			
		||||
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serialize(testModelInstance),
 | 
			
		||||
			).toEqual({id: 1, name: "test", price: "12.548777"});
 | 
			
		||||
 | 
			
		||||
			expect(s.property.model(testModel).type.serialize(null)).toEqual(null);
 | 
			
		||||
			expect(s.property.model(testModel).type.serialize(undefined)).toEqual(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serialize(5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serialize([] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serialize(new (class {})() as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			const testModelInstance = testModel.model(
 | 
			
		||||
				Object.assign(new TestModel(), {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).instance;
 | 
			
		||||
 | 
			
		||||
			expect(
 | 
			
		||||
				testModel
 | 
			
		||||
					.model(
 | 
			
		||||
						s.property
 | 
			
		||||
							.model(testModel)
 | 
			
		||||
							.type.deserialize({id: 1, name: "test", price: "12.548777"}),
 | 
			
		||||
					)
 | 
			
		||||
					.getInstanceProperties(),
 | 
			
		||||
			).toEqual(testModel.model(testModelInstance).getInstanceProperties());
 | 
			
		||||
 | 
			
		||||
			expect(s.property.model(testModel).type.deserialize(null)).toEqual(null);
 | 
			
		||||
			expect(s.property.model(testModel).type.deserialize(undefined)).toEqual(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.deserialize(5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.deserialize([] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			// Try to serialize the difference.
 | 
			
		||||
			const testModelInstance = testModel.model(
 | 
			
		||||
				Object.assign(new TestModel(), {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).instance;
 | 
			
		||||
 | 
			
		||||
			testModelInstance.name = "new";
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializeDiff(testModelInstance),
 | 
			
		||||
			).toEqual({id: 1, name: "new"});
 | 
			
		||||
 | 
			
		||||
			expect(s.property.model(testModel).type.serializeDiff(null)).toEqual(
 | 
			
		||||
				null,
 | 
			
		||||
			);
 | 
			
		||||
			expect(s.property.model(testModel).type.serializeDiff(undefined)).toEqual(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serializeDiff(5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serializeDiff([] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.serializeDiff(new (class {})() as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			{
 | 
			
		||||
				const testModelInstance = testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {
 | 
			
		||||
						id: 1,
 | 
			
		||||
						name: "test",
 | 
			
		||||
						price: 12.548777,
 | 
			
		||||
					}),
 | 
			
		||||
				).instance;
 | 
			
		||||
				expect(
 | 
			
		||||
					s.property
 | 
			
		||||
						.model(testModel)
 | 
			
		||||
						.type.hasChanged(testModelInstance, testModelInstance),
 | 
			
		||||
				).toBeFalsy();
 | 
			
		||||
			}
 | 
			
		||||
			{
 | 
			
		||||
				const testModelInstance = testModel.model(
 | 
			
		||||
					Object.assign(new TestModel(), {
 | 
			
		||||
						id: 1,
 | 
			
		||||
						name: "test",
 | 
			
		||||
						price: 12.548777,
 | 
			
		||||
					}),
 | 
			
		||||
				).instance;
 | 
			
		||||
				testModelInstance.price = 12.548778;
 | 
			
		||||
				expect(
 | 
			
		||||
					s.property
 | 
			
		||||
						.model(testModel)
 | 
			
		||||
						.type.hasChanged(testModelInstance, testModelInstance),
 | 
			
		||||
				).toBeTruthy();
 | 
			
		||||
			}
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(
 | 
			
		||||
					null,
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(
 | 
			
		||||
					undefined,
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					null,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					undefined,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.hasChanged(5 as any, 5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.hasChanged(
 | 
			
		||||
						testModel.model(new TestModel()).instance,
 | 
			
		||||
						[] as any,
 | 
			
		||||
					),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.hasChanged(
 | 
			
		||||
						testModel.model(new TestModel()).instance,
 | 
			
		||||
						new (class {})() as any,
 | 
			
		||||
					),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						{id: 1, name: "test", price: "12.548777"},
 | 
			
		||||
						{id: 1, price: "12.548777", name: "test"},
 | 
			
		||||
					),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						{id: 1, name: "test", price: "12.548777"},
 | 
			
		||||
						{id: 1, name: "test", price: "12.548778"},
 | 
			
		||||
					),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializedHasChanged(null, {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: "12.548777",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.serializedHasChanged(undefined, {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: "12.548777",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						{id: 1, name: "test", price: "12.548777"},
 | 
			
		||||
						null,
 | 
			
		||||
					),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(
 | 
			
		||||
						{id: 1, name: "test", price: "12.548777"},
 | 
			
		||||
						undefined,
 | 
			
		||||
					),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged(5 as any, 5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged({} as any, [] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.serializedHasChanged({} as any, new (class {})() as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			const testModelInstance = testModel.model(
 | 
			
		||||
				Object.assign(new TestModel(), {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).instance;
 | 
			
		||||
 | 
			
		||||
			testModelInstance.price = 555.555;
 | 
			
		||||
			expect(testModel.model(testModelInstance).serializeDiff()).toEqual({
 | 
			
		||||
				id: 1,
 | 
			
		||||
				price: "555.555",
 | 
			
		||||
			});
 | 
			
		||||
			s.property.model(testModel).type.resetDiff(testModelInstance);
 | 
			
		||||
			expect(testModel.model(testModelInstance).serializeDiff()).toEqual({
 | 
			
		||||
				id: 1,
 | 
			
		||||
			});
 | 
			
		||||
 | 
			
		||||
			s.property.model(testModel).type.resetDiff(undefined);
 | 
			
		||||
			s.property.model(testModel).type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.resetDiff(5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.resetDiff([] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.resetDiff(new (class {})() as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("clone", () => {
 | 
			
		||||
			// Test that values are cloned in a different model instance.
 | 
			
		||||
			const testModelInstance = testModel.model(
 | 
			
		||||
				Object.assign(new TestModel(), {
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).instance;
 | 
			
		||||
			testModelInstance.price = 555.555;
 | 
			
		||||
			const clonedModelInstance = s.property
 | 
			
		||||
				.model(testModel)
 | 
			
		||||
				.type.clone(testModelInstance);
 | 
			
		||||
			expect(clonedModelInstance).not.toBe(testModelInstance);
 | 
			
		||||
			expect(
 | 
			
		||||
				testModel.model(clonedModelInstance).getInstanceProperties(),
 | 
			
		||||
			).toEqual(testModel.model(testModelInstance).getInstanceProperties());
 | 
			
		||||
			expect(testModel.model(clonedModelInstance).serializeDiff()).toEqual(
 | 
			
		||||
				testModel.model(testModelInstance).serializeDiff(),
 | 
			
		||||
			);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.model(testModel).type.clone(undefined)).toBe(undefined);
 | 
			
		||||
			expect(s.property.model(testModel).type.clone(null)).toBe(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.clone(5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.clone([] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.clone(new (class {})() as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch with undefined / NULL values.
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.applyPatch(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					undefined,
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeUndefined();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.model(testModel).type.applyPatch(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					null,
 | 
			
		||||
					true,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeNull();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Invalid patch.
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.model(testModel).type.applyPatch(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					5416 as any,
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toThrow(InvalidTypeValueError);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch with originals update.
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property.model(testModel).type.applyPatch(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					{id: 1, name: "another"},
 | 
			
		||||
					true,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "another",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.applyPatch(undefined, {id: 1, name: "test"}, true);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: undefined,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.applyPatch(null, {id: 1, name: "test"}, true);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: undefined,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch without originals update.
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property.model(testModel).type.applyPatch(
 | 
			
		||||
					testModel.model(
 | 
			
		||||
						Object.assign(new TestModel(), {
 | 
			
		||||
							id: 1,
 | 
			
		||||
							name: "test",
 | 
			
		||||
							price: 12.548777,
 | 
			
		||||
						}),
 | 
			
		||||
					).instance,
 | 
			
		||||
					{id: 1, name: "another"},
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "another",
 | 
			
		||||
					price: 12.548777,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "another",
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.applyPatch(undefined, {id: 1, name: "test"}, false);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: undefined,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const modelInstance = s.property
 | 
			
		||||
					.model(testModel)
 | 
			
		||||
					.type.applyPatch(null, {id: 1, name: "test"}, false);
 | 
			
		||||
 | 
			
		||||
				expect(
 | 
			
		||||
					testModel.model(modelInstance).getInstanceProperties(),
 | 
			
		||||
				).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
					price: undefined,
 | 
			
		||||
				});
 | 
			
		||||
				expect(testModel.model(modelInstance).serializeDiff()).toStrictEqual({
 | 
			
		||||
					id: 1,
 | 
			
		||||
					name: "test",
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,164 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {InvalidTypeValueError, NumericType, s} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("numeric type", () => {
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const numericType = s.property.numeric();
 | 
			
		||||
		expect(numericType.type).toBeInstanceOf(NumericType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(s.property.numeric().type.serialize(5.257)).toBe(5.257);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.numeric().type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.numeric().type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => s.property.numeric().type.serialize({} as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(s.property.numeric().type.deserialize(5.257)).toBe(5.257);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.numeric().type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.numeric().type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.numeric().type.deserialize({} as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(s.property.numeric().type.serializeDiff(542)).toBe(542);
 | 
			
		||||
 | 
			
		||||
			expect(s.property.numeric().type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(s.property.numeric().type.serializeDiff(undefined)).toBe(
 | 
			
		||||
				undefined,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.numeric().type.serializeDiff({} as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(s.property.numeric().type.hasChanged(5.257, 5.257)).toBeFalsy();
 | 
			
		||||
			expect(s.property.numeric().type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.numeric().type.hasChanged(null, 5.257)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(undefined, 5.257),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.numeric().type.hasChanged(5.257, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(5.257, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(5.257, 5.257),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(null, 5.257),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(undefined, 5.257),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(5.257, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged(5.257, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.numeric().type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.numeric()
 | 
			
		||||
					.type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			s.property.numeric().type.resetDiff(5.257);
 | 
			
		||||
			s.property.numeric().type.resetDiff(undefined);
 | 
			
		||||
			s.property.numeric().type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				s.property.numeric().type.resetDiff({} as any),
 | 
			
		||||
			).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.numeric().type.clone({} as any)).toStrictEqual({});
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		expect(s.property.numeric().type.applyPatch(1, 5.257, false)).toBe(5.257);
 | 
			
		||||
		expect(s.property.numeric().type.applyPatch(null, 5.257, true)).toBe(5.257);
 | 
			
		||||
		expect(s.property.numeric().type.applyPatch(undefined, 5.257, false)).toBe(
 | 
			
		||||
			5.257,
 | 
			
		||||
		);
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.numeric().type.applyPatch(5.257, undefined, false),
 | 
			
		||||
		).toBeUndefined();
 | 
			
		||||
		expect(s.property.numeric().type.applyPatch(5.257, null, false)).toBeNull();
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,323 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {
 | 
			
		||||
	InvalidTypeValueError,
 | 
			
		||||
	NumericType,
 | 
			
		||||
	ObjectType,
 | 
			
		||||
	s,
 | 
			
		||||
	StringType,
 | 
			
		||||
} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("object type", () => {
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const objectType = s.property.object({
 | 
			
		||||
			test: s.property.string(),
 | 
			
		||||
			another: s.property.numeric(),
 | 
			
		||||
		});
 | 
			
		||||
		expect(objectType.type).toBeInstanceOf(ObjectType);
 | 
			
		||||
 | 
			
		||||
		expect((objectType.type as any).properties).toHaveLength(2);
 | 
			
		||||
		for (const property of (objectType.type as any).properties) {
 | 
			
		||||
			// Check all object properties.
 | 
			
		||||
			if (property.name == "test")
 | 
			
		||||
				expect(property.definition.type).toBeInstanceOf(StringType);
 | 
			
		||||
			else if (property.name == "another")
 | 
			
		||||
				expect(property.definition.type).toBeInstanceOf(NumericType);
 | 
			
		||||
			else expect.unreachable();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const testProperty = s.property.object({
 | 
			
		||||
		test: s.property.string(),
 | 
			
		||||
		another: s.property.decimal(),
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serialize({test: "test", another: 12.548777}),
 | 
			
		||||
			).toEqual({test: "test", another: "12.548777"});
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serialize(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.serialize(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serialize(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serialize([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.deserialize({test: "test", another: "12.548777"}),
 | 
			
		||||
			).toEqual({test: "test", another: 12.548777});
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.deserialize(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.deserialize(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.deserialize(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.deserialize([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializeDiff({test: "test", another: 12.548777}),
 | 
			
		||||
			).toEqual({test: "test", another: "12.548777"});
 | 
			
		||||
 | 
			
		||||
			expect(testProperty.type.serializeDiff(null)).toEqual(null);
 | 
			
		||||
			expect(testProperty.type.serializeDiff(undefined)).toEqual(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.serializeDiff([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					{another: 12.548777, test: "test"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					{test: "test", another: 12.548778},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, undefined)).toBeFalsy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(null, undefined)).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.hasChanged(undefined, null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(null, {test: "test", another: 12.548777}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(undefined, {
 | 
			
		||||
					test: "test",
 | 
			
		||||
					another: 12.548777,
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged({test: "test", another: 12.548777}, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.hasChanged(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					undefined,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.hasChanged(5 as any, 5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.hasChanged({} as any, [] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "test", another: "12.548777"},
 | 
			
		||||
					{another: "12.548777", test: "test"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "test", another: "12.548777"},
 | 
			
		||||
					{test: "test", another: "12.548778"},
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(testProperty.type.serializedHasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(null, {
 | 
			
		||||
					test: "test",
 | 
			
		||||
					another: "12.548777",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(undefined, {
 | 
			
		||||
					test: "test",
 | 
			
		||||
					another: "12.548777",
 | 
			
		||||
				}),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "test", another: "12.548777"},
 | 
			
		||||
					null,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.serializedHasChanged(
 | 
			
		||||
					{test: "test", another: "12.548777"},
 | 
			
		||||
					undefined,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.serializedHasChanged(5 as any, 5 as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.serializedHasChanged({} as any, [] as any),
 | 
			
		||||
			).toThrowError(InvalidTypeValueError);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			testProperty.type.resetDiff({test: "test", another: 12.548777});
 | 
			
		||||
			testProperty.type.resetDiff(undefined);
 | 
			
		||||
			testProperty.type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.resetDiff(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.resetDiff([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("clone", () => {
 | 
			
		||||
		test("clone", () => {
 | 
			
		||||
			{
 | 
			
		||||
				// Test that values are cloned in a different object.
 | 
			
		||||
				const propertyValue = {test: "test", another: 12.548777};
 | 
			
		||||
				const clonedPropertyValue = testProperty.type.clone(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).toEqual(propertyValue);
 | 
			
		||||
			}
 | 
			
		||||
			{
 | 
			
		||||
				// Test that values are cloned in a different object.
 | 
			
		||||
				const propertyValue = {arr: [12, 11]};
 | 
			
		||||
				const clonedPropertyValue = s.property
 | 
			
		||||
					.object({arr: s.property.array(s.property.numeric())})
 | 
			
		||||
					.type.clone(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).not.toBe(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue).toEqual(propertyValue);
 | 
			
		||||
				expect(clonedPropertyValue.arr).not.toBe(propertyValue.arr);
 | 
			
		||||
				expect(clonedPropertyValue.arr).toEqual(propertyValue.arr);
 | 
			
		||||
			}
 | 
			
		||||
			expect(testProperty.type.clone(undefined)).toBe(undefined);
 | 
			
		||||
			expect(testProperty.type.clone(null)).toBe(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => testProperty.type.clone(5 as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
			expect(() => testProperty.type.clone([] as any)).toThrowError(
 | 
			
		||||
				InvalidTypeValueError,
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch with undefined / NULL values.
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					undefined,
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeUndefined();
 | 
			
		||||
			expect(
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					null,
 | 
			
		||||
					true,
 | 
			
		||||
				),
 | 
			
		||||
			).toBeNull();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Invalid patch.
 | 
			
		||||
			expect(() =>
 | 
			
		||||
				testProperty.type.applyPatch(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					5416 as any,
 | 
			
		||||
					false,
 | 
			
		||||
				),
 | 
			
		||||
			).toThrow(InvalidTypeValueError);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			// Apply a patch.
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					{test: "test", another: 12.548777},
 | 
			
		||||
					{test: "another"},
 | 
			
		||||
					true,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				expect(objectInstance).toStrictEqual({
 | 
			
		||||
					test: "another",
 | 
			
		||||
					another: 12.548777,
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					undefined,
 | 
			
		||||
					{test: "test"},
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				expect(objectInstance).toStrictEqual({
 | 
			
		||||
					test: "test",
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			{
 | 
			
		||||
				const objectInstance = testProperty.type.applyPatch(
 | 
			
		||||
					null,
 | 
			
		||||
					{test: "test"},
 | 
			
		||||
					false,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
				expect(objectInstance).toStrictEqual({
 | 
			
		||||
					test: "test",
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -1,162 +0,0 @@
 | 
			
		|||
import {describe, expect, test} from "vitest";
 | 
			
		||||
import {s, StringType} from "../../../src/library";
 | 
			
		||||
 | 
			
		||||
describe("string type", () => {
 | 
			
		||||
	test("definition", () => {
 | 
			
		||||
		const stringType = s.property.string();
 | 
			
		||||
		expect(stringType.type).toBeInstanceOf(StringType);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serialize", () => {
 | 
			
		||||
		test("serialize", () => {
 | 
			
		||||
			expect(s.property.string().type.serialize("test")).toBe("test");
 | 
			
		||||
			expect(s.property.string().type.serialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.string().type.serialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			const testDate = new Date();
 | 
			
		||||
			expect(s.property.string().type.serialize({} as any)).toBe(
 | 
			
		||||
				"[object Object]",
 | 
			
		||||
			);
 | 
			
		||||
			expect(s.property.string().type.serialize(2120 as any)).toBe("2120");
 | 
			
		||||
			expect(s.property.string().type.serialize(testDate as any)).toBe(
 | 
			
		||||
				testDate.toString(),
 | 
			
		||||
			);
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("deserialize", () => {
 | 
			
		||||
		test("deserialize", () => {
 | 
			
		||||
			expect(s.property.string().type.deserialize("test")).toBe("test");
 | 
			
		||||
			expect(s.property.string().type.deserialize(null)).toBe(null);
 | 
			
		||||
			expect(s.property.string().type.deserialize(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.string().type.deserialize({} as any)).toBe(
 | 
			
		||||
				"[object Object]",
 | 
			
		||||
			);
 | 
			
		||||
			expect(s.property.string().type.deserialize(2120 as any)).toBe("2120");
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializeDiff", () => {
 | 
			
		||||
		test("serializeDiff", () => {
 | 
			
		||||
			expect(s.property.string().type.serializeDiff("test")).toBe("test");
 | 
			
		||||
			expect(s.property.string().type.serializeDiff(null)).toBe(null);
 | 
			
		||||
			expect(s.property.string().type.serializeDiff(undefined)).toBe(undefined);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(s.property.string().type.serializeDiff({} as any)).toBe(
 | 
			
		||||
				"[object Object]",
 | 
			
		||||
			);
 | 
			
		||||
			expect(s.property.string().type.serializeDiff(2120 as any)).toBe("2120");
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("hasChanged", () => {
 | 
			
		||||
		test("hasChanged", () => {
 | 
			
		||||
			expect(s.property.string().type.hasChanged("test", "test")).toBeFalsy();
 | 
			
		||||
			expect(s.property.string().type.hasChanged(null, null)).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.hasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(s.property.string().type.hasChanged(null, undefined)).toBeTruthy();
 | 
			
		||||
			expect(s.property.string().type.hasChanged(undefined, null)).toBeTruthy();
 | 
			
		||||
			expect(s.property.string().type.hasChanged(null, "test")).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.hasChanged(undefined, "test"),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(s.property.string().type.hasChanged("test", null)).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.hasChanged("test", undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.hasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.hasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("serializedHasChanged", () => {
 | 
			
		||||
		test("serializedHasChanged", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged("test", "test"),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(null, null),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(undefined, undefined),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(null, undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(undefined, null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(null, "test"),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged(undefined, "test"),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged("test", null),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged("test", undefined),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property.string().type.serializedHasChanged({} as any, {} as any),
 | 
			
		||||
			).toBeTruthy();
 | 
			
		||||
			expect(
 | 
			
		||||
				s.property
 | 
			
		||||
					.string()
 | 
			
		||||
					.type.serializedHasChanged(false as any, false as any),
 | 
			
		||||
			).toBeFalsy();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	describe("resetDiff", () => {
 | 
			
		||||
		test("resetDiff", () => {
 | 
			
		||||
			s.property.string().type.resetDiff("test");
 | 
			
		||||
			s.property.string().type.resetDiff(undefined);
 | 
			
		||||
			s.property.string().type.resetDiff(null);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		test("invalid parameters", () => {
 | 
			
		||||
			expect(() => s.property.string().type.resetDiff({} as any)).not.toThrow();
 | 
			
		||||
		});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("clone", () => {
 | 
			
		||||
		expect(s.property.string().type.clone({} as any)).toStrictEqual({});
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	test("applyPatch", () => {
 | 
			
		||||
		expect(s.property.string().type.applyPatch("another", "test", false)).toBe(
 | 
			
		||||
			"test",
 | 
			
		||||
		);
 | 
			
		||||
		expect(s.property.string().type.applyPatch(undefined, "test", true)).toBe(
 | 
			
		||||
			"test",
 | 
			
		||||
		);
 | 
			
		||||
		expect(s.property.string().type.applyPatch(null, "test", false)).toBe(
 | 
			
		||||
			"test",
 | 
			
		||||
		);
 | 
			
		||||
		expect(
 | 
			
		||||
			s.property.string().type.applyPatch("test", undefined, false),
 | 
			
		||||
		).toBeUndefined();
 | 
			
		||||
		expect(s.property.string().type.applyPatch("test", null, false)).toBeNull();
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
  "ts-node": {
 | 
			
		||||
    "compilerOptions": {
 | 
			
		||||
      "module": "ESNext",
 | 
			
		||||
			"types": ["node"]
 | 
			
		||||
	    "types": ["node"],
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,6 @@
 | 
			
		|||
	  "incremental": true,
 | 
			
		||||
	  "sourceMap": true,
 | 
			
		||||
    "noImplicitAny": true,
 | 
			
		||||
		"noImplicitThis": true,
 | 
			
		||||
	  "esModuleInterop": true,
 | 
			
		||||
	  "allowSyntheticDefaultImports": true,
 | 
			
		||||
	  "resolveJsonModule": true,
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +21,9 @@
 | 
			
		|||
    "module": "ES6",
 | 
			
		||||
	  "target": "ES6",
 | 
			
		||||
    "moduleResolution": "Bundler",
 | 
			
		||||
		"lib": ["ESNext", "DOM"]
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ESNext",
 | 
			
		||||
      "DOM"
 | 
			
		||||
    ],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,19 +1,22 @@
 | 
			
		|||
import {defineConfig, UserConfig} from "vite";
 | 
			
		||||
import {ConfigEnv, defineConfig, UserConfig} from "vite";
 | 
			
		||||
import dts from "vite-plugin-dts";
 | 
			
		||||
 | 
			
		||||
// https://vitejs.dev/config/
 | 
			
		||||
 | 
			
		||||
export default defineConfig((): UserConfig => {
 | 
			
		||||
	return {
 | 
			
		||||
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
 | 
			
		||||
	return ({
 | 
			
		||||
		build: {
 | 
			
		||||
			outDir: "lib",
 | 
			
		||||
			sourcemap: true,
 | 
			
		||||
			minify: "esbuild",
 | 
			
		||||
			lib: {
 | 
			
		||||
				entry: "src/library.ts",
 | 
			
		||||
				entry: "src/index.ts",
 | 
			
		||||
				formats: ["es"],
 | 
			
		||||
				fileName: "index",
 | 
			
		||||
			},
 | 
			
		||||
			rollupOptions: {
 | 
			
		||||
				external: ["reflect-metadata"],
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		plugins: [
 | 
			
		||||
| 
						 | 
				
			
			@ -22,6 +25,6 @@ export default defineConfig((): UserConfig => {
 | 
			
		|||
				rollupTypes: true,
 | 
			
		||||
				exclude: ["node_modules"],
 | 
			
		||||
			}),
 | 
			
		||||
		],
 | 
			
		||||
	};
 | 
			
		||||
		]
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue