Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Madeorsk | 3acc564dee | |
Madeorsk | ca58569011 | |
Madeorsk | 6f7b2fa81d | |
Madeorsk | cb40f95a52 | |
Madeorsk | 15de42f7ca | |
Madeorsk | 44ba66b39f | |
Madeorsk | e7050a2fc0 |
140
README.md
140
README.md
|
@ -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".
|
||||||
|
```
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "@sharkitek/repositories",
|
"name": "@sharkitek/repositories",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "Sharkitek models repositories extension.",
|
"description": "Sharkitek models repositories extension.",
|
||||||
|
"keywords": ["sharkitek", "model", "storage", "repository"],
|
||||||
"repository": "https://git.madeorsk.com/Sharkitek/repositories",
|
"repository": "https://git.madeorsk.com/Sharkitek/repositories",
|
||||||
"author": "Madeorsk <madeorsk@protonmail.com>",
|
"author": "Madeorsk <madeorsk@protonmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sharkitek/core": "^1.0.1",
|
"@sharkitek/core": "^1.0.2",
|
||||||
"reflect-metadata": "^0.1.13"
|
"reflect-metadata": "^0.1.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import {ConstructorOf, Model} from "@sharkitek/core";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Auto retriever interface.
|
* Auto retriever interface.
|
||||||
|
|
|
@ -153,13 +153,14 @@ export class ModelRepository<T extends Model = Model>
|
||||||
/**
|
/**
|
||||||
* Get the model identified by its identifier.
|
* Get the model identified by its identifier.
|
||||||
* @param identifier - Identifier of the model.
|
* @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"
|
return typeof this.models?.[identifier] !== "undefined"
|
||||||
// Model exists, returning it.
|
// Model exists, returning it.
|
||||||
? this.models[identifier]
|
? this.models[identifier]
|
||||||
// Model does not exists, returning NULL.
|
// Model does not exists, returning NULL.
|
||||||
: null;
|
: defaultValue(identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
|
@ -3,3 +3,5 @@
|
||||||
export * from "./Model/Repositories/ModelRepository";
|
export * from "./Model/Repositories/ModelRepository";
|
||||||
export * from "./Model/Repositories/AutoRetriever";
|
export * from "./Model/Repositories/AutoRetriever";
|
||||||
|
|
||||||
|
export * from "./Model/Types/ModelFromRepositoryType";
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
});
|
Loading…
Reference in New Issue