Initial setup: first version of model repositories with auto retrievers.

This commit is contained in:
Madeorsk 2022-08-03 14:23:02 +02:00
parent f2148f8c2a
commit ec964195fd
8 changed files with 376 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# IDEA
.idea/
*.iml
# JS library
coverage/
lib/
.parcel-cache/
.yarn/
.yarnrc*
yarn-error.log
.pnp*
yarn.lock

9
jest.config.js Normal file
View File

@ -0,0 +1,9 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
roots: [
"./tests",
],
};

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "@sharkitek/repositories",
"version": "1.0.0",
"description": "Sharkitek models repositories extension.",
"repository": "https://git.madeorsk.com/Sharkitek/repositories",
"author": "Madeorsk <madeorsk@protonmail.com>",
"license": "MIT",
"scripts": {
"build": "parcel build",
"dev": "parcel watch",
"test": "jest"
},
"main": "lib/index.js",
"source": "src/index.ts",
"types": "lib/index.d.ts",
"files": [
"lib/**/*"
],
"dependencies": {
"@sharkitek/core": "^1.0.1",
"reflect-metadata": "^0.1.13"
},
"devDependencies": {
"@parcel/packager-ts": "2.6.2",
"@parcel/transformer-typescript-types": "2.6.2",
"@types/jest": "^28.1.6",
"jest": "^28.1.3",
"parcel": "^2.6.2",
"process": "^0.11.10",
"ts-jest": "^28.0.7",
"typescript": "^4.7.4"
},
"packageManager": "yarn@3.2.2"
}

View File

@ -0,0 +1,13 @@
import {ConstructorOf, Model} from "@sharkitek/core";
/**
* Auto retriever interface.
*/
export interface AutoRetriever<T>
{
/**
* Auto retrieve a model for the given identifier.
* @param identifier - Identifier for which to retrieve the model.
*/
autoRetrieve(identifier: unknown): Promise<T>;
}

View File

@ -0,0 +1,165 @@
import "reflect-metadata";
import {ConstructorOf, Model} from "@sharkitek/core";
import {AutoRetriever} from "./AutoRetriever";
/**
* Interface of a model with a repository.
*/
export interface ModelWithRepository
{
/**
* Get the model repository name.
*/
getRepositoryName(): string;
/**
* Get the model repository.
*/
getRepository(): ModelRepository;
/**
* Store the model in its repository.
*/
store(): void;
}
/**
* Find a model in the corresponding repository or try to retrieve it.
* @param modelClass - Class of the model to find.
* @param identifier - Identifier of the object to find.
* @param retriever - Function called when a model with the given identifier could not be found.
*/
export async function find<T extends Model&ModelWithRepository, IdentifierType = unknown>(modelClass: ConstructorOf<T>, identifier: IdentifierType, retriever: (identifier: IdentifierType) => Promise<T|null> = async () => null): Promise<T|null>
{
// Getting model repository.
const repository = (modelClass as any).getRepository() as ModelRepository<T>;
// Trying to get the model from repository.
let model = repository.get(String(identifier));
if (!model)
{ // If there is no model with the given identifier in the repository, trying to get one and registering it if found.
model = await retriever(identifier); // Trying to retrieve the model.
if (!model && modelClass.prototype.autoRetrieve)
// If there is not model after calling the custom retriever, trying to get one with the auto retriever if there is one.
model = await (new modelClass() as unknown as AutoRetriever<T>).autoRetrieve(identifier);
// The model has been found, registering it.
if (model) repository.register(model);
}
return model; // Returning found model, if there is one.
}
/**
* Add repository capability to Sharkitek models.
* @param repositoryName - Model class to extend.
*/
export function WithRepository(repositoryName: string): (modelClass: typeof Model) => ConstructorOf<Model&ModelWithRepository>
{
return (modelClass) => (
class WithRepository extends modelClass implements ModelWithRepository {
/**
* Get the model repository name.
*/
static getRepositoryName(): string
{
return repositoryName;
}
/**
* Get the model repository.
*/
static getRepository(): ModelRepository<WithRepository>
{
return ModelsRepositories.get().getRepository(WithRepository.getRepositoryName());
}
getRepositoryName(): string
{
return WithRepository.getRepositoryName();
}
getRepository(): ModelRepository<this>
{
return WithRepository.getRepository() as ModelRepository<this>;
}
store(): void
{
WithRepository.getRepository().register(this);
}
}
);
}
/**
* Models repositories.
*/
export class ModelsRepositories
{
private static instance: ModelsRepositories;
/**
* Get the singleton instance.
*/
static get(): ModelsRepositories
{
// If there is no instance, creating one.
if (!this.instance) this.instance = new ModelsRepositories();
return this.instance; // Return the main instance.
}
protected constructor()
{}
/**
* List of all repositories.
*/
protected repositories: Record<string, ModelRepository> = {};
/**
* Get repository for the given name.
* @param repositoryName - Name of the repository to get.
*/
getRepository<T extends Model = Model>(repositoryName: string): ModelRepository<T>
{
if (!this.repositories[repositoryName])
// The repository for the given name does not exists, initializing it.
this.repositories[repositoryName] = new ModelRepository<T>();
return this.repositories[repositoryName] as ModelRepository<T>; // Return the repository.
}
}
/**
* A model repository.
*/
export class ModelRepository<T extends Model = Model>
{
models: Record<string, T> = {};
/**
* Register the given model in the repository.
* @param model - Model to register.
*/
register(model: T): void
{
this.models[String(model.getIdentifier())] = model;
}
/**
* Get the model identified by its identifier.
* @param identifier - Identifier of the model.
*/
get(identifier: string): T|null
{
return typeof this.models?.[identifier] !== "undefined"
// Model exists, returning it.
? this.models[identifier]
// Model does not exists, returning NULL.
: null;
}
}

