Compare commits

...

7 Commits
v1.0.0 ... main

7 changed files with 226 additions and 7 deletions

140
README.md
View File

@ -1,3 +1,139 @@
# repositories
# Sharkitek Repositories
Sharkitek repositories extension.
The _Sharkitek Repositories_ extension is designed to add global repositories of models. It helps to deduplicate instances
of the same objects (with the same identifier) in an application. It could be thought as a tiny local database.
It adds a model class extension and a special model type that tries to use (and save) the models from
the corresponding repository on deserialization.
## Documentation
To link models to repositories, you can use the `WithRepository` Sharkitek extension.
```typescript
class Author extends WithRepository("Author")(Model)
{
@Property(SString)
name: string = undefined;
@Property(SString)
firstName: string = undefined;
@Property(SString)
@Identifier
email: string = undefined;
}
```
The only argument of `WithRepository` is the name of the repository to use. The easier way to use _Sharkitek Repositories_ is
to always use class name as repository name.
`WithRepository` declares 3 methods in the model, which can be used to get the repository or store the model in it.
These 3 methods are defined in the `ModelWithRepository` interface:
```typescript
/**
* Interface of a model with a repository.
*/
interface ModelWithRepository
{
/**
* Get the model repository name.
*/
getRepositoryName(): string;
/**
* Get the model repository.
*/
getRepository(): ModelRepository;
/**
* Store the model in its repository.
*/
store(): void;
}
```
When a model is linked to a repository, you can use `find` to get instances from it or retrieve it elsewhere (from an API, for example).
```typescript
const author = await find(Author, "example@example.example", async (email: string) => {
// Make an API call or something like that to retrieve the model and store it in the repository.
// ...
return model;
});
```
To help generalization of API calls for a given model, you can implement an automatic retriever of a model.
```typescript
class Author extends WithRepository("Author")(Model) implements AutoRetriever<Author>
{
async autoRetrieve(identifier: unknown): Promise<Author>
{
return (new Author()).deserialize({
// ... make an API call to retrieve serialized data, for example ...
});
}
// ... the rest of the definition ...
}
```
_Sharkitek Repositories_ also provide a special model property type, that automatically uses repositories
to store and retrieve instances. When you use `SModelFromRepository` property type, on deserialization, Sharkitek
will first try to get an instance matching the model identifier in its repository. If it is not found, then it will use
the currently serialized instance of the model as the main instance and store it in the repository.
Here is an example:
```typescript
class A extends WithRepository("A")(Model)
{
@Property(SNumeric)
@Identifier
id: number = undefined;
@Property(SString)
foo: string = undefined;
}
class B extends Model
{
@Property(SNumeric)
@Identifier
id: number = undefined;
@Property(SModelFromRepository(A))
a: A = undefined;
}
// First deserialization, that should store the A instance.
const firstB = (new B()).deserialize({
id: 5,
a: {
id: 3, // No instance with ID 3 is stored in the repository, using this instance and storing it.
foo: "first",
}
});
// To check that the first instance of "A" is used, we change the value of "foo". If a.foo == "first", then the first object has been used.
const secondB = (new B()).deserialize({
id: 7,
a: {
id: 3, // Same ID as the previous object, using the instance from repository.
foo: "second",
}
});
console.log(firstB.a.foo); // Result: "first".
console.log(secondB.a.foo); // Result: "first".
// firstB.a and secondB.a are the same object.
firstB.a.foo = "test";
console.log(secondB.a.foo); // Result: "test".
```

View File

@ -1,7 +1,8 @@
{
"name": "@sharkitek/repositories",
"version": "1.0.0",
"version": "1.1.0",
"description": "Sharkitek models repositories extension.",
"keywords": ["sharkitek", "model", "storage", "repository"],
"repository": "https://git.madeorsk.com/Sharkitek/repositories",
"author": "Madeorsk <madeorsk@protonmail.com>",
"license": "MIT",
@ -17,7 +18,7 @@
"lib/**/*"
],
"dependencies": {
"@sharkitek/core": "^1.0.1",
"@sharkitek/core": "^1.0.2",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {

View File

@ -1,4 +1,3 @@
import {ConstructorOf, Model} from "@sharkitek/core";
/**
* Auto retriever interface.

View File

@ -153,13 +153,14 @@ export class ModelRepository<T extends Model = Model>
/**
* Get the model identified by its identifier.
* @param identifier - Identifier of the model.
* @param defaultValue - Function called when there is no value matching the given identifier in the repository. Return null by default.
*/
get(identifier: string): T|null
get(identifier: string, defaultValue: (identifier: string) => T|null = () => null): T|null
{
return typeof this.models?.[identifier] !== "undefined"
// Model exists, returning it.
? this.models[identifier]
// Model does not exists, returning NULL.
: null;
: defaultValue(identifier);
}
}

View File

@ -0,0 +1,30 @@
import {ConstructorOf, Model, ModelType} from "@sharkitek/core";
import {ModelWithRepository} from "../Repositories/ModelRepository";
/**
* Type of a Sharkitek model (from repository) value.
*/
export class ModelFromRepositoryType<M extends Model&ModelWithRepository> extends ModelType<M>
{
deserialize(value: any): M
{
// Deserializing the given object in a new model.
let model = (new this.modelConstructor()).deserialize(value);
// Getting the object matching the current model identifier, if there is one, or the current model.
model = model.getRepository().get(String(model.getIdentifier()), () => model) as M;
model?.store(); // Storing the current model in the repository if it was not.
return model; // Returning the model.
}
}
/**
* Type of a Sharkitek model (from repository) value.
* @param modelConstructor - Constructor of the model.
*/
export function SModelFromRepository<M extends Model&ModelWithRepository>(modelConstructor: ConstructorOf<M>)
{
return new ModelFromRepositoryType(modelConstructor);
}

View File

@ -3,3 +3,5 @@
export * from "./Model/Repositories/ModelRepository";
export * from "./Model/Repositories/AutoRetriever";
export * from "./Model/Types/ModelFromRepositoryType";

View File

@ -0,0 +1,50 @@
import {Identifier, Model, Property, SNumeric, SString} from "@sharkitek/core";
import {WithRepository, SModelFromRepository} from "../src";
class A extends WithRepository("A")(Model)
{
@Property(SNumeric)
@Identifier
id: number = undefined;
@Property(SString)
foo: string = undefined;
}
class B extends Model
{
@Property(SNumeric)
@Identifier
id: number = undefined;
@Property(SModelFromRepository(A))
a: A = undefined;
}
it("get the same object after two deserializations", () => {
// First deserialization, that should store the A instance.
const firstB = (new B()).deserialize({
id: 5,
a: {
id: 3,
foo: "first",
}
});
// To check that the first instance of "A" is used, we change the value of "foo". If a.foo == "first", then the first object has been used.
const secondB = (new B()).deserialize({
id: 7,
a: {
id: 3,
foo: "second",
}
});
// `a` of `secondB` should be `a` of `firstB`.
expect(secondB.a).toBe(firstB.a);
// If something changes in A instance of firstB,
// A instance of secondB should have the same modification (as it should be the same instance).
firstB.a.foo = "test";
expect(secondB.a.foo).toStrictEqual("test");
});