diff --git a/README.md b/README.md index 1a482e5..0ec7a64 100644 --- a/README.md +++ b/README.md @@ -19,42 +19,64 @@

- Version 3.3.0 + + Latest release + + Tests status

## Introduction -Sharkitek is a Javascript / TypeScript library designed to ease development of client-side models. +Sharkitek is a lightweight Javascript / TypeScript library designed to ease development of models. With Sharkitek, you define the architecture of your models by specifying their properties and their types. -Then, you can use the defined methods like `serialize`, `deserialize`, `patch` or `serializeDiff`. +Then, you can use the defined methods like `serialize`, `parse`, `patch` or `serializeDiff`. ```typescript -class Example extends s.model({ - id: s.property.numeric(), - name: s.property.string(), -}) +class Example { + static model = defineModel({ + Class: Example, + properties: { + id: s.property.numeric(), + name: s.property.string(), + }, + identifier: "id", + }); + + id: number; + name: string; } ``` -## Examples +## Quick start -### Simple model definition +**Note**: by convention, we define our models in a `model` static variable in the model's class. It is a good way to keep your model declaration near the actual class, and its usage will be more natural. + +### Model definition ```typescript /** * A person. */ -class Person extends s.model({ - id: s.property.numeric(), - name: s.property.string(), - firstName: s.property.string(), - email: s.property.string(), - createdAt: s.property.date(), - active: s.property.boolean(), -}, "id") +class Person { + static model = defineModel({ + Class: Person, + properties: { + id: s.property.numeric(), + name: s.property.string(), + email: s.property.string(), + createdAt: s.property.date(), + active: s.property.boolean(), + }, + identifier: "id", + }); + + id: number; + name: string; + email: string; + createdAt: Date; active: boolean = true; } ``` @@ -63,22 +85,28 @@ class Person extends s.model({ /** * An 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") +class Article { + static model = defineModel({ + Class: Article, + properties: { + id: s.property.numeric(), + title: s.property.string(), + authors: s.property.array(s.property.model(Person)), + text: s.property.string(), + evaluation: s.property.decimal(), + tags: s.property.array( + s.property.object({ + name: s.property.string(), + }) + ), + }, + identifier: "id", + }); + id: number; title: string; - authors: Author[] = []; + authors: Person[] = []; text: string; evaluation: number; tags: { @@ -87,6 +115,84 @@ class Article extends s.model({ } ``` +```typescript +/** + * A model with composite keys. + */ +class CompositeKeys +{ + static model = defineModel({ + Class: CompositeKeys, + properties: { + id1: s.property.numeric(), + id2: s.property.string(), + }, + identifier: ["id1", "id2"], + }); + + id1: number; + id2: string; +} +``` + +### 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 = Person.model.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 = Person.model.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 = Person.model.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(Person.model.model(instance).patch()); // { id: 1, name: "Johnny" } +// If you run it one more time, already patched properties will not be included again. +console.log(Person.model.model(instance).patch()); // { id: 1 } +``` + +#### Identifier + +```typescript +const instance = new CompositeKeys(); +instance.id1 = 5; +instance.id2 = "foo"; +const instanceIdentifier = CompositeKeys.model.model(instance).getIdentifier(); +console.log(instanceIdentifier); // [5, "foo"] +``` + ## API ### Types @@ -95,7 +201,7 @@ Types are defined by a class extending `Type`. Sharkitek defines some basic types by default, in these classes: -- `BoolType`: boolean value in the model, boolean value in the serialized object. +- `BooleanType`: 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. @@ -107,18 +213,22 @@ Sharkitek defines some basic types by default, in these classes: When you are defining a property of a Sharkitek model, you must provide its type by instantiating one of these classes. ```typescript -class Example extends s.model({ - foo: s.property.define(new StringType()), -}) +class Example { + static model = defineModel({ + Class: Example, + properties: { + foo: s.property.define(new StringType()), + }, + }); + foo: string; } ``` -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. -- `BoolType` => `s.property.boolean` +- `BooleanType` => `s.property.boolean` - `StringType` => `s.property.string` - `NumericType` => `s.property.numeric` - `DecimalType` => `s.property.decimal` @@ -127,21 +237,32 @@ properties of each type are easily definable with a function for each type. - `ObjectType` => `s.property.object` - `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 extends s.model({ - foo: s.property.string(), -}) +class Example { + static model = defineModel({ + Class: Example, + properties: { + foo: s.property.string(), + }, + }); + foo: 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); +``` + #### `serialize()` Serialize the model. @@ -149,17 +270,17 @@ Serialize the model. Example: ```typescript -const serializedObject = model.serialize(); +const serializedObject = definedModel.model(modelInstance).serialize(); ``` -#### `deserialize(serializedObject)` +#### `parse(serializedObject)` Deserialize the model. Example: ```typescript -const model = (new TestModel()).deserialize({ +const modelInstance = definedModel.parse({ id: 5, title: "Hello World!", users: [ @@ -178,7 +299,7 @@ Serialize the difference between current model state and original one. Example: ```typescript -const model = (new TestModel()).deserialize({ +const modelInstance = definedModel.parse({ id: 5, title: "Hello World!", users: [ @@ -189,9 +310,9 @@ const model = (new TestModel()).deserialize({ ], }); -model.title = "A new title for a new world"; +modelInstance.title = "A new title for a new world"; -const result = model.serializeDiff(); +const result = definedModel.model(modelInstance).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: @@ -205,7 +326,7 @@ Set current properties values as original values. Example: ```typescript -const model = (new TestModel()).deserialize({ +const modelInstance = definedModel.parse({ id: 5, title: "Hello World!", users: [ @@ -216,11 +337,11 @@ const model = (new TestModel()).deserialize({ ], }); -model.title = "A new title for a new world"; +modelInstance.title = "A new title for a new world"; -model.resetDiff(); +definedModel.model(modelInstance).resetDiff(); -const result = model.serializeDiff(); +const result = definedModel.model(modelInstance).serializeDiff(); // if `id` is defined as the model identifier: // result = { id: 5 } // if `id` is not defined as the model identifier: @@ -233,7 +354,7 @@ Get difference between original values and current ones, then reset it. Similar to call `serializeDiff()` then `resetDiff()`. ```typescript -const model = (new TestModel()).deserialize({ +const modelInstance = definedModel.parse({ id: 5, title: "Hello World!", users: [ @@ -244,9 +365,9 @@ const model = (new TestModel()).deserialize({ ], }); -model.title = "A new title for a new world"; +modelInstance.title = "A new title for a new world"; -const result = model.patch(); +const result = definedModel.model(modelInstance).patch(); // 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: