@nestjsx/crud
Advanced tools
Comparing version 3.0.0-rc.1 to 3.0.0-rc.2
import { BadRequestException, NotFoundException } from '@nestjs/common'; | ||
import { RestfulOptions, GetManyDefaultResponse } from '../interfaces'; | ||
import { GetManyDefaultResponse, RequestParamsParsed, RestfulOptions } from '../interfaces'; | ||
export declare abstract class RestfulService<T> { | ||
protected abstract options: RestfulOptions; | ||
constructor(); | ||
abstract getMany(...args: any[]): Promise<GetManyDefaultResponse<T>>; | ||
createPageInfo(data: T[], total: number, limit: number, offset: number): GetManyDefaultResponse<T>; | ||
abstract decidePagination(query: RequestParamsParsed, mergedOptions: RestfulOptions): any; | ||
abstract getMany(...args: any[]): Promise<GetManyDefaultResponse<T> | T[]>; | ||
abstract getOne(...args: any[]): Promise<T>; | ||
@@ -8,0 +10,0 @@ abstract createOne(...args: any[]): Promise<T>; |
@@ -5,3 +5,13 @@ "use strict"; | ||
class RestfulService { | ||
constructor() { } | ||
constructor() { | ||
} | ||
createPageInfo(data, total, limit, offset) { | ||
return { | ||
data, | ||
count: data.length, | ||
total, | ||
page: Math.floor(offset / limit) + 1, | ||
pageCount: limit && total ? Math.round(total / limit) : undefined, | ||
}; | ||
} | ||
throwBadRequestException(msg) { | ||
@@ -8,0 +18,0 @@ throw new common_1.BadRequestException(msg); |
@@ -14,3 +14,2 @@ "use strict"; | ||
const route_paramtypes_enum_1 = require("@nestjs/common/enums/route-paramtypes.enum"); | ||
const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); | ||
const dto_1 = require("../dto"); | ||
@@ -25,4 +24,4 @@ const enums_1 = require("../enums"); | ||
const prototype = target.prototype; | ||
prototype[name] = function getManyBase(query, options) { | ||
return this.service.getMany(query, options); | ||
prototype[name] = function getManyBase(parsedQuery, parsedOptions) { | ||
return this.service.getMany(parsedQuery, parsedOptions.options); | ||
}; | ||
@@ -34,3 +33,3 @@ helpers_1.setParams(Object.assign({}, helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_QUERY_REQUEST_KEY, 0), helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_OPTIONS_METADATA, 1)), target, name); | ||
interceptors_1.RestfulQueryInterceptor, | ||
...getRoutesInterceptors(crudOptions.routes.getManyBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.getManyBase), | ||
], prototype[name]); | ||
@@ -40,7 +39,9 @@ helpers_1.setAction(enums_1.CrudActions.ReadAll, prototype[name]); | ||
helpers_1.setSwaggerQueryGetMany(prototype[name], dto.name); | ||
helpers_1.setSwaggerOperation(prototype[name], `Retrieve many ${dto.name}`); | ||
helpers_1.setSwaggerOkResponse(prototype[name], dto, true); | ||
}, | ||
getOneBase(target, name, dto, crudOptions) { | ||
const prototype = target.prototype; | ||
prototype[name] = function getOneBase(query, options) { | ||
return this.service.getOne(query, options); | ||
prototype[name] = function getOneBase(parsedQuery, parsedOptions) { | ||
return this.service.getOne(parsedQuery, parsedOptions.options); | ||
}; | ||
@@ -52,12 +53,14 @@ helpers_1.setParams(Object.assign({}, helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_QUERY_REQUEST_KEY, 0), helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_OPTIONS_METADATA, 1)), target, name); | ||
interceptors_1.RestfulQueryInterceptor, | ||
...getRoutesInterceptors(crudOptions.routes.getOneBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.getOneBase), | ||
], prototype[name]); | ||
helpers_1.setAction(enums_1.CrudActions.ReadOne, prototype[name]); | ||
helpers_1.setSwaggerParams(prototype[name], crudOptions); | ||
helpers_1.setSwaggerQueryGetOne(prototype[name], dto.name); | ||
helpers_1.setSwaggerQueryGetOne(prototype[name]); | ||
helpers_1.setSwaggerOperation(prototype[name], `Retrieve one ${dto.name}`); | ||
helpers_1.setSwaggerOkResponse(prototype[name], dto); | ||
}, | ||
createOneBase(target, name, dto, crudOptions) { | ||
const prototype = target.prototype; | ||
prototype[name] = function createOneBase(params, body) { | ||
return this.service.createOne(body, params); | ||
prototype[name] = function createOneBase(parsedParams, body) { | ||
return this.service.createOne(body, parsedParams); | ||
}; | ||
@@ -70,11 +73,13 @@ helpers_1.setParams(Object.assign({}, helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_PARAMS_REQUEST_KEY, 0), helpers_1.createParamMetadata(route_paramtypes_enum_1.RouteParamtypes.BODY, 1, [ | ||
interceptors_1.RestfulParamsInterceptorFactory(crudOptions), | ||
...getRoutesInterceptors(crudOptions.routes.createOneBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.createOneBase), | ||
], prototype[name]); | ||
helpers_1.setAction(enums_1.CrudActions.CreateOne, prototype[name]); | ||
helpers_1.setSwaggerParams(prototype[name], crudOptions); | ||
helpers_1.setSwaggerOperation(prototype[name], `Create one ${dto.name}`); | ||
helpers_1.setSwaggerOkResponse(prototype[name], dto); | ||
}, | ||
createManyBase(target, name, dto, crudOptions) { | ||
const prototype = target.prototype; | ||
prototype[name] = function createManyBase(params, body) { | ||
return this.service.createMany(body, params); | ||
prototype[name] = function createManyBase(parsedParams, body) { | ||
return this.service.createMany(body, parsedParams); | ||
}; | ||
@@ -100,11 +105,13 @@ const isArray = utils_1.mockValidatorDecorator('isArray'); | ||
interceptors_1.RestfulParamsInterceptorFactory(crudOptions), | ||
...getRoutesInterceptors(crudOptions.routes.createManyBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.createManyBase), | ||
], prototype[name]); | ||
helpers_1.setAction(enums_1.CrudActions.CreateMany, prototype[name]); | ||
helpers_1.setSwaggerParams(prototype[name], crudOptions); | ||
helpers_1.setSwaggerOperation(prototype[name], `Create many ${dto.name}`); | ||
helpers_1.setSwaggerOkResponse(prototype[name], dto, false); | ||
}, | ||
updateOneBase(target, name, dto, crudOptions) { | ||
const prototype = target.prototype; | ||
prototype[name] = function updateOneBase(params, body) { | ||
return this.service.updateOne(body, params, crudOptions.routes); | ||
prototype[name] = function updateOneBase(parsedParams, body) { | ||
return this.service.updateOne(body, parsedParams, crudOptions.routes); | ||
}; | ||
@@ -117,11 +124,13 @@ helpers_1.setParams(Object.assign({}, helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_PARAMS_REQUEST_KEY, 0), helpers_1.createParamMetadata(route_paramtypes_enum_1.RouteParamtypes.BODY, 1, [ | ||
interceptors_1.RestfulParamsInterceptorFactory(crudOptions), | ||
...getRoutesInterceptors(crudOptions.routes.updateOneBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.updateOneBase), | ||
], prototype[name]); | ||
helpers_1.setAction(enums_1.CrudActions.UpdateOne, prototype[name]); | ||
helpers_1.setSwaggerParams(prototype[name], crudOptions); | ||
helpers_1.setSwaggerOperation(prototype[name], `Update one ${dto.name}`); | ||
helpers_1.setSwaggerOkResponse(prototype[name], dto); | ||
}, | ||
deleteOneBase(target, name, crudOptions) { | ||
deleteOneBase(target, name, dto, crudOptions) { | ||
const prototype = target.prototype; | ||
prototype[name] = function deleteOneBase(params) { | ||
return this.service.deleteOne(params, crudOptions.routes); | ||
prototype[name] = function deleteOneBase(parsedParams) { | ||
return this.service.deleteOne(parsedParams, crudOptions.routes); | ||
}; | ||
@@ -132,6 +141,7 @@ helpers_1.setParams(Object.assign({}, helpers_1.createCustomRequestParamMetadata(constants_1.PARSED_PARAMS_REQUEST_KEY, 0)), target, name); | ||
interceptors_1.RestfulParamsInterceptorFactory(crudOptions), | ||
...getRoutesInterceptors(crudOptions.routes.deleteOneBase), | ||
...helpers_1.getRouteInterceptors(crudOptions.routes.deleteOneBase), | ||
], prototype[name]); | ||
helpers_1.setAction(enums_1.CrudActions.DeleteOne, prototype[name]); | ||
helpers_1.setSwaggerParams(prototype[name], crudOptions); | ||
helpers_1.setSwaggerOperation(prototype[name], `Delete one ${dto.name}`); | ||
}, | ||
@@ -187,4 +197,4 @@ }; | ||
const path = helpers_1.getControllerPath(target); | ||
paramsOptionsInit(crudOptions); | ||
const slug = getRoutesSlugName(crudOptions, path); | ||
helpers_1.paramsOptionsInit(crudOptions); | ||
const slug = helpers_1.getRoutesSlugName(crudOptions, path); | ||
Object.keys(baseRoutes).forEach((name) => { | ||
@@ -196,5 +206,3 @@ const route = baseRoutes[name]; | ||
} | ||
route.name !== 'deleteOneBase' | ||
? baseRoutesInit[route.name](target, route.name, dto, crudOptions) | ||
: baseRoutesInit[route.name](target, route.name, crudOptions); | ||
baseRoutesInit[route.name](target, route.name, dto, crudOptions); | ||
route.enable = true; | ||
@@ -210,4 +218,10 @@ } | ||
const baseAction = helpers_1.getAction(prototype[overrided]); | ||
const baseSwaggerParams = helpers_1.getSwaggerParams(prototype[overrided]); | ||
const baseSwaggerOkResponse = helpers_1.getSwaggeOkResponse(prototype[overrided]); | ||
const baseSwaggerOperation = helpers_1.getSwaggerOperation(prototype[overrided]); | ||
helpers_1.setInterceptors([...baseInterceptors, ...interceptors], prototype[name]); | ||
helpers_1.setAction(baseAction, prototype[name]); | ||
helpers_1.setSwaggerParamsMeta(baseSwaggerParams, prototype[name]); | ||
helpers_1.setSwaggerOkResponseMeta(baseSwaggerOkResponse, prototype[name]); | ||
helpers_1.setSwaggerOperationMeta(baseSwaggerOperation, prototype[name]); | ||
helpers_1.setRoute(route.path, route.method, prototype[name]); | ||
@@ -223,2 +237,3 @@ route.override = true; | ||
}); | ||
helpers_1.cleanRoutesOptionsInterceptors(crudOptions); | ||
}; | ||
@@ -229,38 +244,2 @@ exports.Override = (name) => (target, key, descriptor) => { | ||
}; | ||
function paramsOptionsInit(crudOptions) { | ||
const check = (obj) => shared_utils_1.isNil(obj) || !shared_utils_1.isObject(obj) || !Object.keys(obj).length; | ||
if (check(crudOptions.params)) { | ||
crudOptions.params = { id: 'number' }; | ||
} | ||
if (check(crudOptions.routes)) { | ||
crudOptions.routes = {}; | ||
} | ||
if (check(crudOptions.routes.getManyBase)) { | ||
crudOptions.routes.getManyBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.getOneBase)) { | ||
crudOptions.routes.getOneBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.createOneBase)) { | ||
crudOptions.routes.createOneBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.createManyBase)) { | ||
crudOptions.routes.createManyBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.updateOneBase)) { | ||
crudOptions.routes.updateOneBase = { allowParamsOverride: false, interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.deleteOneBase)) { | ||
crudOptions.routes.deleteOneBase = { returnDeleted: false, interceptors: [] }; | ||
} | ||
} | ||
function getRoutesSlugName(crudOptions, path) { | ||
if (!shared_utils_1.isNil(crudOptions.params.id)) { | ||
return 'id'; | ||
} | ||
return Object.keys(crudOptions.params).filter((slug) => !path.includes(`:${slug}`))[0] || 'id'; | ||
} | ||
function getRoutesInterceptors(routeOptions) { | ||
return Array.isArray(routeOptions.interceptors) ? routeOptions.interceptors : []; | ||
} | ||
//# sourceMappingURL=crud.decorator.js.map |
@@ -1,2 +0,2 @@ | ||
import { RequestMethod, ParseIntPipe, ValidationPipe } from '@nestjs/common'; | ||
import { RequestMethod, ValidationPipe } from '@nestjs/common'; | ||
import { RouteParamtypes } from '@nestjs/common/enums/route-paramtypes.enum'; | ||
@@ -11,4 +11,9 @@ import { CrudActions, CrudValidate } from '../enums'; | ||
export declare function setAction(action: CrudActions, func: Function): void; | ||
export declare function setSwaggerOkResponseMeta(meta: any, func: Function): void; | ||
export declare function setSwaggerOperationMeta(meta: any, func: Function): void; | ||
export declare function setSwaggerParamsMeta(meta: any, func: Function): void; | ||
export declare function setSwaggerOkResponse(func: Function, dto: any, isArray?: boolean): void; | ||
export declare function setSwaggerOperation(func: Function, summary?: string): void; | ||
export declare function setSwaggerParams(func: Function, crudOptions: CrudOptions): void; | ||
export declare function setSwaggerQueryGetOne(func: Function, name: string): void; | ||
export declare function setSwaggerQueryGetOne(func: Function): void; | ||
export declare function setSwaggerQueryGetMany(func: Function, name: string): void; | ||
@@ -21,4 +26,10 @@ export declare function createParamMetadata(paramtype: RouteParamtypes, index: number, pipes?: any[], data?: any): any; | ||
export declare function getControllerPath(target: any): string; | ||
export declare function getSwaggerParams(func: Function): any[]; | ||
export declare function getSwaggeOkResponse(func: Function): any; | ||
export declare function getSwaggerOperation(func: Function): any; | ||
export declare function setValidationPipe(crudOptions: CrudOptions, group: CrudValidate): ValidationPipe; | ||
export declare function setParseIntPipe(): ParseIntPipe; | ||
export declare function enableRoute(name: BaseRouteName, crudOptions: CrudOptions): boolean; | ||
export declare function paramsOptionsInit(crudOptions: CrudOptions): void; | ||
export declare function getRoutesSlugName(crudOptions: CrudOptions, path: string): string; | ||
export declare function getRouteInterceptors(routeOptions: any): any[]; | ||
export declare function cleanRoutesOptionsInterceptors(crudOptions: CrudOptions): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const common_1 = require("@nestjs/common"); | ||
const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); | ||
const constants_1 = require("@nestjs/common/constants"); | ||
@@ -28,4 +29,44 @@ const constants_2 = require("../constants"); | ||
exports.setAction = setAction; | ||
function setSwaggerOkResponseMeta(meta, func) { | ||
if (utils_1.swagger) { | ||
Reflect.defineMetadata(utils_1.swagger.DECORATORS.API_RESPONSE, meta, func); | ||
} | ||
} | ||
exports.setSwaggerOkResponseMeta = setSwaggerOkResponseMeta; | ||
function setSwaggerOperationMeta(meta, func) { | ||
if (utils_1.swagger) { | ||
Reflect.defineMetadata(utils_1.swagger.DECORATORS.API_OPERATION, meta, func); | ||
} | ||
} | ||
exports.setSwaggerOperationMeta = setSwaggerOperationMeta; | ||
function setSwaggerParamsMeta(meta, func) { | ||
if (utils_1.swagger) { | ||
Reflect.defineMetadata(utils_1.swagger.DECORATORS.API_PARAMETERS, meta, func); | ||
} | ||
} | ||
exports.setSwaggerParamsMeta = setSwaggerParamsMeta; | ||
function setSwaggerOkResponse(func, dto, isArray) { | ||
if (utils_1.swagger) { | ||
const metadata = getSwaggeOkResponse(func); | ||
const groupedMetadata = { | ||
[200]: { | ||
type: dto, | ||
isArray, | ||
description: '', | ||
}, | ||
}; | ||
setSwaggerOkResponseMeta(Object.assign({}, metadata, groupedMetadata), func); | ||
} | ||
} | ||
exports.setSwaggerOkResponse = setSwaggerOkResponse; | ||
function setSwaggerOperation(func, summary = '') { | ||
if (utils_1.swagger) { | ||
const metadata = getSwaggerOperation(func); | ||
setSwaggerOperationMeta(Object.assign(metadata, { summary }), func); | ||
} | ||
} | ||
exports.setSwaggerOperation = setSwaggerOperation; | ||
function setSwaggerParams(func, crudOptions) { | ||
if (utils_1.swagger) { | ||
const metadata = getSwaggerParams(func); | ||
const params = Object.keys(crudOptions.params).map((key) => ({ | ||
@@ -37,12 +78,13 @@ name: key, | ||
})); | ||
setSwagger(params, func); | ||
setSwaggerParamsMeta([...metadata, ...params], func); | ||
} | ||
} | ||
exports.setSwaggerParams = setSwaggerParams; | ||
function setSwaggerQueryGetOne(func, name) { | ||
function setSwaggerQueryGetOne(func) { | ||
if (utils_1.swagger) { | ||
const metadata = getSwaggerParams(func); | ||
const params = [ | ||
{ | ||
name: 'fields', | ||
description: `${name} fields`, | ||
description: `<h4>Selects fields that should be returned in the reponse body.</h4><i>Syntax:</i> <strong>?fields=field1,field2,...</strong> <br/><i>Example:</i> <strong>?fields=email,name</strong>`, | ||
required: false, | ||
@@ -54,3 +96,3 @@ in: 'query', | ||
name: 'join', | ||
description: `Join relational entity with ${name}`, | ||
description: `<h4>Receive joined relational objects in GET result (with all or selected fields).</h4><i>Syntax:</i><ul><li><strong>?join=relation</strong></li><li><strong>?join=relation||field1,field2,...</strong></li><li><strong>?join=relation1||field11,field12,...&join=relation1.nested||field21,field22,...&join=...</strong></li></ul><br/><i>Examples:</i></i><ul><li><strong>?join=profile</strong></li><li><strong>?join=profile||firstName,email</strong></li><li><strong>?join=profile||firstName,email&join=notifications||content&join=tasks</strong></li><li><strong>?join=relation1&join=relation1.nested&join=relation1.nested.deepnested</strong></li></ul><strong><i>Notice:</i></strong> <code>id</code> field always persists in relational objects. To use nested relations, the parent level MUST be set before the child level like example above.`, | ||
required: false, | ||
@@ -62,3 +104,3 @@ in: 'query', | ||
name: 'cache', | ||
description: `Reset cached result`, | ||
description: `<h4>Reset cache (if was enabled) and receive entities from the DB.</h4><i>Usage:</i> <strong>?cache=0</strong>`, | ||
required: false, | ||
@@ -69,3 +111,3 @@ in: 'query', | ||
]; | ||
setSwagger(params, func); | ||
setSwaggerParamsMeta([...metadata, ...params], func); | ||
} | ||
@@ -76,6 +118,7 @@ } | ||
if (utils_1.swagger) { | ||
const metadata = getSwaggerParams(func); | ||
const params = [ | ||
{ | ||
name: 'fields', | ||
description: `${name} fields in the collection`, | ||
description: `<h4>Selects fields that should be returned in the reponse body.</h4><i>Syntax:</i> <strong>?fields=field1,field2,...</strong> <br/><i>Example:</i> <strong>?fields=email,name</strong>`, | ||
required: false, | ||
@@ -87,3 +130,3 @@ in: 'query', | ||
name: 'filter', | ||
description: `Filter ${name} collection with condition`, | ||
description: `<h4>Adds fields request condition (multiple conditions) to the request.</h4><i>Syntax:</i> <strong>?filter=field||condition||value</strong><br/><i>Examples:</i> <ul><li><strong>?filter=name||eq||batman</strong></li><li><strong>?filter=isVillain||eq||false&filter=city||eq||Arkham</strong> (multiple filters are treated as a combination of AND type of conditions)</li><li><strong>?filter=shots||in||12,26</strong> (some conditions accept multiple values separated by commas)</li><li><strong>?filter=power||isnull</strong> (some conditions don't accept value)</li></ul><br/>Filter Conditions:<ul><li><strong><code>eq</code></strong> (<code>=</code>, equal)</li><li><strong><code>ne</code></strong> (<code>!=</code>, not equal)</li><li><strong><code>gt</code></strong> (<code>></code>, greater than)</li><li><strong><code>lt</code></strong> (<code><</code>, lower that)</li><li><strong><code>gte</code></strong> (<code>>=</code>, greater than or equal)</li><li><strong><code>lte</code></strong> (<code><=</code>, lower than or equal)</li><li><strong><code>starts</code></strong> (<code>LIKE val%</code>, starts with)</li><li><strong><code>ends</code></strong> (<code>LIKE %val</code>, ends with)</li><li><strong><code>cont</code></strong> (<code>LIKE %val%</code>, contains)</li><li><strong><code>excl</code></strong> (<code>NOT LIKE %val%</code>, not contains)</li><li><strong><code>in</code></strong> (<code>IN</code>, in range, <strong><em>accepts multiple values</em></strong>)</li><li><strong><code>notin</code></strong> (<code>NOT IN</code>, not in range, <strong><em>accepts multiple values</em></strong>)</li><li><strong><code>isnull</code></strong> (<code>IS NULL</code>, is NULL, <strong><em>doesn't accept value</em></strong>)</li><li><strong><code>notnull</code></strong> (<code>IS NOT NULL</code>, not NULL, <strong><em>doesn't accept value</em></strong>)</li><li><strong><code>between</code></strong> (<code>BETWEEN</code>, between, <strong><em>accepts two values</em></strong>)</li></ul>`, | ||
required: false, | ||
@@ -95,3 +138,3 @@ in: 'query', | ||
name: 'or', | ||
description: `Filter ${name} collection with condition (OR)`, | ||
description: `<h4>Adds <code>OR</code> conditions to the request.</h4><i>Syntax:</i> <strong>?or=field||condition||value</strong><br/>It uses the same conditions as the filter parameter<br/><i>Rules and <i>Examples:</i></i><ul><li>If there is only <strong>one</strong> <code>or</code> present (without <code>filter</code>) then it will be interpreted as simple filter:</li><ul><li><strong>?or=name||eq||batman</strong></li></ul></ul><ul><li>If there are <strong>multiple</strong> <code>or</code> present (without <code>filter</code>) then it will be interpreted as a compination of <code>OR</code> conditions, as follows:<br><code>WHERE {or} OR {or} OR ...</code></li><ul><li><strong>?or=name||eq||batman&or=name||eq||joker</strong></li></ul></ul><ul><li>If there are <strong>one</strong> <code>or</code> and <strong>one</strong> <code>filter</code> then it will be interpreted as <code>OR</code> condition, as follows:<br><code>WHERE {filter} OR {or}</code></li><ul><li><strong>?filter=name||eq||batman&or=name||eq||joker</strong></li></ul></ul><ul><li>If present <strong>both</strong> <code>or</code> and <code>filter</code> in any amount (<strong>one</strong> or <strong>miltiple</strong> each) then both interpreted as a combitation of <code>AND</code> conditions and compared with each other by <code>OR</code> condition, as follows:<br><code>WHERE ({filter} AND {filter} AND ...) OR ({or} AND {or} AND ...)</code></li><ul><li><strong>?filter=type||eq||hero&filter=status||eq||alive&or=type||eq||villain&or=status||eq||dead</strong></li></ul></ul>`, | ||
required: false, | ||
@@ -103,3 +146,3 @@ in: 'query', | ||
name: 'sort', | ||
description: `Sort ${name} collection by field and order`, | ||
description: `<h4>Adds sort by field (by multiple fields) and order to query result.</h4><i>Syntax:</i> <strong>?sort=field,ASC|DESC</strong><br/><i>Examples:</i></i><ul><li><strong>?sort=name,ASC</strong></li><li><strong>?sort=name,ASC&sort=id,DESC</strong></li></ul>`, | ||
required: false, | ||
@@ -111,3 +154,3 @@ in: 'query', | ||
name: 'join', | ||
description: `Join relational entity with ${name}`, | ||
description: `<h4>Receive joined relational objects in GET result (with all or selected fields).</h4><i>Syntax:</i><ul><li><strong>?join=relation</strong></li><li><strong>?join=relation||field1,field2,...</strong></li><li><strong>?join=relation1||field11,field12,...&join=relation1.nested||field21,field22,...&join=...</strong></li></ul><br/><i>Examples:</i></i><ul><li><strong>?join=profile</strong></li><li><strong>?join=profile||firstName,email</strong></li><li><strong>?join=profile||firstName,email&join=notifications||content&join=tasks</strong></li><li><strong>?join=relation1&join=relation1.nested&join=relation1.nested.deepnested</strong></li></ul><strong><i>Notice:</i></strong> <code>id</code> field always persists in relational objects. To use nested relations, the parent level MUST be set before the child level like example above.`, | ||
required: false, | ||
@@ -119,3 +162,3 @@ in: 'query', | ||
name: 'limit', | ||
description: `Limit ${name} collection`, | ||
description: `<h4>Receive <code>N</code> amount of entities.</h4><i>Syntax:</i> <strong>?limit=number</strong><br/><i>Example:</i> <strong>?limit=10</strong>`, | ||
required: false, | ||
@@ -127,3 +170,3 @@ in: 'query', | ||
name: 'offset', | ||
description: `Offset ${name} collection`, | ||
description: `<h4>Offset <code>N</code> amount of entities.</h4><i>Syntax:</i> <strong>?offset=number</strong><br/><i>Example:</i> <strong>?offset=10</strong>`, | ||
required: false, | ||
@@ -135,3 +178,3 @@ in: 'query', | ||
name: 'page', | ||
description: `Set page of ${name} collection`, | ||
description: `<h4>Receive a portion of <code>limit</code> (per_page) entities (alternative to <code>offset</code>). Will be applied if <code>limit</code> is set up.</h4><i>Syntax:</i> <strong>?page=number</strong><br/><i>Example:</i> <strong>?page=2</strong>`, | ||
required: false, | ||
@@ -142,4 +185,11 @@ in: 'query', | ||
{ | ||
name: 'per_page', | ||
description: `Alias for limit`, | ||
required: false, | ||
in: 'query', | ||
type: Number, | ||
}, | ||
{ | ||
name: 'cache', | ||
description: `Reset cached result`, | ||
description: `<h4>Reset cache (if was enabled) and receive entities from the DB.</h4><i>Usage:</i> <strong>?cache=0</strong>`, | ||
required: false, | ||
@@ -150,3 +200,3 @@ in: 'query', | ||
]; | ||
setSwagger(params, func); | ||
setSwaggerParamsMeta([...metadata, ...params], func); | ||
} | ||
@@ -192,2 +242,20 @@ } | ||
exports.getControllerPath = getControllerPath; | ||
function getSwaggerParams(func) { | ||
if (utils_1.swagger) { | ||
return Reflect.getMetadata(utils_1.swagger.DECORATORS.API_PARAMETERS, func) || []; | ||
} | ||
} | ||
exports.getSwaggerParams = getSwaggerParams; | ||
function getSwaggeOkResponse(func) { | ||
if (utils_1.swagger) { | ||
return Reflect.getMetadata(utils_1.swagger.DECORATORS.API_RESPONSE, func) || {}; | ||
} | ||
} | ||
exports.getSwaggeOkResponse = getSwaggeOkResponse; | ||
function getSwaggerOperation(func) { | ||
if (utils_1.swagger) { | ||
return Reflect.getMetadata(utils_1.swagger.DECORATORS.API_OPERATION, func) || {}; | ||
} | ||
} | ||
exports.getSwaggerOperation = getSwaggerOperation; | ||
function setValidationPipe(crudOptions, group) { | ||
@@ -200,10 +268,3 @@ const options = crudOptions.validation || {}; | ||
exports.setValidationPipe = setValidationPipe; | ||
function setParseIntPipe() { | ||
return utils_1.hasTypeorm ? new common_1.ParseIntPipe() : undefined; | ||
} | ||
exports.setParseIntPipe = setParseIntPipe; | ||
function enableRoute(name, crudOptions) { | ||
if (!crudOptions.routes) { | ||
return true; | ||
} | ||
if (crudOptions.routes.only && crudOptions.routes.only.length) { | ||
@@ -218,6 +279,49 @@ return crudOptions.routes.only.some((only) => only === name); | ||
exports.enableRoute = enableRoute; | ||
function setSwagger(params, func) { | ||
const metadata = Reflect.getMetadata(utils_1.swagger.DECORATORS.API_PARAMETERS, func) || []; | ||
Reflect.defineMetadata(utils_1.swagger.DECORATORS.API_PARAMETERS, [...metadata, ...params], func); | ||
function paramsOptionsInit(crudOptions) { | ||
const check = (obj) => shared_utils_1.isNil(obj) || !shared_utils_1.isObject(obj) || !Object.keys(obj).length; | ||
if (check(crudOptions.params)) { | ||
crudOptions.params = { id: 'number' }; | ||
} | ||
if (check(crudOptions.routes)) { | ||
crudOptions.routes = {}; | ||
} | ||
if (check(crudOptions.routes.getManyBase)) { | ||
crudOptions.routes.getManyBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.getOneBase)) { | ||
crudOptions.routes.getOneBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.createOneBase)) { | ||
crudOptions.routes.createOneBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.createManyBase)) { | ||
crudOptions.routes.createManyBase = { interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.updateOneBase)) { | ||
crudOptions.routes.updateOneBase = { allowParamsOverride: false, interceptors: [] }; | ||
} | ||
if (check(crudOptions.routes.deleteOneBase)) { | ||
crudOptions.routes.deleteOneBase = { returnDeleted: false, interceptors: [] }; | ||
} | ||
} | ||
exports.paramsOptionsInit = paramsOptionsInit; | ||
function getRoutesSlugName(crudOptions, path) { | ||
if (!shared_utils_1.isNil(crudOptions.params.id)) { | ||
return 'id'; | ||
} | ||
return Object.keys(crudOptions.params).filter((slug) => !path.includes(`:${slug}`))[0] || 'id'; | ||
} | ||
exports.getRoutesSlugName = getRoutesSlugName; | ||
function getRouteInterceptors(routeOptions) { | ||
return Array.isArray(routeOptions.interceptors) ? routeOptions.interceptors : []; | ||
} | ||
exports.getRouteInterceptors = getRouteInterceptors; | ||
function cleanRoutesOptionsInterceptors(crudOptions) { | ||
Object.keys(crudOptions.routes).forEach((option) => { | ||
if (option !== 'exclude' && option !== 'only') { | ||
crudOptions.routes[option].interceptors = []; | ||
} | ||
}); | ||
} | ||
exports.cleanRoutesOptionsInterceptors = cleanRoutesOptionsInterceptors; | ||
//# sourceMappingURL=helpers.js.map |
@@ -20,5 +20,6 @@ "use strict"; | ||
const constants_1 = require("../constants"); | ||
let counter = 0; | ||
function RestfulParamsInterceptorFactory(crudOptions) { | ||
let RestfulParamsInterceptor = class RestfulParamsInterceptor { | ||
intercept(context, call$) { | ||
intercept(context, next) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
@@ -29,3 +30,3 @@ const req = context.switchToHttp().getRequest(); | ||
req[constants_1.PARSED_OPTIONS_METADATA] = options; | ||
return call$; | ||
return next.handle(); | ||
}); | ||
@@ -76,3 +77,3 @@ } | ||
} | ||
return options; | ||
return Object.assign({}, crudOptions, { options }); | ||
} | ||
@@ -83,2 +84,6 @@ }; | ||
], RestfulParamsInterceptor); | ||
Object.defineProperty(RestfulParamsInterceptor, 'name', { | ||
value: `RestfulParamsInterceptor${counter++}`, | ||
writable: false, | ||
}); | ||
return RestfulParamsInterceptor; | ||
@@ -85,0 +90,0 @@ } |
@@ -1,3 +0,2 @@ | ||
import { NestInterceptor, ExecutionContext } from '@nestjs/common'; | ||
import { Observable } from 'rxjs'; | ||
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; | ||
export declare class RestfulQueryInterceptor implements NestInterceptor { | ||
@@ -7,3 +6,3 @@ private delim; | ||
private reservedFields; | ||
intercept(context: ExecutionContext, call$: Observable<any>): Observable<any>; | ||
intercept(context: ExecutionContext, next: CallHandler): import("rxjs").Observable<any>; | ||
private transform; | ||
@@ -10,0 +9,0 @@ private splitString; |
@@ -33,6 +33,6 @@ "use strict"; | ||
} | ||
intercept(context, call$) { | ||
intercept(context, next) { | ||
const req = context.switchToHttp().getRequest(); | ||
req[constants_1.PARSED_QUERY_REQUEST_KEY] = this.transform(req.query); | ||
return call$; | ||
return next.handle(); | ||
} | ||
@@ -127,3 +127,3 @@ transform(query) { | ||
const result = []; | ||
for (let item of param) { | ||
for (const item of param) { | ||
result.push(parser.call(this, item)); | ||
@@ -130,0 +130,0 @@ } |
import { FilterParamParsed } from './request-parsed-params.interface'; | ||
import { RestfulOptions } from './restful-options.interface'; | ||
import { CrudOptions } from './crud-options.interface'; | ||
import { GetManyDefaultResponse } from './get-many-default-response.interface'; | ||
import { RestfulParamsDto } from '../dto/restful-params.dto'; | ||
@@ -7,8 +8,8 @@ import { RestfulService } from '../classes/restful-service.class'; | ||
service: S; | ||
getManyBase?(query: RestfulParamsDto, options: RestfulOptions): Promise<T[]>; | ||
getOneBase?(query: RestfulParamsDto, options: RestfulOptions): Promise<T>; | ||
createOneBase?(params: FilterParamParsed[], dto: T): Promise<T>; | ||
createManyBase?(params: FilterParamParsed[], dto: EntitiesBulk<T>): Promise<T[]>; | ||
updateOneBase?(params: FilterParamParsed[], dto: T): Promise<T>; | ||
deleteOneBase?(params: FilterParamParsed[]): Promise<void>; | ||
getManyBase?(parsedQuery: RestfulParamsDto, parsedOptions: CrudOptions): Promise<GetManyDefaultResponse<T> | T[]>; | ||
getOneBase?(parsedQuery: RestfulParamsDto, parsedOptions: CrudOptions): Promise<T>; | ||
createOneBase?(parsedParams: FilterParamParsed[], dto: T): Promise<T>; | ||
createManyBase?(parsedParams: FilterParamParsed[], dto: EntitiesBulk<T>): Promise<T[]>; | ||
updateOneBase?(parsedParams: FilterParamParsed[], dto: T): Promise<T>; | ||
deleteOneBase?(parsedParams: FilterParamParsed[]): Promise<void>; | ||
} | ||
@@ -15,0 +16,0 @@ export interface EntitiesBulk<T> { |
@@ -5,22 +5,28 @@ import { BaseRouteName } from '../types'; | ||
only?: BaseRouteName[]; | ||
getManyBase?: { | ||
interceptors?: any[]; | ||
}; | ||
getOneBase?: { | ||
interceptors?: any[]; | ||
}; | ||
createOneBase?: { | ||
interceptors?: any[]; | ||
}; | ||
createManyBase?: { | ||
interceptors?: any[]; | ||
}; | ||
updateOneBase?: { | ||
interceptors?: any[]; | ||
allowParamsOverride?: boolean; | ||
}; | ||
deleteOneBase?: { | ||
interceptors?: any[]; | ||
returnDeleted?: boolean; | ||
}; | ||
getManyBase?: GetMayRouteOptions; | ||
getOneBase?: GetOneRouteOptions; | ||
createOneBase?: CreateOneRouteOptions; | ||
createManyBase?: CreateManyRouteOptions; | ||
updateOneBase?: UpdateOneRouteOptions; | ||
deleteOneBase?: DeleteOneRouteOptions; | ||
} | ||
export interface GetMayRouteOptions { | ||
interceptors?: any[]; | ||
} | ||
export interface GetOneRouteOptions { | ||
interceptors?: any[]; | ||
} | ||
export interface CreateOneRouteOptions { | ||
interceptors?: any[]; | ||
} | ||
export interface CreateManyRouteOptions { | ||
interceptors?: any[]; | ||
} | ||
export interface UpdateOneRouteOptions { | ||
interceptors?: any[]; | ||
allowParamsOverride?: boolean; | ||
} | ||
export interface DeleteOneRouteOptions { | ||
interceptors?: any[]; | ||
returnDeleted?: boolean; | ||
} |
{ | ||
"name": "@nestjsx/crud", | ||
"version": "3.0.0-rc.1", | ||
"version": "3.0.0-rc.2", | ||
"description": "NestJs CRUD for RESTful APIs", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
import { DeepPartial, Repository } from 'typeorm'; | ||
import { RestfulService } from '../classes/restful-service.class'; | ||
import { FilterParamParsed, RequestParamsParsed, RestfulOptions, RoutesOptions, GetManyDefaultResponse } from '../interfaces'; | ||
import { RestfulService } from '../classes'; | ||
import { FilterParamParsed, RequestParamsParsed, RestfulOptions, GetManyDefaultResponse, UpdateOneRouteOptions, DeleteOneRouteOptions } from '../interfaces'; | ||
export declare class RepositoryService<T> extends RestfulService<T> { | ||
@@ -13,3 +13,4 @@ protected repo: Repository<T>; | ||
private readonly alias; | ||
getMany(query?: RequestParamsParsed, options?: RestfulOptions): Promise<GetManyDefaultResponse<T>>; | ||
decidePagination(query: RequestParamsParsed, mergedOptions: RestfulOptions): boolean; | ||
getMany(query?: RequestParamsParsed, options?: RestfulOptions): Promise<GetManyDefaultResponse<T> | T[]>; | ||
getOne({ fields, join, cache }?: RequestParamsParsed, options?: RestfulOptions): Promise<T>; | ||
@@ -20,4 +21,4 @@ createOne(data: T, params: FilterParamParsed[]): Promise<T>; | ||
}, params?: FilterParamParsed[]): Promise<T[]>; | ||
updateOne(data: DeepPartial<T>, params?: FilterParamParsed[], routesOptions?: RoutesOptions): Promise<T>; | ||
deleteOne(params: FilterParamParsed[], routesOptions?: RoutesOptions): Promise<void | T>; | ||
updateOne(data: DeepPartial<T>, params?: FilterParamParsed[], routeOptions?: UpdateOneRouteOptions): Promise<T>; | ||
deleteOne(params: FilterParamParsed[], routeOptions?: DeleteOneRouteOptions): Promise<void | T>; | ||
private getOneOrFail; | ||
@@ -30,2 +31,3 @@ private buildQuery; | ||
private hasColumn; | ||
private hasRelation; | ||
private validateHasColumn; | ||
@@ -32,0 +34,0 @@ private getAllowedColumns; |
@@ -14,5 +14,5 @@ "use strict"; | ||
const class_transformer_1 = require("class-transformer"); | ||
const restful_service_class_1 = require("../classes/restful-service.class"); | ||
const classes_1 = require("../classes"); | ||
const utils_1 = require("../utils"); | ||
class RepositoryService extends restful_service_class_1.RestfulService { | ||
class RepositoryService extends classes_1.RestfulService { | ||
constructor(repo) { | ||
@@ -33,15 +33,16 @@ super(); | ||
} | ||
decidePagination(query, mergedOptions) { | ||
return (isFinite(query.page) || isFinite(query.offset)) && !!this.getTake(query, mergedOptions); | ||
} | ||
getMany(query = {}, options = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const builder = yield this.buildQuery(query, options); | ||
const [data, total] = yield Promise.all([builder.getMany(), builder.getCount()]); | ||
const mergedOptions = Object.assign({}, this.options, options); | ||
const limit = this.getTake(query, mergedOptions); | ||
return { | ||
data, | ||
count: data.length, | ||
total, | ||
page: query.page || 1, | ||
pageCount: limit && total ? Math.round(total / limit) : undefined, | ||
}; | ||
if (this.decidePagination(query, mergedOptions)) { | ||
const [data, total] = yield builder.getManyAndCount(); | ||
const limit = builder.expressionMap.take; | ||
const offset = builder.expressionMap.skip; | ||
return this.createPageInfo(data, total, limit, offset); | ||
} | ||
return builder.getMany(); | ||
}); | ||
@@ -81,6 +82,6 @@ } | ||
} | ||
updateOne(data, params = [], routesOptions = {}) { | ||
updateOne(data, params = [], routeOptions = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const found = yield this.getOneOrFail({}, { filter: params }); | ||
if (params.length && !routesOptions.updateOneBase.allowParamsOverride) { | ||
if (params.length && !routeOptions.allowParamsOverride) { | ||
for (const filter of params) { | ||
@@ -93,7 +94,7 @@ data[filter.field] = filter.value; | ||
} | ||
deleteOne(params, routesOptions = {}) { | ||
deleteOne(params, routeOptions = {}) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const found = yield this.getOneOrFail({}, { filter: params }); | ||
const deleted = yield this.repo.remove(found); | ||
if (routesOptions.deleteOneBase.returnDeleted) { | ||
if (routeOptions.returnDeleted) { | ||
for (const filter of params) { | ||
@@ -174,3 +175,3 @@ deleted[filter.field] = filter.value; | ||
if (utils_1.isArrayFull(query.join)) { | ||
const joinOptions = Object.assign({}, (this.options.join ? this.options.join : {}), (options.join ? options.join : {})); | ||
const joinOptions = mergedOptions.join || {}; | ||
if (Object.keys(joinOptions).length) { | ||
@@ -186,20 +187,13 @@ for (let i = 0; i < query.join.length; i++) { | ||
const take = this.getTake(query, mergedOptions); | ||
if (take) { | ||
if (isFinite(take)) { | ||
builder.take(take); | ||
} | ||
const skip = this.getSkip(query, take); | ||
if (skip) { | ||
if (isFinite(skip)) { | ||
builder.skip(skip); | ||
} | ||
} | ||
if (query.cache === 0 && | ||
this.repo.metadata.connection.queryResultCache && | ||
this.repo.metadata.connection.queryResultCache.remove) { | ||
const cacheId = this.getCacheId(query, options); | ||
yield this.repo.metadata.connection.queryResultCache.remove([cacheId]); | ||
if (mergedOptions.cache && query.cache !== 0) { | ||
builder.cache(builder.getQueryAndParameters(), mergedOptions.cache); | ||
} | ||
if (mergedOptions.cache) { | ||
const cacheId = this.getCacheId(query, options); | ||
builder.cache(cacheId, mergedOptions.cache); | ||
} | ||
return builder; | ||
@@ -250,6 +244,27 @@ }); | ||
} | ||
hasRelation(column) { | ||
return this.entityRelationsHash[column]; | ||
} | ||
validateHasColumn(column) { | ||
if (!this.hasColumn(column)) { | ||
this.throwBadRequestException(`Invalid column name '${column}'`); | ||
if (column.indexOf('.') !== -1) { | ||
const nests = column.split('.'); | ||
if (nests.length > 2) { | ||
this.throwBadRequestException('Too many nested levels! ' + | ||
`Usage: '[join=<other-relation>&]join=[<other-relation>.]<relation>&filter=<relation>.<field>||op||val'`); | ||
} | ||
let relation; | ||
[relation, column] = nests; | ||
if (!this.hasRelation(relation)) { | ||
this.throwBadRequestException(`Invalid relation name '${relation}'`); | ||
} | ||
const noColumn = !this.entityRelationsHash[relation].columns.find((o) => o === column); | ||
if (noColumn) { | ||
this.throwBadRequestException(`Invalid column name '${column}' for relation '${relation}'`); | ||
} | ||
} | ||
else { | ||
if (!this.hasColumn(column)) { | ||
this.throwBadRequestException(`Invalid column name '${column}'`); | ||
} | ||
} | ||
} | ||
@@ -349,3 +364,3 @@ getAllowedColumns(columns, options) { | ||
getSkip(query, take) { | ||
return query.page && take ? take * (query.page - 1) : query.offset ? query.offset : 0; | ||
return query.page && take ? take * (query.page - 1) : query.offset ? query.offset : null; | ||
} | ||
@@ -367,3 +382,3 @@ getTake(query, options) { | ||
} | ||
return options.maxLimit ? options.maxLimit : 0; | ||
return options.maxLimit ? options.maxLimit : null; | ||
} | ||
@@ -386,3 +401,3 @@ getSort(query, options) { | ||
mapOperatorsToQuery(cond, param) { | ||
const field = `${this.alias}.${cond.field}`; | ||
const field = cond.field.indexOf('.') === -1 ? `${this.alias}.${cond.field}` : cond.field; | ||
let str; | ||
@@ -389,0 +404,0 @@ let params; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
175844
2089