Initial setup: first version of model repositories with auto retrievers.
This commit is contained in:
parent
f2148f8c2a
commit
ec964195fd
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
9
jest.config.js
Normal 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
34
package.json
Normal 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"
|
||||
}
|
13
src/Model/Repositories/AutoRetriever.ts
Normal file
13
src/Model/Repositories/AutoRetriever.ts
Normal 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>;
|
||||
}
|
165
src/Model/Repositories/ModelRepository.ts
Normal file
165
src/Model/Repositories/ModelRepository.ts
Normal 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
5
src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
|
||||
export * from "./Model/Repositories/ModelRepository";
|
||||
export * from "./Model/Repositories/AutoRetriever";
|
||||
|
113
tests/ModelRepository.test.ts
Normal file
113
tests/ModelRepository.test.ts
Normal 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
21
tsconfig.json
Normal 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"
|
||||
]
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user