Routes inheritance
#
Inheritance logicaom
supports the mechanics of data inheritance, which allows you to create generic route nodes,
the logic of which extends to common cases.
Inheritance is done in a standard JavaScript way: using the extends
operator.
When inheriting for a child class, it is necessary to use the @Controller
class decorator,
since it is in it that the actions for the correct transfer of routes, endpoints, middleware
and used arguments are performed.
If the child class has its own static methods that overload the values of the parent class, then
they will remain unchanged. Similarly, if endpoints
or bridge
constructions with addresses
and methods similar to the parent ones exist in the child class, then they will not be affected
by inheritance. In that case, when assembling the route map, notifications will be shown about which
elements were skipped during their inheritance.
Inheritance also carries information from the decorators of the OpenApi documentation. An important
point in this case will be the use of the context of the data schemas of the child class. To access
this data, use the ThisRef
function, which returns the constructor
value of the current
cursor as an argument to the passed function. More on this in the relevant section.
Example
// CatalogsBase.ts// initiate a common parent class
// using `ThisRef` define filters that will be applied to search queriesconst CatalogsQuery = ThisRef(<T extends typeof CatalogsBase>({ model }: T) => $SafeQuery(model));// using `ThisRef` define filters that will be applied to incoming dataconst CatalogsBody = ThisRef(<T extends typeof CatalogsBase>({ model }: T) => $SafeBody(model));// using `ThisRef` define the data schema that will be used in the OpenApi documentationconst CatalogsSchema = ThisRef(<T extends typeof CatalogsBase>({ model }: T) => model);
// we describe the route node that the child classes will inherit@Controller()class CatalogsBase { // we indicate what type of documents will be used in the context of class instances document: models.Categories | models.Brands; // we indicate in the static property of the class the admissible data models static model: typeof models.Categories | typeof models.Brands; // parent route nodes can use parameters static id = "id";
static toString() { return `:${this.id}(.{24})`; }
// define an endpoint to get a list of data @Get() @Summary("Data list") @Responses({ status: 200, isArray: true, // use the `ThisRef` wrapper to get the context value of a specific model at the time of assembling routes schema: CatalogsSchema, }) // a previously defined function is used for filtering static Index(@Query(CatalogsQuery) query) { return this.model.find({ ...query }); }
// define an endpoint to add an element @Post() @Summary("Add value") @RequestBody({ schema: CatalogsSchema, }) @Responses({ status: 200, schema: CatalogsSchema, }) static Add(@Body(CatalogsBody) body) { return this.model.create({ ...body }); }
// define a middleware to check for the existence of a value @Middleware() @PathParameters({ [`${CatalogsBase}`]: { name: CatalogsBase.id, schema: { type: "string", pattern: "^[0-9,a-f,A-F]{24}$" }, description: "Идентификатор", }, }) @Responses({ status: 404, schema: NotFoundError, }) static async Check( @This() catalog: CatalogsBase, @Params(CatalogsBase.id) id, @Next() next, @Err() err ) { catalog.document = await this.model.findById(id); if (catalog.document) { return next(); } return err("document not found", 404); }
// define a parametric endpoint to edit the value @Patch(`${CatalogsBase}`) @Summary("Edit value") @Use(CatalogsBase.Check) @RequestBody({ schema: CatalogsSchema, }) @Responses({ status: 200, schema: CatalogsSchema, }) static async Update(@This() { document }: CatalogsBase, @Body(CatalogsBody) body) { Object.assign(document, { ...body }); await document.save(); return document; }
// define a parametric endpoint to remove the value @Delete(`${CatalogsBase}`) @Summary("Remove value") @Use(CatalogsBase.Check) @Responses({ status: 200, schema: MessageResponse, }) static async Delete(@This() { document }: CatalogsBase, @Body(CatalogsBody) body) { await document.remove(); return { message: "Документ удален", document }; }}
// create a route node `Categories` inherited from the parent class@Controller()@AddTag("Categories")@Use(Categories.Prepare)class Categories extends CatalogsBase { static model = models.Categories;
@UseTag(Categories) @Middleware() static Prepare(@Next() next) { return next(); }}
// create a route node `Categories` inherited from the parent class@Controller()@AddTag("Brands")@Use(Brands.Prepare)class Brands extends CatalogsBase { static model = models.Brands;
@UseTag(Brands) @Middleware() static Prepare(@Next() next) { return next(); }}
// use created route nodes@Bridge("/categories", Categories)@Bridge("/brands", Brands)@AddTag("Catalogs processing")@Use(Catalogs.Init)class Catalogs { //.. @UseTag(Catalogs) @MergeNextTags() @Middleware() static Init(@Next() next) { return next(); }}
As a result of this code, two route nodes Categories
and Brands
will be created, which will
inherit all endpoints
and middleware
, including the documentation decorators, providing a
valid context transfer when using the decorators @This
, as well as when using the values of
this
in static methods of the class.
Inheritance in aom
allows bridge
constructs to be applied to child route nodes as well.
However, the use of the @Bridge
decorator implies the use of strictly defined route nodes,
which will be accessed "as is" to the child classes, without performing complex cascading
inheritance procedures.
ThisRef
#
Using The ThisRef
function creates a wrapper container to access a child class that inherits the
specific decorators of the parent class.
Accepts as its only argument another function, in which the argument is placed the value of
constructor
from the current cursor, which in general is a direct inheritor of the class for
which this construct is applied.
The function can be used in the following decorators:
@Body
as a function argument, must return a validation function for incoming data@Query
as a function argument, must return a validation function for incoming data@Responses
for the valueschema
, must return the JSON schema of the document@RequestBody
for the valueschema
, must return the JSON schema of the document
If the parent class is used as a self-contained routing node, then ThisRef
will return its
own value from the parent class, and will work correctly in this situation.