5
src/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from "./Model/Repositories/ModelRepository";
export * from "./Model/Repositories/AutoRetriever";

View File

@ -0,0 +1,113 @@
import {Model, SString, SNumeric, SDecimal, SArray, SModel, Identifier, Property} from "@sharkitek/core";
import {find, ModelRepository, WithRepository, AutoRetriever} from "../src";
/**
* Another test model.
*/
class Author extends WithRepository("Author")(Model) implements AutoRetriever<Author>
{
async autoRetrieve(identifier: unknown): Promise<Author>
{
return (new Author()).deserialize({
name: "autoretrieved",
firstName: "autoretrieved",
email: identifier,
});
}
@Property(SString)
name: string = undefined;
@Property(SString)
firstName: string = undefined;
@Property(SString)
@Identifier
email: string = undefined;
constructor(name: string = undefined, firstName: string = undefined, email: string = undefined)
{
super();
this.name = name;
this.firstName = firstName;
this.email = email;
}
}
/**
* A test model.
*/
class Article extends Model
{
@Property(SNumeric)
@Identifier
id: number = undefined;
@Property(SString)
title: string = undefined;
@Property(SArray(SModel(Author)))
authors: Author[] = [];
@Property(SString)
text: string = undefined;
@Property(SDecimal)
evaluation: number = undefined;
}
it("find in empty repository", async () => {
const author = await find(Author, "test@test.test", async (email: string) => (
(new Author()).deserialize({
name: "TEST",
firstName: "Test",
email: email,
})
));
expect(author.serialize()).toStrictEqual({
name: "TEST",
firstName: "Test",
email: "test@test.test",
});
});
it("find in repository", async () => {
(new Author()).deserialize({
name: "TEST",
firstName: "Test",
email: "test@test.test",
}).store();
const author = await find(Author, "test@test.test");
expect(author.serialize()).toStrictEqual({
name: "TEST",
firstName: "Test",
email: "test@test.test",
});
});
it("get repository data", () => {
const author = (new Author()).deserialize({
name: "TEST",
firstName: "Test",
email: "test@test.test",
});
expect(author.getRepositoryName()).toStrictEqual("Author");
expect(author.getRepository()).toBeInstanceOf(ModelRepository);
});
it("test auto retriever", async () => {
const identifier = "autoretrieve@test.test";
expect(
(await find(Author, identifier)).serialize()
).toStrictEqual({
name: "autoretrieved",
firstName: "autoretrieved",
email: identifier,
});
});

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"files": ["src/index.ts"],
"compilerOptions": {
"outDir": "./lib/",
"noImplicitAny": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"module": "ES6",
"moduleResolution": "Node",
"target": "ES6",
"lib": [
"ESNext",
"DOM"
]
}
}