UNPKG

@kitstack/nest-powertools

Version:

A comprehensive collection of NestJS powertools, decorators, and utilities to supercharge your backend development

583 lines 22.7 kB
"use strict"; 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