From 19fb7c04e2891d37183857c10302458348aad67d Mon Sep 17 00:00:00 2001 From: david Date: Tue, 30 Sep 2025 12:59:32 +0200 Subject: [PATCH] Criteria -> quickSearch --- modules/core/src/api/helpers/index.ts | 2 +- .../core/src/api/helpers/sequelize-func.ts | 18 ++ .../express/api-error-mapper.ts | 19 +- .../express/errors/internal-api-error.ts | 4 +- .../express/errors/unavailable-api-error.ts | 4 +- .../sequelize/sequelize-error-translator.ts | 2 + modules/core/src/common/dto/critera.dto.ts | 2 + .../models/customer-invoice.model.ts | 7 + .../queries/list-customers.presenter.ts | 3 +- .../controllers/list-customers.controller.ts | 4 +- .../sequelize/models/customer.model.ts | 5 + .../repositories/customer.repository.ts | 12 +- package.json | 9 +- packages/rdx-criteria/readme.md | 10 +- packages/rdx-criteria/src/critera.ts | 14 +- .../src/criteria-from-url-converter.ts | 16 +- .../src/criteria-to-sequelize-converter.ts | 127 +++++++++--- packages/rdx-criteria/src/types.d.ts | 16 ++ packages/rdx-criteria/src/utils.ts | 29 +++ pnpm-lock.yaml | 188 ++++++++++++++++-- 20 files changed, 413 insertions(+), 78 deletions(-) create mode 100644 modules/core/src/api/helpers/sequelize-func.ts create mode 100644 packages/rdx-criteria/src/types.d.ts create mode 100644 packages/rdx-criteria/src/utils.ts diff --git a/modules/core/src/api/helpers/index.ts b/modules/core/src/api/helpers/index.ts index 8b137891..46e147c0 100644 --- a/modules/core/src/api/helpers/index.ts +++ b/modules/core/src/api/helpers/index.ts @@ -1 +1 @@ - +export * from "./sequelize-func"; diff --git a/modules/core/src/api/helpers/sequelize-func.ts b/modules/core/src/api/helpers/sequelize-func.ts new file mode 100644 index 00000000..c0f9cea3 --- /dev/null +++ b/modules/core/src/api/helpers/sequelize-func.ts @@ -0,0 +1,18 @@ +import { FindOptions } from "sequelize"; + +// orderItem puede ser: ['campo', 'ASC'|'DESC'] +// o [Sequelize.literal('score'), 'DESC'] +// o [[{ model: X, as: 'alias' }, 'campo', 'ASC']] etc. +type OrderItem = any; + +export function prependOrder(options: FindOptions, orderItem: OrderItem) { + if (!options.order) { + options.order = [orderItem]; + return; + } + // Si viene como algo no-array (poco común), lo envolvemos + if (!Array.isArray(options.order)) { + options.order = [options.order as any]; + } + (options.order as OrderItem[]).unshift(orderItem); +} diff --git a/modules/core/src/api/infrastructure/express/api-error-mapper.ts b/modules/core/src/api/infrastructure/express/api-error-mapper.ts index 24e00cda..d016c327 100644 --- a/modules/core/src/api/infrastructure/express/api-error-mapper.ts +++ b/modules/core/src/api/infrastructure/express/api-error-mapper.ts @@ -24,7 +24,12 @@ import { isDuplicateEntityError, isEntityNotFoundError, } from "../../domain"; -import { isInfrastructureRepositoryError, isInfrastructureUnavailableError } from "../errors"; +import { + InfrastructureRepositoryError, + InfrastructureUnavailableError, + isInfrastructureRepositoryError, + isInfrastructureUnavailableError, +} from "../errors"; import { ApiError, ConflictApiError, @@ -146,14 +151,22 @@ const defaultRules: ReadonlyArray = [ { priority: 60, matches: (e) => isInfrastructureUnavailableError(e), - build: () => new UnavailableApiError("Service temporarily unavailable."), + build: (e) => + new UnavailableApiError( + (e as InfrastructureUnavailableError).message, + "Service temporarily unavailable." + ), }, // 6) Infra no transitoria (errores de repositorio inesperados) { priority: 50, matches: (e) => isInfrastructureRepositoryError(e), - build: () => new InternalApiError("Unexpected repository error."), + build: (e) => + new InternalApiError( + (e as InfrastructureRepositoryError).message, + "Unexpected repository error" + ), }, // 7) Autenticación/autorización por nombre (si no tienes clases dedicadas) diff --git a/modules/core/src/api/infrastructure/express/errors/internal-api-error.ts b/modules/core/src/api/infrastructure/express/errors/internal-api-error.ts index 8570f40a..acf69080 100644 --- a/modules/core/src/api/infrastructure/express/errors/internal-api-error.ts +++ b/modules/core/src/api/infrastructure/express/errors/internal-api-error.ts @@ -1,10 +1,10 @@ import { ApiError } from "./api-error"; export class InternalApiError extends ApiError { - constructor(detail: string) { + constructor(detail: string, title?: string) { super({ status: 500, - title: "Internal Server Error", + title: title ?? "Internal Server Error", detail, type: "https://httpstatuses.com/500", }); diff --git a/modules/core/src/api/infrastructure/express/errors/unavailable-api-error.ts b/modules/core/src/api/infrastructure/express/errors/unavailable-api-error.ts index 6aac3098..50df2325 100644 --- a/modules/core/src/api/infrastructure/express/errors/unavailable-api-error.ts +++ b/modules/core/src/api/infrastructure/express/errors/unavailable-api-error.ts @@ -1,10 +1,10 @@ import { ApiError } from "./api-error"; export class UnavailableApiError extends ApiError { - constructor(detail: string) { + constructor(detail: string, title?: string) { super({ status: 503, - title: "Service Unavailable", + title: title ?? "Service Unavailable", detail, type: "https://httpstatuses.com/503", }); diff --git a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts index c272947d..f995aa6e 100644 --- a/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts +++ b/modules/core/src/api/infrastructure/sequelize/sequelize-error-translator.ts @@ -17,6 +17,8 @@ import { InfrastructureUnavailableError } from "../errors/infrastructure-unavail * 👉 Este traductor pertenece a la infraestructura (persistencia) */ export function translateSequelizeError(err: unknown): Error { + console.log(err); + // 1) Duplicados (índices únicos) if (err instanceof UniqueConstraintError) { // Tomamos el primer detalle (puede haber varios) diff --git a/modules/core/src/common/dto/critera.dto.ts b/modules/core/src/common/dto/critera.dto.ts index f5f33047..34dc6e4a 100644 --- a/modules/core/src/common/dto/critera.dto.ts +++ b/modules/core/src/common/dto/critera.dto.ts @@ -12,6 +12,8 @@ export const FilterPrimitiveSchema = z.object({ }); export const CriteriaSchema = z.object({ + q: z.string().optional(), + filters: z.array(FilterPrimitiveSchema).optional(), // Preferimos omitido; si viene, no puede ser cadena vacía diff --git a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts index 220b1f1f..4bcf5a6d 100644 --- a/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts +++ b/modules/customer-invoices/src/api/infrastructure/sequelize/models/customer-invoice.model.ts @@ -43,6 +43,7 @@ export class CustomerInvoiceModel extends Model< declare currency_code: CreationOptional; declare reference: CreationOptional; + declare description: CreationOptional; declare notes: CreationOptional; @@ -192,6 +193,12 @@ export default (database: Sequelize) => { defaultValue: null, }, + description: { + type: new DataTypes.STRING(), + allowNull: true, + defaultValue: null, + }, + notes: { type: new DataTypes.TEXT(), allowNull: true, diff --git a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts index 8e5b89ab..4abba009 100644 --- a/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts +++ b/modules/customers/src/api/application/presenters/queries/list-customers.presenter.ts @@ -1,3 +1,4 @@ +import { CriteriaDTO } from "@erp/core"; import { Presenter } from "@erp/core/api"; import { CustomerListDTO } from "@erp/customer-invoices/api/infrastructure"; import { Criteria } from "@repo/rdx-criteria/server"; @@ -68,7 +69,7 @@ export class ListCustomersPresenter extends Presenter { items: items, metadata: { entity: "customers", - criteria: criteria.toJSON(), + criteria: criteria.toJSON() as CriteriaDTO, //links: { // self: `/api/customer-invoices?page=${criteria.pageNumber}&per_page=${criteria.pageSize}`, // first: `/api/customer-invoices?page=1&per_page=${criteria.pageSize}`, diff --git a/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts b/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts index 20e9c4da..a9a9eb70 100644 --- a/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts +++ b/modules/customers/src/api/infrastructure/express/controllers/list-customers.controller.ts @@ -14,8 +14,8 @@ export class ListCustomersController extends ExpressController { return this.criteria; } - const { filters, pageSize, pageNumber } = this.criteria.toPrimitives(); - return Criteria.fromPrimitives(filters, "name", "ASC", pageSize, pageNumber); + const { q: quicksearch, filters, pageSize, pageNumber } = this.criteria.toPrimitives(); + return Criteria.fromPrimitives(filters, "name", "ASC", pageSize, pageNumber, quicksearch); } protected async executeImpl() { diff --git a/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts b/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts index 42e8137d..8c930612 100644 --- a/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts +++ b/modules/customers/src/api/infrastructure/sequelize/models/customer.model.ts @@ -233,6 +233,11 @@ export default (database: Sequelize) => { indexes: [ { name: "company_idx", fields: ["company_id"], unique: false }, { name: "idx_company_idx", fields: ["id", "company_id"], unique: true }, + { + name: "ft_customer", + type: "FULLTEXT", + fields: ["name", "trade_name", "reference", "tin", "email_primary", "mobile_primary"], + }, ], whereMergeStrategy: "and", // <- cómo tratar el merge de un scope diff --git a/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts b/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts index 1b9738cf..b3f98469 100644 --- a/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts +++ b/modules/customers/src/api/infrastructure/sequelize/repositories/customer.repository.ts @@ -121,7 +121,17 @@ export class CustomerRepository }); const converter = new CriteriaToSequelizeConverter(); - const query = converter.convert(criteria, { name: "name" }); + const query = converter.convert(criteria, { + searchableFields: [ + "name", + "trade_name", + "reference", + "tin", + "email_primary", + "mobile_primary", + ], + database: this._database, + }); query.where = { ...query.where, diff --git a/package.json b/package.json index bf22a327..48bca0c7 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,7 @@ { "name": "uecko-erp-2025", "private": true, - "workspaces": [ - "apps/*", - "modules/*", - "packages/*" - ], + "workspaces": ["apps/*", "modules/*", "packages/*"], "scripts": { "build": "turbo build", "dev": "turbo dev", @@ -21,9 +17,12 @@ "devDependencies": { "@biomejs/biome": "1.9.4", "@repo/typescript-config": "workspace:*", + "@types/jest": "^29.5.14", "change-case": "^5.4.4", "inquirer": "^12.5.2", + "jest": "^29.7.0", "plop": "^4.0.4", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "turbo": "^2.5.1", "typescript": "5.8.3" diff --git a/packages/rdx-criteria/readme.md b/packages/rdx-criteria/readme.md index 2511ef67..35bcce2d 100644 --- a/packages/rdx-criteria/readme.md +++ b/packages/rdx-criteria/readme.md @@ -11,10 +11,12 @@ The criteria converter expect an url with the following format: * `pageNumber`: The page number. ### Valid operators -* `=`: Equal -* `!=`: Not equal -* `>`: Greater than -* `<`: Less than +* `EQUALS`: Equal +* `NOT_EQUALS`: Not equal +* `GREATER_THAN`: Greater than +* `GREATER_THAN_OR_EQUAL`: Greater than or equal +* `LOWER_THAN`: Less than +* `LOWER_THAN_OR_EQUAL`: Less than or equal * `CONTAINS`: Contains. It will translate to `like` in SQL. * `NOT_CONTAINS`: Not contains. It will translate to `not like` in SQL. diff --git a/packages/rdx-criteria/src/critera.ts b/packages/rdx-criteria/src/critera.ts index c2c5f11d..9a762a1c 100644 --- a/packages/rdx-criteria/src/critera.ts +++ b/packages/rdx-criteria/src/critera.ts @@ -5,17 +5,20 @@ export class Criteria extends BaseCriteria { /** * Creates a new Criteria instance. * + * @param quickSearch - 'Quicksearch' field. * @param filters - The filters to apply. * @param order - The order to apply. * @param pageSize - The size of the page. * @param pageNumber - The number of the page. + * */ - constructor( + protected constructor( public readonly filters: Filters, public readonly order: Order, public readonly pageSize: number, - public readonly pageNumber: number + public readonly pageNumber: number, + public readonly quickSearch?: string //Texto libre para búsqueda rápida ) { super(filters, order, pageSize, pageNumber); } @@ -25,18 +28,21 @@ export class Criteria extends BaseCriteria { orderBy: string | null, orderType: string | null, pageSize: number | null, - pageNumber: number | null + pageNumber: number | null, + quickSearch?: string | null ): Criteria { return new Criteria( Filters.fromPrimitives(filters), Order.fromPrimitives(orderBy, orderType), pageSize ?? INITIAL_PAGE_SIZE, - pageNumber ?? INITIAL_PAGE_INDEX + pageNumber ?? INITIAL_PAGE_INDEX, + quickSearch ?? undefined ); } toPrimitives() { return { + q: this.quickSearch ?? "", filters: this.filters.toPrimitives(), orderBy: this.order.orderBy.value, orderType: this.order.orderType.value, diff --git a/packages/rdx-criteria/src/criteria-from-url-converter.ts b/packages/rdx-criteria/src/criteria-from-url-converter.ts index 3521022a..01514959 100644 --- a/packages/rdx-criteria/src/criteria-from-url-converter.ts +++ b/packages/rdx-criteria/src/criteria-from-url-converter.ts @@ -26,23 +26,25 @@ export class CriteriaFromUrlConverter { defaults: defaultsType; } ): Criteria { - const { searchParams } = url; + const { searchParams: urlParams } = url; const defaults = { order: options?.defaults?.order ?? defaultOptions.order, pageSize: options?.defaults?.pageSize ?? defaultOptions.pageSize, pageNumber: options?.defaults?.pageNumber ?? defaultOptions.pageNumber, }; - const filters = this.parseFilters(searchParams); + const quickSearch = urlParams.get("q"); - const orderBy = searchParams.get("orderBy"); + const filters = this.parseFilters(urlParams); - const order = searchParams.has("order") ? String(searchParams.get("order")) : defaults.order; + const orderBy = urlParams.get("orderBy"); - const pageSize = this.parseInteger(searchParams.get("pageSize"), defaults.pageSize); - const pageNumber = this.parseInteger(searchParams.get("pageNumber"), defaults.pageNumber); + const order = urlParams.has("order") ? String(urlParams.get("order")) : defaults.order; - return Criteria.fromPrimitives(filters, orderBy, order, pageSize, pageNumber); + const pageSize = this.parseInteger(urlParams.get("pageSize"), defaults.pageSize); + const pageNumber = this.parseInteger(urlParams.get("pageNumber"), defaults.pageNumber); + + return Criteria.fromPrimitives(filters, orderBy, order, pageSize, pageNumber, quickSearch); } public toFiltersPrimitives(url: URL): FiltersPrimitives[] { diff --git a/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts b/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts index e4044a0a..5c2582d8 100644 --- a/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts +++ b/packages/rdx-criteria/src/criteria-to-sequelize-converter.ts @@ -1,32 +1,115 @@ -import { Filter } from "@codelytv/criteria"; -import { FindOptions, Op, OrderItem, WhereOptions } from "sequelize"; +import { FindOptions, Op, OrderItem, Sequelize, WhereOptions } from "sequelize"; import { Criteria } from "./critera"; +import { type ConvertParams, type CriteriaMappings, ICriteriaToOrmConverter } from "./types"; +import { appendOrder, prependOrder } from "./utils"; -type CriteriaMappings = { [key: string]: string }; - -export class CriteriaToSequelizeConverter { +export class CriteriaToSequelizeConverter implements ICriteriaToOrmConverter { //convert(fieldsToSelect: string[], criteria: Criteria, mappings: Mappings = {}): FindOptions { - convert(criteria: Criteria, mappings: CriteriaMappings = {}): FindOptions { + convert(criteria: Criteria, params: ConvertParams = {}): FindOptions { const options: FindOptions = {}; + const { mappings = {} } = params; // Selección de campos /*if (fieldsToSelect.length > 0) { options.attributes = fieldsToSelect; }*/ - // Filtros + this.applyFilters(options, criteria, mappings); + this.applyQuickSearch(options, criteria, params); + this.applyOrder(options, criteria, mappings); + this.applyPagination(options, criteria); + + return options; + } + + public applyFilters(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings) { + const filterConditions: WhereOptions = {}; if (criteria.hasFilters()) { - options.where = this.buildWhere(criteria.filters.value, mappings); + criteria.filters.value.forEach((filter) => { + const field = mappings[filter.field.value] || filter.field.value; + const operator = this.mapOperator(filter.operator.value); + const value = filter.value.value; + + if (!filterConditions[field]) { + filterConditions[field] = {}; + } + + filterConditions[field][operator] = this.transformValue(operator, value); + }); + + if (options.where) { + options.where = { [Op.and]: [options.where, { [Op.or]: filterConditions }] }; + } else { + options.where = { [Op.or]: filterConditions }; + } + } + } + + public applyQuickSearch(options: FindOptions, criteria: Criteria, params: ConvertParams): void { + const { + mappings = {}, + searchableFields = [], + database, + } = params as ConvertParams & { + database: Sequelize; + }; + + const term = typeof criteria.quickSearch === "string" ? criteria.quickSearch.trim() : ""; + + // Si no hay término de búsqueda o no hay campos configurados, no hacemos nada + if (term === "" || searchableFields.length === 0) { + return; } - // Orden + // Construimos query de boolean mode con prefijo + const booleanTerm = term + .split(/\s+/) + .map((w) => `+${w}*`) + .join(" "); + + // Campos reales (con mappings aplicados) + const mappedFields = searchableFields.map((field) => mappings[field] || field); + + const matchExpr = `MATCH(${mappedFields.join(", ")}) AGAINST (${database.escape( + booleanTerm + )} IN BOOLEAN MODE)`; + + const matchLiteral = Sequelize.literal(matchExpr); + + // Añadimos score a los attributes (sin machacar si ya existen) + if (!options.attributes) { + options.attributes = { include: [] }; + } + + if (Array.isArray(options.attributes)) { + options.attributes.push([matchLiteral, "score"]); + } else { + options.attributes.include = options.attributes.include || []; + options.attributes.include.push([matchLiteral, "score"]); + } + + // WHERE score > 0 + const scoreCondition = Sequelize.where(matchLiteral, { [Op.gt]: 0 }); + if (options.where) { + options.where = { [Op.and]: [options.where, { [Op.or]: scoreCondition }] }; + } else { + options.where = { [Op.and]: scoreCondition }; + } + + // Ordenar por relevancia (score) + prependOrder(options, [Sequelize.literal("score"), "DESC"]); + } + + public applyOrder(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings): void { if (criteria.hasOrder()) { const field = mappings[criteria.order.orderBy.value] || criteria.order.orderBy.value; const direction = criteria.order.orderType.value.toUpperCase(); - options.order = [[field, direction]] as OrderItem[]; - } - // Paginación + appendOrder(options, [[field, direction]] as OrderItem[]); + } + } + + public applyPagination(options: FindOptions, criteria: Criteria): void { if (criteria.pageSize !== null) { options.limit = criteria.pageSize; } @@ -34,26 +117,6 @@ export class CriteriaToSequelizeConverter { if (criteria.pageSize !== null && criteria.pageNumber !== null) { options.offset = criteria.pageSize * criteria.pageNumber; } - - return options; - } - - private buildWhere(filters: Filter[], mappings: CriteriaMappings): WhereOptions { - const where: WhereOptions = {}; - - filters.forEach((filter) => { - const field = mappings[filter.field.value] || filter.field.value; - const operator = this.mapOperator(filter.operator.value); - const value = filter.value.value; - - if (!where[field]) { - where[field] = {}; - } - - where[field][operator] = this.transformValue(operator, value); - }); - - return where; } private mapOperator(operator: string): symbol { diff --git a/packages/rdx-criteria/src/types.d.ts b/packages/rdx-criteria/src/types.d.ts new file mode 100644 index 00000000..e44e729d --- /dev/null +++ b/packages/rdx-criteria/src/types.d.ts @@ -0,0 +1,16 @@ +import { FindOptions } from "sequelize"; +import { Criteria } from "./critera"; + +export type CriteriaMappings = { [key: string]: string }; +export type ConvertParams = { mappings?: CriteriaMappings; searchableFields?: string[] } & Record< + string, + unknown +>; + +export interface ICriteriaToOrmConverter { + convert(criteria: Criteria, params: ConvertParams): FindOptions; + applyFilters(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings): void; + applyQuickSearch(options: FindOptions, criteria: Criteria, params: ConvertParams): void; + applyOrder(options: FindOptions, criteria: Criteria, mappings: CriteriaMappings): void; + applyPagination(options: FindOptions, criteria: Criteria): void; +} diff --git a/packages/rdx-criteria/src/utils.ts b/packages/rdx-criteria/src/utils.ts new file mode 100644 index 00000000..ced92e0a --- /dev/null +++ b/packages/rdx-criteria/src/utils.ts @@ -0,0 +1,29 @@ +import { FindOptions } from "sequelize"; + +// orderItem puede ser: ['campo', 'ASC'|'DESC'] +// o [Sequelize.literal('score'), 'DESC'] +// o [[{ model: X, as: 'alias' }, 'campo', 'ASC']] etc. +type OrderItem = any; + +export function prependOrder(options: FindOptions, orderItem: OrderItem) { + if (!options.order) { + options.order = [orderItem]; + return; + } + // Si viene como algo no-array (poco común), lo envolvemos + if (!Array.isArray(options.order)) { + options.order = [options.order as any]; + } + (options.order as OrderItem[]).unshift(orderItem); +} + +export function appendOrder(options: FindOptions, orderItem: OrderItem) { + if (!options.order) { + options.order = [orderItem]; + return; + } + if (!Array.isArray(options.order)) { + options.order = [options.order as any]; + } + (options.order as OrderItem[]).push(orderItem); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e3dd562..c356a5ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,15 +14,24 @@ importers: '@repo/typescript-config': specifier: workspace:* version: link:packages/typescript-config + '@types/jest': + specifier: ^29.5.14 + version: 29.5.14 change-case: specifier: ^5.4.4 version: 5.4.4 inquirer: specifier: ^12.5.2 version: 12.6.3(@types/node@24.0.3) + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) plop: specifier: ^4.0.4 version: 4.0.4(@types/node@24.0.3) + ts-jest: + specifier: ^29.2.5 + version: 29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)))(typescript@5.8.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@24.0.3)(typescript@5.8.3) @@ -1154,10 +1163,6 @@ packages: resolution: {integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==} engines: {node: '>=6.9.0'} - '@babel/generator@7.27.3': - resolution: {integrity: sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.27.5': resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} engines: {node: '>=6.9.0'} @@ -7085,14 +7090,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.27.3': - dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.3 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - '@babel/generator@7.27.5': dependencies: '@babel/parser': 7.27.5 @@ -7760,6 +7757,41 @@ snapshots: - supports-color - ts-node + '@jest/core@29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.32 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 @@ -9874,6 +9906,21 @@ snapshots: - supports-color - ts-node + create-jest@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -10976,6 +11023,25 @@ snapshots: - supports-color - ts-node + jest-cli@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jest-config@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3)): dependencies: '@babel/core': 7.27.4 @@ -11007,6 +11073,68 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@22.15.32)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.27.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.15.32 + ts-node: 10.9.2(@types/node@24.0.3)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)): + dependencies: + '@babel/core': 7.27.4 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.0.3 + ts-node: 10.9.2(@types/node@24.0.3)(typescript@5.8.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@29.7.0: dependencies: chalk: 4.1.2 @@ -11164,10 +11292,10 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.27.4 - '@babel/generator': 7.27.3 + '@babel/generator': 7.27.5 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.27.4) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4) - '@babel/types': 7.27.3 + '@babel/types': 7.27.6 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 @@ -11234,6 +11362,18 @@ snapshots: - supports-color - ts-node + jest@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)): + dependencies: + '@jest/core': 29.7.0(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + jiti@2.4.2: {} joi@17.13.3: @@ -12859,6 +12999,26 @@ snapshots: esbuild: 0.25.5 jest-util: 29.7.0 + ts-jest@29.4.0(@babel/core@7.27.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.27.4))(jest-util@29.7.0)(jest@29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)))(typescript@5.8.3): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@24.0.3)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@24.0.3)(typescript@5.8.3)) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.8.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.27.4 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.4) + jest-util: 29.7.0 + ts-node@10.9.2(@types/node@22.15.32)(typescript@5.8.3): dependencies: '@cspotcode/source-map-support': 0.8.1