import Joi from "joi"; import { ValueObject } from "@repo/rdx-ddd"; import { Result, RuleValidator } from "@repo/rdx-utils"; import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE, MAX_PAGE_SIZE, MIN_PAGE_INDEX, MIN_PAGE_SIZE, } from "./defaults"; export interface IOffsetPagingProps { offset: number | string | undefined; limit: number | string | undefined; } export interface IOffsetPaging { offset: number; limit: number; } export class OffsetPaging extends ValueObject { public static readonly LIMIT_DEFAULT_VALUE: number = INITIAL_PAGE_SIZE; public static readonly LIMIT_MINIMAL_VALUE: number = MIN_PAGE_SIZE; public static readonly LIMIT_MAXIMAL_VALUE: number = MAX_PAGE_SIZE; public static readonly OFFSET_DEFAULT_VALUE: number = INITIAL_PAGE_INDEX; public static readonly OFFSET_MINIMAL_VALUE: number = MIN_PAGE_INDEX; public static readonly OFFSET_MAXIMAL_VALUE: number = Number.MAX_SAFE_INTEGER; public static createWithMaxLimit(): Result { return OffsetPaging.create({ offset: OffsetPaging.OFFSET_DEFAULT_VALUE, limit: OffsetPaging.LIMIT_MAXIMAL_VALUE, }); } public static createWithDefaultValues(): Result { return OffsetPaging.create({ offset: OffsetPaging.OFFSET_DEFAULT_VALUE, limit: OffsetPaging.LIMIT_DEFAULT_VALUE, }); } public static create(paginationProps: IOffsetPagingProps) { const offset = paginationProps.offset || OffsetPaging.OFFSET_DEFAULT_VALUE; const limit = paginationProps.limit || OffsetPaging.LIMIT_DEFAULT_VALUE; const validatedProps = OffsetPaging.validate(offset, limit); if (validatedProps.isFailure) { return Result.fail(validatedProps.error); } const paging: IOffsetPaging = validatedProps.data as IOffsetPaging; return Result.ok(new OffsetPaging(paging)); } private static validate(offset: string | number, limit: string | number) { const numberOrError = RuleValidator.validate(RuleValidator.RULE_IS_TYPE_NUMBER, offset); if (numberOrError.isFailure) { return numberOrError; } const _offset = typeof offset === "string" ? Number.parseInt(offset, 10) : offset; const offsetValidate = RuleValidator.validate( Joi.number().min(OffsetPaging.OFFSET_MINIMAL_VALUE).max(OffsetPaging.OFFSET_MAXIMAL_VALUE), offset ); if (offsetValidate.isFailure) { return Result.fail( new Error(`Page need to be larger than or equal to ${OffsetPaging.OFFSET_MINIMAL_VALUE}.`) ); } // limit const limitNumberOrError = RuleValidator.validate(RuleValidator.RULE_IS_TYPE_NUMBER, limit); if (limitNumberOrError.isFailure) { return limitNumberOrError; } const _limit = typeof limit === "string" ? Number.parseInt(limit, 10) : limit; const limitValidate = RuleValidator.validate( Joi.number().min(0).max(OffsetPaging.LIMIT_MAXIMAL_VALUE), offset ); if (limitValidate.isFailure) { return Result.fail( new Error(`Page size need to be smaller than ${OffsetPaging.LIMIT_MAXIMAL_VALUE}`) ); } return Result.ok({ offset: _offset, limit: _limit, }); } private constructor(props: IOffsetPaging) { super(props); } getValue() { return this.props; } get offset(): number { return this.props.offset; } get limit(): number { return this.props.limit; } public next(): OffsetPaging { return new OffsetPaging({ offset: this.props.offset + this.props.limit, limit: this.props.limit, }); } public toJSON(): string { return JSON.stringify(this.toObject()); } public toPrimitive(): string { throw new Error("NOT IMPLEMENT OffsetPaging.toPrimitive()"); } public toObject(): Record { return { limit: this.props.limit, offset: this.props.offset, }; } }