@kitstack/nest-powertools
Version:
A comprehensive collection of NestJS powertools, decorators, and utilities to supercharge your backend development
583 lines • 22.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserAgent = exports.IpAddress = exports.CurrentUser = exports.DomainPagination = exports.createPagination = exports.PaginationBuilder = exports.QuickPagination = exports.Pagination = void 0;
const common_1 = require("@nestjs/common");
const enums_1 = require("../types/enums");
const powertools_config_1 = require("../config/powertools.config");
exports.Pagination = (0, common_1.createParamDecorator)((options = {}, ctx) => {
const request = ctx.switchToHttp().getRequest();
const query = request.query;
const configService = powertools_config_1.PowertoolsConfigService.getInstance();
const globalPaginationConfig = configService.getFeatureConfig('pagination');
const config = {
defaultPage: options.defaultPage ??
globalPaginationConfig?.defaultPage ??
enums_1.PaginationDefaults.DEFAULT_PAGE,
defaultLimit: options.defaultLimit ??
globalPaginationConfig?.defaultLimit ??
enums_1.PaginationDefaults.DEFAULT_LIMIT,
maxLimit: options.maxLimit ??
globalPaginationConfig?.maxLimit ??
enums_1.PaginationDefaults.MAX_LIMIT,
minLimit: options.minLimit ?? 1,
allowUnlimited: options.allowUnlimited ??
globalPaginationConfig?.allowUnlimited ??
false,
defaultSortBy: options.defaultSortBy ?? 'id',
defaultSortOrder: options.defaultSortOrder ??
globalPaginationConfig?.sortOrder ??
enums_1.SortOrder.ASC,
allowedSortFields: options.allowedSortFields ?? [],
allowedSortOrders: options.allowedSortOrders ?? [
enums_1.SortOrder.ASC,
enums_1.SortOrder.DESC,
],
pageParam: options.pageParam ?? 'page',
limitParam: options.limitParam ?? 'limit',
sortByParam: options.sortByParam ?? 'sortBy',
sortOrderParam: options.sortOrderParam ?? 'sortOrder',
searchParam: options.searchParam ?? 'search',
filtersParam: options.filtersParam ?? 'filters',
transform: options.transform ?? true,
validate: options.validate ?? true,
customValidator: options.customValidator,
onInvalidQuery: options.onInvalidQuery ?? 'throw',
};
const rawPage = query[config.pageParam];
const rawLimit = query[config.limitParam];
const rawSortBy = query[config.sortByParam];
const rawSortOrder = query[config.sortOrderParam];
const rawSearch = query[config.searchParam];
const rawFilters = query[config.filtersParam];
let page = config.defaultPage;
if (rawPage !== undefined) {
const parsedPage = Number.parseInt(rawPage);
if (config.validate && (isNaN(parsedPage) || parsedPage < 1)) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid page number', {
page: config.defaultPage,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
page = parsedPage || config.defaultPage;
}
let limit = config.defaultLimit;
if (rawLimit !== undefined) {
if (rawLimit === 'unlimited' || rawLimit === '-1') {
if (!config.allowUnlimited) {
return handleInvalidQuery(config.onInvalidQuery, 'Unlimited results not allowed', {
page,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
limit = -1;
}
else {
const parsedLimit = Number.parseInt(rawLimit);
if (config.validate &&
(isNaN(parsedLimit) || parsedLimit < config.minLimit)) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid limit value', {
page,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
if (parsedLimit > config.maxLimit && !config.allowUnlimited) {
limit = config.maxLimit;
}
else {
limit = parsedLimit || config.defaultLimit;
}
}
}
let sortBy = config.defaultSortBy;
if (rawSortBy !== undefined) {
const requestedSortBy = rawSortBy;
if (config.allowedSortFields.length > 0 &&
!config.allowedSortFields.includes(requestedSortBy)) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, `Invalid sort field: ${requestedSortBy}`, {
page,
limit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
}
else {
sortBy = requestedSortBy;
}
}
let sortOrder = config.defaultSortOrder;
if (rawSortOrder !== undefined) {
const requestedSortOrder = rawSortOrder.toUpperCase();
if (!config.allowedSortOrders.includes(requestedSortOrder)) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, `Invalid sort order: ${rawSortOrder}`, {
page,
limit,
sortBy,
sortOrder: config.defaultSortOrder,
});
}
}
else {
sortOrder = requestedSortOrder;
}
}
const search = rawSearch ? String(rawSearch) : undefined;
let filters;
if (rawFilters) {
try {
if (typeof rawFilters === 'string') {
filters = JSON.parse(rawFilters);
}
else {
filters = rawFilters;
}
}
catch (error) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid filters format', {
page,
limit,
sortBy,
sortOrder,
});
}
}
}
const paginationResult = {
page,
limit,
sortBy,
sortOrder,
search,
filters,
offset: limit === -1 ? 0 : (page - 1) * limit,
hasCustomLimits: limit !== config.defaultLimit || page !== config.defaultPage,
originalQuery: { ...query },
};
if (config.customValidator) {
const validationResult = config.customValidator(paginationResult);
if (validationResult !== true) {
const errorMessage = typeof validationResult === 'string'
? validationResult
: 'Custom validation failed';
return handleInvalidQuery(config.onInvalidQuery, errorMessage, {
page: config.defaultPage,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
}
return paginationResult;
function handleInvalidQuery(onInvalidQuery, message, defaultValues) {
switch (onInvalidQuery) {
case 'throw':
throw new common_1.BadRequestException(message);
case 'default':
return {
...defaultValues,
offset: ((defaultValues.page || 1) - 1) * (defaultValues.limit || 10),
hasCustomLimits: false,
originalQuery: { ...query },
};
case 'ignore':
default:
return paginationResult;
}
}
});
exports.QuickPagination = {
Small: (options = {}) => (0, exports.Pagination)({
defaultLimit: 5,
maxLimit: 20,
...options,
}),
Medium: (options = {}) => (0, exports.Pagination)({
defaultLimit: 10,
maxLimit: 50,
...options,
}),
Large: (options = {}) => (0, exports.Pagination)({
defaultLimit: 25,
maxLimit: 200,
...options,
}),
Unlimited: (options = {}) => (0, exports.Pagination)({
defaultLimit: 10,
maxLimit: 1000,
allowUnlimited: true,
...options,
}),
Search: (options = {}) => (0, exports.Pagination)({
defaultLimit: 20,
maxLimit: 100,
searchParam: 'q',
...options,
}),
Strict: (options = {}) => (0, exports.Pagination)({
defaultLimit: 10,
maxLimit: 100,
validate: true,
onInvalidQuery: 'throw',
...options,
}),
Lenient: (options = {}) => (0, exports.Pagination)({
defaultLimit: 10,
maxLimit: 100,
validate: true,
onInvalidQuery: 'default',
...options,
}),
};
class PaginationBuilder {
constructor() {
this.options = {};
}
defaultPage(page) {
this.options.defaultPage = page;
return this;
}
defaultLimit(limit) {
this.options.defaultLimit = limit;
return this;
}
maxLimit(limit) {
this.options.maxLimit = limit;
return this;
}
minLimit(limit) {
this.options.minLimit = limit;
return this;
}
allowUnlimited(allow = true) {
this.options.allowUnlimited = allow;
return this;
}
defaultSortBy(field) {
this.options.defaultSortBy = field;
return this;
}
defaultSortOrder(order) {
this.options.defaultSortOrder = order;
return this;
}
allowedSortFields(fields) {
this.options.allowedSortFields = fields;
return this;
}
allowedSortOrders(orders) {
this.options.allowedSortOrders = orders;
return this;
}
paramNames(params) {
if (params.page)
this.options.pageParam = params.page;
if (params.limit)
this.options.limitParam = params.limit;
if (params.sortBy)
this.options.sortByParam = params.sortBy;
if (params.sortOrder)
this.options.sortOrderParam = params.sortOrder;
if (params.search)
this.options.searchParam = params.search;
if (params.filters)
this.options.filtersParam = params.filters;
return this;
}
validate(enable = true) {
this.options.validate = enable;
return this;
}
onInvalidQuery(behavior) {
this.options.onInvalidQuery = behavior;
return this;
}
customValidator(validator) {
this.options.customValidator = validator;
return this;
}
build() {
const builderOptions = { ...this.options };
return (0, common_1.createParamDecorator)((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const query = request.query;
const configService = powertools_config_1.PowertoolsConfigService.getInstance();
const globalPaginationConfig = configService.getFeatureConfig('pagination');
const config = {
defaultPage: builderOptions.defaultPage ??
globalPaginationConfig?.defaultPage ??
enums_1.PaginationDefaults.DEFAULT_PAGE,
defaultLimit: builderOptions.defaultLimit ??
globalPaginationConfig?.defaultLimit ??
enums_1.PaginationDefaults.DEFAULT_LIMIT,
maxLimit: builderOptions.maxLimit ??
globalPaginationConfig?.maxLimit ??
enums_1.PaginationDefaults.MAX_LIMIT,
minLimit: builderOptions.minLimit ?? 1,
allowUnlimited: builderOptions.allowUnlimited ??
globalPaginationConfig?.allowUnlimited ??
false,
defaultSortBy: builderOptions.defaultSortBy ?? 'id',
defaultSortOrder: builderOptions.defaultSortOrder ??
globalPaginationConfig?.sortOrder ??
enums_1.SortOrder.ASC,
allowedSortFields: builderOptions.allowedSortFields ?? [],
allowedSortOrders: builderOptions.allowedSortOrders ?? [
enums_1.SortOrder.ASC,
enums_1.SortOrder.DESC,
],
pageParam: builderOptions.pageParam ?? 'page',
limitParam: builderOptions.limitParam ?? 'limit',
sortByParam: builderOptions.sortByParam ?? 'sortBy',
sortOrderParam: builderOptions.sortOrderParam ?? 'sortOrder',
searchParam: builderOptions.searchParam ?? 'search',
filtersParam: builderOptions.filtersParam ?? 'filters',
transform: builderOptions.transform ?? true,
validate: builderOptions.validate ?? true,
customValidator: builderOptions.customValidator,
onInvalidQuery: builderOptions.onInvalidQuery ?? 'throw',
};
const rawPage = query[config.pageParam];
const rawLimit = query[config.limitParam];
const rawSortBy = query[config.sortByParam];
const rawSortOrder = query[config.sortOrderParam];
const rawSearch = query[config.searchParam];
const rawFilters = query[config.filtersParam];
let page = config.defaultPage;
if (rawPage !== undefined) {
const parsedPage = Number.parseInt(rawPage);
if (config.validate && (isNaN(parsedPage) || parsedPage < 1)) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid page number', {
page: config.defaultPage,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
page = parsedPage || config.defaultPage;
}
let limit = config.defaultLimit;
if (rawLimit !== undefined) {
if (rawLimit === 'unlimited' || rawLimit === '-1') {
if (!config.allowUnlimited) {
return handleInvalidQuery(config.onInvalidQuery, 'Unlimited results not allowed', {
page,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
limit = -1;
}
else {
const parsedLimit = Number.parseInt(rawLimit);
if (config.validate &&
(isNaN(parsedLimit) || parsedLimit < config.minLimit)) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid limit value', {
page,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
if (parsedLimit > config.maxLimit && !config.allowUnlimited) {
limit = config.maxLimit;
}
else {
limit = parsedLimit || config.defaultLimit;
}
}
}
let sortBy = config.defaultSortBy;
if (rawSortBy !== undefined) {
const requestedSortBy = rawSortBy;
if (config.allowedSortFields.length > 0 &&
!config.allowedSortFields.includes(requestedSortBy)) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, `Invalid sort field: ${requestedSortBy}`, {
page,
limit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
}
else {
sortBy = requestedSortBy;
}
}
let sortOrder = config.defaultSortOrder;
if (rawSortOrder !== undefined) {
const requestedSortOrder = rawSortOrder.toUpperCase();
if (!config.allowedSortOrders.includes(requestedSortOrder)) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, `Invalid sort order: ${rawSortOrder}`, {
page,
limit,
sortBy,
sortOrder: config.defaultSortOrder,
});
}
}
else {
sortOrder = requestedSortOrder;
}
}
const search = rawSearch ? String(rawSearch) : undefined;
let filters;
if (rawFilters) {
try {
if (typeof rawFilters === 'string') {
filters = JSON.parse(rawFilters);
}
else {
filters = rawFilters;
}
}
catch (error) {
if (config.validate) {
return handleInvalidQuery(config.onInvalidQuery, 'Invalid filters format', {
page,
limit,
sortBy,
sortOrder,
});
}
}
}
const paginationResult = {
page,
limit,
sortBy,
sortOrder,
search,
filters,
offset: limit === -1 ? 0 : (page - 1) * limit,
hasCustomLimits: limit !== config.defaultLimit || page !== config.defaultPage,
originalQuery: { ...query },
};
if (config.customValidator) {
const validationResult = config.customValidator(paginationResult);
if (validationResult !== true) {
const errorMessage = typeof validationResult === 'string'
? validationResult
: 'Custom validation failed';
return handleInvalidQuery(config.onInvalidQuery, errorMessage, {
page: config.defaultPage,
limit: config.defaultLimit,
sortBy: config.defaultSortBy,
sortOrder: config.defaultSortOrder,
});
}
}
return paginationResult;
function handleInvalidQuery(onInvalidQuery, message, defaultValues) {
switch (onInvalidQuery) {
case 'throw':
throw new common_1.BadRequestException(message);
case 'default':
return {
...defaultValues,
offset: ((defaultValues.page || 1) - 1) * (defaultValues.limit || 10),
hasCustomLimits: false,
originalQuery: { ...query },
};
case 'ignore':
default:
return paginationResult;
}
}
})();
}
getOptions() {
return { ...this.options };
}
}
exports.PaginationBuilder = PaginationBuilder;
const createPagination = () => new PaginationBuilder();
exports.createPagination = createPagination;
exports.DomainPagination = {
Users: (0, exports.createPagination)()
.defaultLimit(20)
.maxLimit(100)
.allowedSortFields(['id', 'email', 'name', 'createdAt', 'lastLogin'])
.defaultSortBy('createdAt')
.defaultSortOrder(enums_1.SortOrder.DESC)
.build(),
Products: (0, exports.createPagination)()
.defaultLimit(12)
.maxLimit(48)
.allowedSortFields([
'id',
'name',
'price',
'category',
'rating',
'createdAt',
])
.defaultSortBy('name')
.paramNames({ sortBy: 'sort', sortOrder: 'order' })
.build(),
Posts: (0, exports.createPagination)()
.defaultLimit(10)
.maxLimit(50)
.allowedSortFields(['id', 'title', 'publishedAt', 'views', 'likes'])
.defaultSortBy('publishedAt')
.defaultSortOrder(enums_1.SortOrder.DESC)
.build(),
Comments: (0, exports.createPagination)()
.defaultLimit(25)
.maxLimit(100)
.allowedSortFields(['id', 'createdAt', 'likes'])
.defaultSortBy('createdAt')
.defaultSortOrder(enums_1.SortOrder.ASC)
.build(),
Admin: (0, exports.createPagination)()
.defaultLimit(50)
.maxLimit(500)
.allowUnlimited(true)
.validate(true)
.onInvalidQuery('throw')
.build(),
Mobile: (0, exports.createPagination)()
.defaultLimit(10)
.maxLimit(25)
.validate(true)
.onInvalidQuery('default')
.build(),
Search: (0, exports.createPagination)()
.defaultLimit(20)
.maxLimit(100)
.paramNames({ search: 'q', filters: 'f' })
.customValidator((query) => {
if (query.search && query.search.length < 2) {
return 'Search query must be at least 2 characters';
}
return true;
})
.build(),
};
exports.CurrentUser = (0, common_1.createParamDecorator)((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
});
exports.IpAddress = (0, common_1.createParamDecorator)((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
return (request.ip ||
request.connection.remoteAddress ||
request.socket.remoteAddress);
});
exports.UserAgent = (0, common_1.createParamDecorator)((data, ctx) => {
const request = ctx.switchToHttp().getRequest();
return request.headers['user-agent'] || '';
});
//# sourceMappingURL=parameter-extractors.js.map