@adonisjs/route-model-binding
Version:
Route model binding for AdonisJS
224 lines (218 loc) • 6.59 kB
JavaScript
import {
__name,
resolveRouteHandler
} from "./chunk-FAJSNHSW.js";
// src/resource_loader.ts
import string from "@poppinss/utils/string";
// src/params_parser.ts
var ParamsParser = class {
static {
__name(this, "ParamsParser");
}
#params;
#routePattern;
constructor(params, routePattern) {
this.#params = params;
this.#routePattern = routePattern;
}
/**
* A param can be one of the following
*
* post
* post(slug)
* >comment
* >comment(slug)
*/
#parseParam(param) {
let scoped = false;
let [name, lookupKey] = param.split("(");
if (lookupKey) {
lookupKey = lookupKey.slice(0, -1);
} else {
lookupKey = "$primaryKey";
}
if (name.startsWith(">")) {
scoped = true;
name = name.substring(1);
}
return {
scoped,
name,
param,
lookupKey,
parent: null
};
}
/**
* Loop through the params and setup the parents
*/
#computeParamParents(params) {
params.forEach((param, index) => {
if (param.scoped) {
if (index === 0) {
throw new Error(`The first parameter in route "${this.#routePattern}" cannot be scoped`);
}
param.parent = params[index - 1].name;
}
});
return params;
}
/**
* Parse route params for a given request
*/
parse() {
return this.#computeParamParents(this.#params.map((param) => this.#parseParam(param)));
}
};
// src/exceptions/missing_relationship.ts
import { Exception } from "@poppinss/utils";
var MissingRelationshipException = class extends Exception {
static {
__name(this, "MissingRelationshipException");
}
static invoke(paramName, route, parentModel) {
const errorMessage = `Cannot load "${paramName}" for route "${route}". Make sure to define it as a relationship on model "${parentModel}"`;
return new this(errorMessage, {
code: "E_MISSING_RELATIONSHIP",
status: 500
});
}
};
// src/resource_loader.ts
var ResourceLoader = class {
static {
__name(this, "ResourceLoader");
}
ctx;
#router;
resources;
constructor(ctx, router) {
this.ctx = ctx;
this.resources = {};
this.#router = router;
}
/**
* Returns true when value is a resource model
*/
#isResourceModel(value) {
if (!value) {
return false;
}
return typeof value["findForRequest"] === "function" || typeof value["findOrFail"] === "function" || typeof value["findRelatedForRequest"] === "function";
}
/**
* Returns the relationship name for a scoped resource
*/
#getRelationshipName(param, parentModel) {
let relationshipName = string.singular(string.camelCase(param.name));
if (parentModel.$hasRelation(relationshipName)) {
return relationshipName;
}
relationshipName = string.pluralize(relationshipName);
if (parentModel.$hasRelation(relationshipName)) {
return relationshipName;
}
throw MissingRelationshipException.invoke(param.name, this.ctx.route.pattern, parentModel.name);
}
/**
* Instantiate scoped model. The parent model instance is passed as
* the first argument.
*/
async #instantiateScopedModel(model, param, value) {
const parentModel = this.resources[param.parent];
const parentModelConstructor = parentModel.constructor;
if (typeof parentModel["findRelatedForRequest"] === "function") {
return parentModel["findRelatedForRequest"](this.ctx, param, value);
}
const relatedQuery = parentModel.related(this.#getRelationshipName(param, parentModelConstructor)).query();
if (param.lookupKey !== "$primaryKey") {
return relatedQuery.where(param.lookupKey, value).firstOrFail();
}
if (model.routeLookupKey) {
return relatedQuery.where(model.routeLookupKey, value).firstOrFail();
}
return relatedQuery.where(model.primaryKey, value).firstOrFail();
}
/**
* Instantiate model
*/
async #instantiateModel(model, param, value) {
if (typeof model.findForRequest === "function") {
return model.findForRequest(this.ctx, param, value);
}
if (param.lookupKey !== "$primaryKey") {
return model.findByOrFail(param.lookupKey, value);
}
if (model.routeLookupKey) {
return model.findByOrFail(model.routeLookupKey, value);
}
return model.findOrFail(value);
}
/**
* Rewrite ctx.params to use normalized param names
*/
#rewriteParams(params) {
params.forEach((param) => {
if (param.name !== param.param) {
this.ctx.params[param.name] = this.ctx.params[param.param];
delete this.ctx.params[param.param];
}
});
}
/**
* Load models based upon the current request route params
*/
async load(models) {
let index = 0;
if (!this.ctx.route.meta.resolvedParams) {
this.ctx.route.meta.resolvedParams = new ParamsParser(this.#router.match(this.ctx.request.url(), this.ctx.request.method()).route.meta.params, this.ctx.route.pattern).parse();
}
const routeParams = this.ctx.route.meta.resolvedParams;
for (let param of routeParams) {
const model = models[index];
const value = this.ctx.request.param(param.param);
if (value !== void 0 && value !== null) {
if (this.#isResourceModel(model)) {
this.resources[param.name] = param.scoped ? await this.#instantiateScopedModel(model, param, value) : await this.#instantiateModel(model, param, value);
} else {
this.resources[param.name] = value;
}
}
index++;
}
this.#rewriteParams(routeParams);
}
};
// src/rmb_middleware.ts
var RouteModelBindingMiddleware = class {
static {
__name(this, "RouteModelBindingMiddleware");
}
#app;
constructor(application) {
this.#app = application;
}
async handle(ctx, next) {
ctx.resources = {};
const route = ctx.route;
if (!route) {
return next();
}
const handler = route.handler;
if (!handler || typeof handler === "function") {
return next();
}
const { controllerConstructor, method } = await resolveRouteHandler(handler.reference, this.#app);
if (!controllerConstructor || !controllerConstructor.bindings || controllerConstructor.bindings && !controllerConstructor.bindings[method]) {
return next();
}
const resourceLoader = new ResourceLoader(ctx, await this.#app.container.make("router"));
await resourceLoader.load(controllerConstructor["bindings"][method]);
ctx.resources = resourceLoader.resources;
await next();
}
};
export {
RouteModelBindingMiddleware
};
//# sourceMappingURL=chunk-LNBQB6LP.js.map