From 01d0ff67f8157e10c2cb416d28b236642655dac4 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 24 Aug 2026 23:43:00 +0200 Subject: [PATCH] Audit --- apps/server/package.json | 1 + apps/server/src/app.ts | 14 +- apps/server/src/index.ts | 37 +- .../audit/sequelize/audit-change.model.ts | 81 + .../audit/sequelize/audit-event.model.ts | 95 + .../infrastructure/audit/sequelize/index.ts | 10 + .../sequelize/sequelize-audit-entry.mapper.ts | 68 + .../sequelize/sequelize-audit-store.test.ts | 136 ++ .../audit/sequelize/sequelize-audit-store.ts | 43 + apps/server/src/lib/modules/model-loader.ts | 9 +- .../src/lib/modules/module-loader.test.ts | 16 +- .../http/audit-runtime.integration.test.ts | 298 +++ .../src/testing/http/http-test-runtime.ts | 51 +- .../sequelize-audit-store.integration.test.ts | 161 ++ .../Sistema-transversal-de-auditoría-erp.md | 769 +++++++ .../payment-methods/di/payment-methods.di.ts | 4 +- .../payment-terms/di/payment-terms.di.ts | 4 +- .../tax-definitions/di/tax-definitions.di.ts | 4 +- .../tax-regimes/di/tax-regimes.di.ts | 4 +- .../src/api/infrastructure/di/companies.di.ts | 4 +- modules/core/package.json | 5 +- .../src/api/infrastructure/context/index.ts | 3 + .../operation-audit-context-provider.test.ts | 55 + .../operation-audit-context-provider.ts | 23 + .../context/operation-context-store.test.ts | 94 + .../context/operation-context-store.ts | 128 ++ .../context/operation-context.types.ts | 27 + .../api/infrastructure/di/transactions.di.ts | 7 +- .../src/api/infrastructure/errors/index.ts | 1 + .../nested-transaction-not-allowed-error.ts | 7 + .../express/middlewares/index.ts | 1 + .../operation-context.middleware.test.ts | 73 + .../operation-context.middleware.ts | 62 + modules/core/src/api/infrastructure/index.ts | 1 + .../persistence/sequelize/index.ts | 1 + .../sequelize-transaction-context.test.ts | 136 ++ .../sequelize-transaction-context.ts | 21 + .../sequelize-transaction-manager.ts | 41 +- modules/core/src/api/modules/types.ts | 13 + .../di/company-verifactu-profile.di.ts | 2 +- .../issued-invoices/di/issued-invoices.di.ts | 4 +- .../proformas/di/proformas.di.ts | 4 +- .../src/api/infrastructure/di/customers.di.ts | 4 +- .../infrastructure/di/document-series.di.ts | 4 +- .../express/document-series.routes.ts | 4 +- .../src/api/infraestructure/di/factuges.di.ts | 4 +- ...identity-auth-public-services.interface.ts | 6 +- .../di/identity-public-services.ts | 12 +- .../src/api/infrastructure/di/identity.di.ts | 4 +- .../authenticate-user.middleware.test.ts | 60 + .../authenticate-user.middleware.ts | 8 + .../identity-authenticate-request.ts | 4 + ...require-company-context.middleware.test.ts | 88 + .../require-company-context.middleware.ts | 56 +- .../require-identity-middlewares.ts | 9 +- .../src/api/infrastructure/di/suppliers.di.ts | 4 +- packages/rdx-audit/package.json | 27 + packages/rdx-audit/src/audit-state.types.ts | 11 + packages/rdx-audit/src/audit.test.ts | 173 ++ packages/rdx-audit/src/audit.ts | 120 + packages/rdx-audit/src/audit.types.ts | 114 + packages/rdx-audit/src/errors/audit-error.ts | 18 + packages/rdx-audit/src/errors/index.ts | 1 + packages/rdx-audit/src/index.ts | 31 + .../rdx-audit/src/internal/audit-diff.test.ts | 89 + packages/rdx-audit/src/internal/audit-diff.ts | 95 + .../src/internal/audit-input-validator.ts | 85 + .../internal/audit-state-validator.test.ts | 62 + .../src/internal/audit-state-validator.ts | 57 + packages/rdx-audit/tsconfig.json | 9 + pnpm-lock.yaml | 1936 +++++++++++------ 71 files changed, 4815 insertions(+), 798 deletions(-) create mode 100644 apps/server/src/infrastructure/audit/sequelize/audit-change.model.ts create mode 100644 apps/server/src/infrastructure/audit/sequelize/audit-event.model.ts create mode 100644 apps/server/src/infrastructure/audit/sequelize/index.ts create mode 100644 apps/server/src/infrastructure/audit/sequelize/sequelize-audit-entry.mapper.ts create mode 100644 apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.test.ts create mode 100644 apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.ts create mode 100644 apps/server/src/testing/http/audit-runtime.integration.test.ts create mode 100644 apps/server/src/testing/http/sequelize-audit-store.integration.test.ts create mode 100644 docs/adit/Sistema-transversal-de-auditoría-erp.md create mode 100644 modules/core/src/api/infrastructure/context/index.ts create mode 100644 modules/core/src/api/infrastructure/context/operation-audit-context-provider.test.ts create mode 100644 modules/core/src/api/infrastructure/context/operation-audit-context-provider.ts create mode 100644 modules/core/src/api/infrastructure/context/operation-context-store.test.ts create mode 100644 modules/core/src/api/infrastructure/context/operation-context-store.ts create mode 100644 modules/core/src/api/infrastructure/context/operation-context.types.ts create mode 100644 modules/core/src/api/infrastructure/errors/nested-transaction-not-allowed-error.ts create mode 100644 modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.test.ts create mode 100644 modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.ts create mode 100644 modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.test.ts create mode 100644 modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.ts create mode 100644 modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.test.ts create mode 100644 modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.test.ts create mode 100644 packages/rdx-audit/package.json create mode 100644 packages/rdx-audit/src/audit-state.types.ts create mode 100644 packages/rdx-audit/src/audit.test.ts create mode 100644 packages/rdx-audit/src/audit.ts create mode 100644 packages/rdx-audit/src/audit.types.ts create mode 100644 packages/rdx-audit/src/errors/audit-error.ts create mode 100644 packages/rdx-audit/src/errors/index.ts create mode 100644 packages/rdx-audit/src/index.ts create mode 100644 packages/rdx-audit/src/internal/audit-diff.test.ts create mode 100644 packages/rdx-audit/src/internal/audit-diff.ts create mode 100644 packages/rdx-audit/src/internal/audit-input-validator.ts create mode 100644 packages/rdx-audit/src/internal/audit-state-validator.test.ts create mode 100644 packages/rdx-audit/src/internal/audit-state-validator.ts create mode 100644 packages/rdx-audit/tsconfig.json diff --git a/apps/server/package.json b/apps/server/package.json index 383a4f75..2a9a3dfe 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -47,6 +47,7 @@ "@erp/factuges": "workspace:*", "@erp/suppliers": "workspace:*", "@repo/rdx-logger": "workspace:*", + "@repo/rdx-audit": "workspace:*", "@repo/rdx-utils": "workspace:*", "bcrypt": "^6.0.0", "cls-rtracer": "^2.6.3", diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 2455523a..27ceb51e 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -3,6 +3,8 @@ import express, { type Application } from "express"; import helmet from "helmet"; import responseTime from "response-time"; +import { createOperationContextMiddleware, type IOperationContextStore } from "@erp/core/api"; + import type { ConfigType } from "./config"; // ❗️ No cargamos dotenv aquí. Debe hacerse en el entrypoint o en ./config. @@ -10,7 +12,10 @@ import type { ConfigType } from "./config"; import { logger } from "./lib/logger"; -export function createApp(config: ConfigType): Application { +export function createApp( + config: ConfigType, + operationContext: IOperationContextStore +): Application { const app = express(); // ─────────────────────────────────────────────────────────────────────────── @@ -28,6 +33,8 @@ export function createApp(config: ConfigType): Application { "Pagination-Count", "Pagination-Page", "Pagination-Limit", + "X-Request-Id", + "X-Correlation-Id", ], }; @@ -43,9 +50,14 @@ export function createApp(config: ConfigType): Application { "Pagination-Count", "Pagination-Page", "Pagination-Limit", + "X-Request-Id", + "X-Correlation-Id", ], }; + // Se instala antes de cualquier middleware que pueda necesitar sus identificadores. + app.use(createOperationContextMiddleware(operationContext)); + app.use(cors(config.flags.isDev ? devCors : prodCors)); app.options("*", cors(config.flags.isDev ? devCors : prodCors)); diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 150ea931..8d2ba89c 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -1,6 +1,12 @@ import http from "node:http"; import os from "node:os"; +import { + AsyncLocalStorageOperationContextStore, + OperationAuditContextProvider, + SequelizeTransactionContext, +} from "@erp/core/api"; +import { Audit } from "@repo/rdx-audit"; import { DateTime } from "luxon"; import { z } from "zod/v4"; @@ -8,13 +14,23 @@ import { createApp } from "./app.ts"; import { initConfig } from "./config"; import { tryConnectToDatabase } from "./config/database.ts"; import { registerHealthRoutes } from "./health.ts"; +import { + AuditChangeModel, + AuditEventModel, + SequelizeAuditStore, + auditModelInitializers, +} from "./infrastructure/audit/sequelize"; import { listRoutes, logger } from "./lib/index.ts"; -import { initModules } from "./lib/modules/index.ts"; +import { initModules, registerModels } from "./lib/modules/index.ts"; import { registerModules } from "./register-modules.ts"; z.config(z.locales.es()); const config = initConfig(logger); +const operationContext = new AsyncLocalStorageOperationContextStore(); +const transactionContext = new SequelizeTransactionContext(); +const auditContextProvider = new OperationAuditContextProvider(operationContext); +const AUDIT_SOURCE = { type: "application", id: "uecko-erp" } as const; // Guardamos información del estado del servidor export const currentState = { @@ -118,7 +134,7 @@ const serverConnection = (conn: any) => { // Cargar paquetes de la aplicación: customers, invoices, routes... registerModules(); -const app = createApp(config); +const app = createApp(config, operationContext); // Crea el servidor HTTP const server = http.createServer(app); @@ -217,12 +233,29 @@ process.on("uncaughtException", async (error: Error) => { // ➕ Rutas de salud disponibles desde el inicio del proceso registerHealthRoutes(app, config.server.apiBasePath, () => ({ ready: isReady })); + registerModels(auditModelInitializers, undefined, { moduleName: "server-infrastructure" }); + const auditStore = new SequelizeAuditStore( + AuditEventModel, + AuditChangeModel, + transactionContext + ); + const audit = new Audit({ + source: AUDIT_SOURCE, + contextProvider: auditContextProvider, + store: auditStore, + }); await initModules({ config, app, database, logger, + operationContext, + transactionContext, + audit, + getService: () => { + throw new Error("Services are only available from a module-scoped context."); + }, }); // El servidor ya está listo para recibir tráfico diff --git a/apps/server/src/infrastructure/audit/sequelize/audit-change.model.ts b/apps/server/src/infrastructure/audit/sequelize/audit-change.model.ts new file mode 100644 index 00000000..475b8529 --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/audit-change.model.ts @@ -0,0 +1,81 @@ +import { + DataTypes, + type InferAttributes, + type InferCreationAttributes, + Model, + type Sequelize, +} from "sequelize"; + +import { AuditEventModel } from "./audit-event.model"; + +/** Cambios ordenados de una mutación; no tienen identificador global propio. */ +export class AuditChangeModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare audit_event_id: string; + declare position: number; + declare path: string; + declare kind: string; + declare before_value: unknown | null; + declare after_value: unknown | null; + + static associate(_database: Sequelize): void { + AuditChangeModel.belongsTo(AuditEventModel, { + as: "event", + foreignKey: "audit_event_id", + targetKey: "id", + }); + } +} + +export function initializeAuditChangeModel(database: Sequelize): typeof AuditChangeModel { + AuditChangeModel.init( + { + audit_event_id: { + type: DataTypes.STRING(64), + allowNull: false, + primaryKey: true, + references: { model: "audit_events", key: "id" }, + onUpdate: "RESTRICT", + onDelete: "CASCADE", + }, + position: { type: DataTypes.INTEGER.UNSIGNED, allowNull: false, primaryKey: true }, + path: { type: DataTypes.STRING(255), allowNull: false }, + kind: { type: DataTypes.STRING(32), allowNull: false }, + before_value: { + type: DataTypes.JSON, + allowNull: true, + defaultValue: null, + // MariaDB devuelve JSON como texto; el modelo mantiene el contrato JSON al leerlo. + get(this: AuditChangeModel): unknown | null { + return deserializeJsonValue(this.getDataValue("before_value")); + }, + }, + after_value: { + type: DataTypes.JSON, + allowNull: true, + defaultValue: null, + // MariaDB devuelve JSON como texto; el modelo mantiene el contrato JSON al leerlo. + get(this: AuditChangeModel): unknown | null { + return deserializeJsonValue(this.getDataValue("after_value")); + }, + }, + }, + { + sequelize: database, + modelName: "AuditChangeModel", + tableName: "audit_changes", + charset: "utf8mb4", + collate: "utf8mb4_unicode_ci", + underscored: true, + timestamps: false, + } + ); + + return AuditChangeModel; +} + +function deserializeJsonValue(value: unknown): unknown | null { + return typeof value === "string" ? JSON.parse(value) : value; +} diff --git a/apps/server/src/infrastructure/audit/sequelize/audit-event.model.ts b/apps/server/src/infrastructure/audit/sequelize/audit-event.model.ts new file mode 100644 index 00000000..c843f424 --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/audit-event.model.ts @@ -0,0 +1,95 @@ +import { + DataTypes, + type InferAttributes, + type InferCreationAttributes, + Model, + type Sequelize, +} from "sequelize"; + +import { AuditChangeModel } from "./audit-change.model"; + +/** Modelo append-only; las referencias externas se almacenan como identificadores opacos. */ +export class AuditEventModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: string; + declare kind: string; + declare occurred_at: Date; + declare source_type: string; + declare source_id: string; + declare operation_id: string; + declare correlation_id: string; + declare origin: string; + declare actor_type: string; + declare actor_id: string | null; + declare actor_label: string | null; + declare scope_type: string | null; + declare scope_id: string | null; + declare scope_label: string | null; + declare action: string; + declare target_type: string; + declare target_id: string | null; + declare target_label: string | null; + + static associate(_database: Sequelize): void { + AuditEventModel.hasMany(AuditChangeModel, { + as: "changes", + foreignKey: "audit_event_id", + sourceKey: "id", + }); + } +} + +export function initializeAuditEventModel(database: Sequelize): typeof AuditEventModel { + AuditEventModel.init( + { + id: { type: DataTypes.STRING(64), allowNull: false, primaryKey: true }, + kind: { type: DataTypes.STRING(32), allowNull: false }, + occurred_at: { type: DataTypes.DATE(3), allowNull: false }, + source_type: { type: DataTypes.STRING(64), allowNull: false }, + source_id: { type: DataTypes.STRING(128), allowNull: false }, + operation_id: { type: DataTypes.STRING(128), allowNull: false }, + correlation_id: { type: DataTypes.STRING(128), allowNull: false }, + origin: { type: DataTypes.STRING(64), allowNull: false }, + actor_type: { type: DataTypes.STRING(64), allowNull: false }, + actor_id: { type: DataTypes.STRING(128), allowNull: true, defaultValue: null }, + actor_label: { type: DataTypes.STRING(255), allowNull: true, defaultValue: null }, + scope_type: { type: DataTypes.STRING(64), allowNull: true, defaultValue: null }, + scope_id: { type: DataTypes.STRING(128), allowNull: true, defaultValue: null }, + scope_label: { type: DataTypes.STRING(255), allowNull: true, defaultValue: null }, + action: { type: DataTypes.STRING(191), allowNull: false }, + target_type: { type: DataTypes.STRING(64), allowNull: false }, + target_id: { type: DataTypes.STRING(128), allowNull: true, defaultValue: null }, + target_label: { type: DataTypes.STRING(255), allowNull: true, defaultValue: null }, + }, + { + sequelize: database, + modelName: "AuditEventModel", + tableName: "audit_events", + charset: "utf8mb4", + collate: "utf8mb4_unicode_ci", + underscored: true, + timestamps: false, + indexes: [ + { name: "idx_audit_events_occurred_at_id", fields: ["occurred_at", "id"] }, + { + name: "idx_audit_events_scope_occurred_at_id", + fields: ["scope_type", "scope_id", "occurred_at", "id"], + }, + { + name: "idx_audit_events_target_occurred_at_id", + fields: ["target_type", "target_id", "occurred_at", "id"], + }, + { + name: "idx_audit_events_actor_occurred_at_id", + fields: ["actor_type", "actor_id", "occurred_at", "id"], + }, + { name: "idx_audit_events_operation_id", fields: ["operation_id"] }, + { name: "idx_audit_events_correlation_id", fields: ["correlation_id"] }, + ], + } + ); + + return AuditEventModel; +} diff --git a/apps/server/src/infrastructure/audit/sequelize/index.ts b/apps/server/src/infrastructure/audit/sequelize/index.ts new file mode 100644 index 00000000..1f71d76d --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/index.ts @@ -0,0 +1,10 @@ +export * from "./audit-change.model"; +export * from "./audit-event.model"; +export * from "./sequelize-audit-entry.mapper"; +export * from "./sequelize-audit-store"; + +import { initializeAuditChangeModel } from "./audit-change.model"; +import { initializeAuditEventModel } from "./audit-event.model"; + +/** Initializers globales, registrados una vez por el runtime antes de initModels(). */ +export const auditModelInitializers = [initializeAuditEventModel, initializeAuditChangeModel]; diff --git a/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-entry.mapper.ts b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-entry.mapper.ts new file mode 100644 index 00000000..4a35e0d8 --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-entry.mapper.ts @@ -0,0 +1,68 @@ +import type { AuditEntry, AuditValue } from "@repo/rdx-audit"; + +export type AuditEventPersistence = { + id: string; + kind: "event" | "mutation"; + occurred_at: Date; + source_type: string; + source_id: string; + operation_id: string; + correlation_id: string; + origin: string; + actor_type: string; + actor_id: string | null; + actor_label: string | null; + scope_type: string | null; + scope_id: string | null; + scope_label: string | null; + action: string; + target_type: string; + target_id: string | null; + target_label: string | null; +}; + +export type AuditChangePersistence = { + audit_event_id: string; + position: number; + path: string; + kind: "set" | "unset" | "change"; + before_value: AuditValue | null; + after_value: AuditValue | null; +}; + +/** Convierte el contrato portable en filas SQL sin exponer nombres de columnas al dominio. */ +export function mapAuditEntry(entry: AuditEntry): { + event: AuditEventPersistence; + changes: AuditChangePersistence[]; +} { + return { + event: { + id: entry.id, + kind: entry.kind, + occurred_at: new Date(entry.occurredAt), + source_type: entry.source.type, + source_id: entry.source.id, + operation_id: entry.operation.id, + correlation_id: entry.operation.correlationId, + origin: entry.operation.origin, + actor_type: entry.actor.type, + actor_id: entry.actor.id, + actor_label: entry.actor.label, + scope_type: entry.scope?.type ?? null, + scope_id: entry.scope?.id ?? null, + scope_label: entry.scope?.label ?? null, + action: entry.action, + target_type: entry.target.type, + target_id: entry.target.id, + target_label: entry.target.label, + }, + changes: entry.changes.map((change, position) => ({ + audit_event_id: entry.id, + position, + path: change.path, + kind: change.kind, + before_value: change.before, + after_value: change.after, + })), + }; +} diff --git a/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.test.ts b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.test.ts new file mode 100644 index 00000000..c8c6f13f --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.test.ts @@ -0,0 +1,136 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { ISequelizeTransactionContext } from "@erp/core/api"; +import type { AuditEntry } from "@repo/rdx-audit"; +import { AuditStoreError } from "@repo/rdx-audit"; +import type { Transaction } from "sequelize"; + +import type { AuditChangeModel } from "./audit-change.model"; +import type { AuditEventModel } from "./audit-event.model"; +import { SequelizeAuditStore } from "./sequelize-audit-store"; + +const eventEntry: AuditEntry = { + id: "event-1", + kind: "event", + occurredAt: "2026-08-24T10:20:30.123Z", + source: { type: "server", id: "server-1" }, + operation: { id: "operation-1", correlationId: "correlation-1", origin: "http" }, + actor: { type: "anonymous", id: null, label: null }, + scope: null, + action: "authentication.failed", + target: { type: "session", id: null, label: null }, + changes: [], +}; + +const mutationEntry: AuditEntry = { + ...eventEntry, + id: "mutation-1", + kind: "mutation", + action: "customer.updated", + actor: { type: "user", id: "user-1", label: "user@example.com" }, + scope: { type: "company", id: "company-1", label: null }, + target: { type: "customer", id: "customer-1", label: "Acme" }, + changes: [ + { path: "name", kind: "change", before: "Before", after: "After" }, + { path: "active", kind: "set", before: null, after: true }, + ], +}; + +function buildStore(transaction: Transaction | null = null) { + const createdEvents: unknown[] = []; + const createdChanges: unknown[] = []; + const eventOptions: unknown[] = []; + const changeOptions: unknown[] = []; + + const eventModel = { + create: async (attributes: unknown, options: unknown) => { + createdEvents.push(attributes); + eventOptions.push(options); + }, + } as unknown as typeof AuditEventModel; + const changeModel = { + bulkCreate: async (attributes: unknown, options: unknown) => { + createdChanges.push(attributes); + changeOptions.push(options); + }, + } as unknown as typeof AuditChangeModel; + const transactionContext = { + current: () => transaction, + } as unknown as ISequelizeTransactionContext; + + return { + store: new SequelizeAuditStore(eventModel, changeModel, transactionContext), + createdEvents, + createdChanges, + eventOptions, + changeOptions, + }; +} + +test("SequelizeAuditStore persiste un evento sin transacción y mapea sus campos", async () => { + const fixture = buildStore(); + + const result = await fixture.store.append(eventEntry); + + assert.equal(result.isSuccess, true); + assert.equal(fixture.createdEvents.length, 1); + assert.equal(fixture.createdChanges.length, 0); + assert.deepEqual(fixture.createdEvents[0], { + id: "event-1", + kind: "event", + occurred_at: new Date("2026-08-24T10:20:30.123Z"), + source_type: "server", + source_id: "server-1", + operation_id: "operation-1", + correlation_id: "correlation-1", + origin: "http", + actor_type: "anonymous", + actor_id: null, + actor_label: null, + scope_type: null, + scope_id: null, + scope_label: null, + action: "authentication.failed", + target_type: "session", + target_id: null, + target_label: null, + }); +}); + +test("SequelizeAuditStore exige transacción para mutaciones y reutiliza la activa", async () => { + const withoutTransaction = buildStore(); + const rejected = await withoutTransaction.store.append(mutationEntry); + + assert.equal(rejected.isFailure, true); + assert.ok(rejected.error instanceof AuditStoreError); + assert.equal(withoutTransaction.createdEvents.length, 0); + + const transaction = {} as Transaction; + const withinTransaction = buildStore(transaction); + const appended = await withinTransaction.store.append(mutationEntry); + + assert.equal(appended.isSuccess, true); + assert.equal(withinTransaction.createdEvents.length, 1); + assert.equal(withinTransaction.createdChanges.length, 1); + assert.deepEqual(withinTransaction.eventOptions[0], { transaction }); + assert.deepEqual(withinTransaction.changeOptions[0], { transaction }); + assert.deepEqual(withinTransaction.createdChanges[0], [ + { + audit_event_id: "mutation-1", + position: 0, + path: "name", + kind: "change", + before_value: "Before", + after_value: "After", + }, + { + audit_event_id: "mutation-1", + position: 1, + path: "active", + kind: "set", + before_value: null, + after_value: true, + }, + ]); +}); diff --git a/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.ts b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.ts new file mode 100644 index 00000000..024a6dde --- /dev/null +++ b/apps/server/src/infrastructure/audit/sequelize/sequelize-audit-store.ts @@ -0,0 +1,43 @@ +import type { ISequelizeTransactionContext } from "@erp/core/api"; +import type { AuditEntry, IAuditStore } from "@repo/rdx-audit"; +import { AuditStoreError } from "@repo/rdx-audit"; +import { Result } from "@repo/rdx-utils"; +import type { ModelStatic } from "sequelize"; + +import type { AuditChangeModel } from "./audit-change.model"; +import type { AuditEventModel } from "./audit-event.model"; +import { mapAuditEntry } from "./sequelize-audit-entry.mapper"; + +/** + * Store append-only. Una mutación exige transacción activa para compartir el + * rollback con la operación funcional; un evento aislado puede insertarse solo. + */ +export class SequelizeAuditStore implements IAuditStore { + constructor( + private readonly eventModel: ModelStatic, + private readonly changeModel: ModelStatic, + private readonly transactionContext: ISequelizeTransactionContext + ) {} + + async append(entry: AuditEntry): Promise> { + const transaction = this.transactionContext.current(); + if (entry.kind === "mutation" && !transaction) { + return Result.fail( + new AuditStoreError("An active transaction is required for audit mutations.") + ); + } + + try { + const { event, changes } = mapAuditEntry(entry); + await this.eventModel.create(event, { transaction: transaction ?? undefined }); + + if (entry.kind === "mutation") { + await this.changeModel.bulkCreate(changes, { transaction }); + } + + return Result.ok(); + } catch { + return Result.fail(new AuditStoreError("Unable to append audit entry.")); + } + } +} diff --git a/apps/server/src/lib/modules/model-loader.ts b/apps/server/src/lib/modules/model-loader.ts index 3cd007a5..e2c11ec9 100644 --- a/apps/server/src/lib/modules/model-loader.ts +++ b/apps/server/src/lib/modules/model-loader.ts @@ -1,5 +1,6 @@ // apps/server/src/lib/modules/model-loader.ts import type { ModuleParams } from "@erp/core/api"; +import type { Sequelize } from "sequelize"; import { logger } from "../logger"; @@ -10,10 +11,10 @@ type SequelizeLike = { type ModelStatic = { name: string; - associate?: (sequelize: SequelizeLike) => void; + associate?: (sequelize: Sequelize) => void; }; -export type ModelInitializer = (sequelize: SequelizeLike) => ModelStatic; +export type ModelInitializer = (sequelize: Sequelize) => ModelStatic; type InitializerEntry = { init: ModelInitializer; moduleName: string }; @@ -92,7 +93,7 @@ const defineModel = (database: SequelizeLike, entry: InitializerEntry): void => const { init, moduleName } = entry; try { - const model = init(database); + const model = init(database as unknown as Sequelize); assertValidModel(model); assertModelNameIsAvailable(model.name, moduleName); @@ -147,7 +148,7 @@ const associateModel = (database: SequelizeLike, entry: RegisteredModelEntry): v } try { - model.associate(database); + model.associate(database as unknown as Sequelize); } catch (err) { const error = err as Error; diff --git a/apps/server/src/lib/modules/module-loader.test.ts b/apps/server/src/lib/modules/module-loader.test.ts index afc1b0e5..a9c31d2f 100644 --- a/apps/server/src/lib/modules/module-loader.test.ts +++ b/apps/server/src/lib/modules/module-loader.test.ts @@ -1,7 +1,14 @@ import assert from "node:assert/strict"; import test from "node:test"; -import type { IModuleServer, ModuleParams } from "@erp/core/api"; +import { + AsyncLocalStorageOperationContextStore, + type IModuleServer, + type ModuleParams, + SequelizeTransactionContext, +} from "@erp/core/api"; +import { Audit } from "@repo/rdx-audit"; +import { Result } from "@repo/rdx-utils"; import { resetModelsRegistry } from "./model-loader"; import { initModules, registerModule, resetModuleRegistry } from "./module-loader"; @@ -31,6 +38,13 @@ function buildModuleParams(): ModuleParams & { models: {}, }, logger: console, + operationContext: new AsyncLocalStorageOperationContextStore(), + transactionContext: new SequelizeTransactionContext(), + audit: new Audit({ + source: { type: "test", id: "module-loader" }, + contextProvider: { get: () => null }, + store: { append: async () => Result.ok() }, + }), }; } diff --git a/apps/server/src/testing/http/audit-runtime.integration.test.ts b/apps/server/src/testing/http/audit-runtime.integration.test.ts new file mode 100644 index 00000000..7733b4f9 --- /dev/null +++ b/apps/server/src/testing/http/audit-runtime.integration.test.ts @@ -0,0 +1,298 @@ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; +import test from "node:test"; + +import { type IModuleServer, SequelizeTransactionManager } from "@erp/core/api"; +import { AuditContextUnavailableError } from "@repo/rdx-audit"; +import { Result } from "@repo/rdx-utils"; +import type { Request, Response } from "express"; + +import { AuditChangeModel, AuditEventModel } from "@/infrastructure/audit/sequelize"; + +import { + requireIdentityAuthenticated, + requireIdentityTenantMembership, +} from "../../../../../modules/identity/src/api/infrastructure/express/middlewares/require-identity-middlewares.ts"; + +import { + buildCompanyHeaders, + createTestAccount, + createTestCompany, + createTestMembership, + loginByApi, +} from "./http-test-fixtures.ts"; +import { createHttpTestRuntime } from "./http-test-runtime.ts"; + +const auditProbeModule: IModuleServer = { + name: "audit-probe", + version: "1.0.0", + dependencies: ["identity"], + start: (params) => { + params.app.post( + "/api/v1/__test/audit/event", + ...requireIdentityTenantMembership(params), + async (request: Request, response: Response) => { + const result = await params.audit.recordEvent({ + action: "test.audit.event", + target: { + type: "test_resource", + id: + typeof request.query.resourceId === "string" + ? request.query.resourceId + : "resource-1", + label: "Test resource", + }, + }); + response.status(result.isSuccess ? 201 : 500).json({ success: result.isSuccess }); + } + ); + + params.app.post( + "/api/v1/__test/audit/event-no-company", + ...requireIdentityAuthenticated(params), + async (_request: Request, response: Response) => { + const result = await params.audit.recordEvent({ + action: "test.audit.no_company", + target: { type: "test_resource", id: "resource-no-company", label: "Test resource" }, + }); + response.status(result.isSuccess ? 201 : 500).json({ success: result.isSuccess }); + } + ); + + params.app.post( + "/api/v1/__test/audit/mutation", + ...requireIdentityTenantMembership(params), + async (_request: Request, response: Response) => { + const transactionManager = new SequelizeTransactionManager( + params.database, + params.transactionContext + ); + const result = await transactionManager.complete(async () => + params.audit.recordMutation({ + action: "test.resource.updated", + target: { type: "test_resource", id: "resource-1", label: "Test resource" }, + before: { name: "Before" }, + after: { name: "After" }, + }) + ); + response.status(result.isSuccess ? 201 : 500).json({ success: result.isSuccess }); + } + ); + + params.app.post( + "/api/v1/__test/audit/mutation-without-transaction", + ...requireIdentityTenantMembership(params), + async (_request: Request, response: Response) => { + const result = await params.audit.recordMutation({ + action: "test.resource.updated_without_transaction", + target: { type: "test_resource", id: "resource-2", label: "Test resource" }, + before: { name: "Before" }, + after: { name: "After" }, + }); + response.status(result.isSuccess ? 201 : 409).json({ success: result.isSuccess }); + } + ); + + params.app.post( + "/api/v1/__test/audit/mutation-rollback", + ...requireIdentityTenantMembership(params), + async (_request: Request, response: Response) => { + const transactionManager = new SequelizeTransactionManager( + params.database, + params.transactionContext + ); + try { + await transactionManager.complete(async () => { + const result = await params.audit.recordMutation({ + action: "test.resource.rollback", + target: { type: "test_resource", id: "resource-rollback", label: "Test resource" }, + before: { name: "Before" }, + after: { name: "After" }, + }); + if (result.isFailure) return result; + return Result.fail(new Error("test rollback")); + }); + } catch { + response.status(409).json({ success: false }); + return; + } + response.status(500).json({ success: false }); + } + ); + + params.app.post( + "/api/v1/__test/audit/mutation-noop", + ...requireIdentityTenantMembership(params), + async (_request: Request, response: Response) => { + const result = await params.audit.recordMutation({ + action: "test.resource.noop", + target: { type: "test_resource", id: "resource-noop", label: "Test resource" }, + before: { name: "Same" }, + after: { name: "Same" }, + }); + response.status(result.isSuccess ? 201 : 500).json({ success: result.isSuccess }); + } + ); + }, +}; + +test("Audit runtime propaga contexto HTTP y transacciones hasta MariaDB", async (t) => { + const runtime = await createHttpTestRuntime({ testModules: [auditProbeModule] }); + t.after(async () => runtime.stop()); + + const first = await createAuthenticatedCompanyMember(runtime); + const second = await createAuthenticatedCompanyMember(runtime); + const correlationId = "audit-integration-test-123"; + + const eventResponse = await runtime.request("/api/v1/__test/audit/event", { + method: "POST", + headers: { + ...buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + "x-correlation-id": correlationId, + }, + }); + assert.equal(eventResponse.status, 201); + + const event = await AuditEventModel.findOne({ where: { action: "test.audit.event" } }); + assert.ok(event); + assert.equal(event.source_type, "application"); + assert.equal(event.source_id, "uecko-erp"); + assert.equal(event.origin, "http"); + assert.equal(event.operation_id, eventResponse.headers.get("x-request-id")); + assert.equal(event.correlation_id, correlationId); + assert.equal(event.actor_type, "user"); + assert.equal(event.actor_id, first.accountId); + assert.equal(event.scope_type, "company"); + assert.equal(event.scope_id, first.companyId); + assert.equal(event.target_type, "test_resource"); + assert.equal(event.target_id, "resource-1"); + assert.equal(await AuditChangeModel.count({ where: { audit_event_id: event.id } }), 0); + + const noCompanyResponse = await runtime.request("/api/v1/__test/audit/event-no-company", { + method: "POST", + headers: buildCompanyHeaders({ accessToken: first.accessToken }), + }); + assert.equal(noCompanyResponse.status, 201); + const noCompanyEvent = await AuditEventModel.findOne({ + where: { action: "test.audit.no_company" }, + }); + assert.ok(noCompanyEvent); + assert.equal(noCompanyEvent.scope_type, null); + assert.equal(noCompanyEvent.scope_id, null); + + const mutationResponse = await runtime.request("/api/v1/__test/audit/mutation", { + method: "POST", + headers: buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + }); + assert.equal(mutationResponse.status, 201); + const mutation = await AuditEventModel.findOne({ where: { action: "test.resource.updated" } }); + assert.ok(mutation); + assert.equal(mutation.kind, "mutation"); + const mutationChange = await AuditChangeModel.findOne({ where: { audit_event_id: mutation.id } }); + assert.ok(mutationChange); + assert.equal(mutationChange.path, "name"); + assert.equal(mutationChange.kind, "change"); + assert.equal(mutationChange.before_value, "Before"); + assert.equal(mutationChange.after_value, "After"); + + const mutationWithoutTransaction = await runtime.request( + "/api/v1/__test/audit/mutation-without-transaction", + { + method: "POST", + headers: buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + } + ); + assert.equal(mutationWithoutTransaction.status, 409); + assert.equal( + await AuditEventModel.count({ where: { action: "test.resource.updated_without_transaction" } }), + 0 + ); + + const rollbackResponse = await runtime.request("/api/v1/__test/audit/mutation-rollback", { + method: "POST", + headers: buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + }); + assert.equal(rollbackResponse.status, 409); + assert.equal(await AuditEventModel.count({ where: { action: "test.resource.rollback" } }), 0); + assert.equal(await AuditChangeModel.count(), 1); + + const countBeforeNoop = await AuditEventModel.count(); + const noopResponse = await runtime.request("/api/v1/__test/audit/mutation-noop", { + method: "POST", + headers: buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + }); + assert.equal(noopResponse.status, 201); + assert.equal(await AuditEventModel.count(), countBeforeNoop); + + const absentContext = await runtime.audit.recordEvent({ + action: "test.audit.outside_context", + target: { type: "test_resource", id: "outside", label: null }, + }); + assert.equal(absentContext.isFailure, true); + assert.ok(absentContext.error instanceof AuditContextUnavailableError); + + const [firstConcurrent, secondConcurrent] = await Promise.all([ + runtime.request("/api/v1/__test/audit/event?resourceId=concurrent-1", { + method: "POST", + headers: { + ...buildCompanyHeaders({ accessToken: first.accessToken, companyId: first.companyId }), + "x-correlation-id": "concurrent-correlation-1", + }, + }), + runtime.request("/api/v1/__test/audit/event?resourceId=concurrent-2", { + method: "POST", + headers: { + ...buildCompanyHeaders({ accessToken: second.accessToken, companyId: second.companyId }), + "x-correlation-id": "concurrent-correlation-2", + }, + }), + ]); + assert.equal(firstConcurrent.status, 201); + assert.equal(secondConcurrent.status, 201); + + const concurrentEvents = await AuditEventModel.findAll({ + where: { action: "test.audit.event" }, + order: [["target_id", "ASC"]], + }); + const firstConcurrentEvent = concurrentEvents.find((entry) => entry.target_id === "concurrent-1"); + const secondConcurrentEvent = concurrentEvents.find( + (entry) => entry.target_id === "concurrent-2" + ); + assert.equal(firstConcurrentEvent?.actor_id, first.accountId); + assert.equal(firstConcurrentEvent?.scope_id, first.companyId); + assert.equal(firstConcurrentEvent?.correlation_id, "concurrent-correlation-1"); + assert.equal(secondConcurrentEvent?.actor_id, second.accountId); + assert.equal(secondConcurrentEvent?.scope_id, second.companyId); + assert.equal(secondConcurrentEvent?.correlation_id, "concurrent-correlation-2"); +}); + +async function createAuthenticatedCompanyMember( + runtime: Awaited> +) { + const account = await createTestAccount(runtime.database, { + email: `audit-${randomUUID()}@example.com`, + password: "Secret123!", + }); + const company = await createTestCompany(runtime.database, { + legalName: `Audit Company ${randomUUID()}`, + }); + await createTestMembership(runtime.database, { + accountId: account.id, + companyId: company.id, + }); + const loginResponse = await loginByApi({ + request: runtime.request, + email: account.email, + password: account.password, + }); + assert.equal(loginResponse.status, 200); + assert.equal(typeof loginResponse.body, "object"); + assert.notEqual(loginResponse.body, null); + const loginBody = loginResponse.body as { access_token: string }; + + return { + accountId: account.id, + companyId: company.id, + accessToken: loginBody.access_token, + }; +} diff --git a/apps/server/src/testing/http/http-test-runtime.ts b/apps/server/src/testing/http/http-test-runtime.ts index 1cd0771e..1bd0c47b 100644 --- a/apps/server/src/testing/http/http-test-runtime.ts +++ b/apps/server/src/testing/http/http-test-runtime.ts @@ -3,6 +3,15 @@ import { type Server, createServer } from "node:http"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; +import { + AsyncLocalStorageOperationContextStore, + type IModuleServer, + type ISequelizeTransactionContext, + OperationAuditContextProvider, + SequelizeTransactionContext, +} from "@erp/core/api"; +import type { IAudit } from "@repo/rdx-audit"; +import { Audit } from "@repo/rdx-audit"; import dotenv from "dotenv"; import mysql from "mysql2/promise"; import type { Sequelize } from "sequelize"; @@ -13,9 +22,17 @@ import { closeDatabase, tryConnectToDatabase } from "@/config/database.ts"; import type { ConfigType } from "@/config/index.ts"; import { initConfig, resetConfigForTests } from "@/config/index.ts"; import { registerHealthRoutes } from "@/health.ts"; +import { + AuditChangeModel, + AuditEventModel, + SequelizeAuditStore, + auditModelInitializers, +} from "@/infrastructure/audit/sequelize"; import { logger } from "@/lib/logger.ts"; import { initModules, + registerModels, + registerModule, resetModelsRegistry, resetModuleRegistry, resetServiceRegistry, @@ -25,11 +42,18 @@ import { registerModules } from "@/register-modules.ts"; const currentDir = dirname(fileURLToPath(import.meta.url)); const TEST_ENV_FILE = resolve(currentDir, "../../../.env.test"); const FORBIDDEN_DATABASE_NAMES = new Set(["uecko_erp", "uecko_erp_sync", "rodax_db"]); +const AUDIT_SOURCE = { type: "application", id: "uecko-erp" } as const; + +export interface HttpTestRuntimeOptions { + readonly testModules?: readonly IModuleServer[]; +} export interface IHttpTestRuntime { readonly baseUrl: string; readonly config: ConfigType; readonly database: Sequelize; + readonly transactionContext: ISequelizeTransactionContext; + readonly audit: IAudit; request( path: string, init?: RequestInit @@ -51,7 +75,9 @@ export interface IHttpTestRuntime { * - Sequelize real * - login real */ -export async function createHttpTestRuntime(): Promise { +export async function createHttpTestRuntime( + options: HttpTestRuntimeOptions = {} +): Promise { loadTestEnvironment(); assignUniqueTestDatabaseName(); resetServerRuntimeForTests(); @@ -63,18 +89,37 @@ export async function createHttpTestRuntime(): Promise { await recreateTestDatabase(config); const database = await tryConnectToDatabase(config); - const app = createApp(config); + const operationContext = new AsyncLocalStorageOperationContextStore(); + const transactionContext = new SequelizeTransactionContext(); + const auditContextProvider = new OperationAuditContextProvider(operationContext); + const auditStore = new SequelizeAuditStore(AuditEventModel, AuditChangeModel, transactionContext); + const audit = new Audit({ + source: AUDIT_SOURCE, + contextProvider: auditContextProvider, + store: auditStore, + }); + const app = createApp(config, operationContext); registerHealthRoutes(app, config.server.apiBasePath, () => ({ ready: true })); let server: Server | null = null; try { registerModules(); + for (const testModule of options.testModules ?? []) { + registerModule(testModule); + } + registerModels(auditModelInitializers, undefined, { moduleName: "server-infrastructure" }); await initModules({ app, config, database, logger, + operationContext, + transactionContext, + audit, + getService: () => { + throw new Error("Services are only available from a module-scoped context."); + }, }); server = createServer(app); @@ -91,6 +136,8 @@ export async function createHttpTestRuntime(): Promise { baseUrl, config, database, + transactionContext, + audit, request: async (path: string, init?: RequestInit) => { const response = await fetch(`${baseUrl}${path}`, init); const contentType = response.headers.get("content-type") ?? ""; diff --git a/apps/server/src/testing/http/sequelize-audit-store.integration.test.ts b/apps/server/src/testing/http/sequelize-audit-store.integration.test.ts new file mode 100644 index 00000000..f18564b1 --- /dev/null +++ b/apps/server/src/testing/http/sequelize-audit-store.integration.test.ts @@ -0,0 +1,161 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { SequelizeTransactionManager } from "@erp/core/api"; +import type { AuditEntry, AuditMutationEntry } from "@repo/rdx-audit"; +import { Result } from "@repo/rdx-utils"; + +import { + AuditChangeModel, + AuditEventModel, + SequelizeAuditStore, +} from "@/infrastructure/audit/sequelize"; +import { createHttpTestRuntime } from "@/testing/http/http-test-runtime"; + +function createEvent(id: string): AuditEntry { + return { + id, + kind: "event", + occurredAt: "2026-08-24T10:20:30.123Z", + source: { type: "server", id: "server-1" }, + operation: { id: "operation-1", correlationId: "correlation-1", origin: "http" }, + actor: { type: "anonymous", id: null, label: null }, + scope: null, + action: "authentication.failed", + target: { type: "session", id: null, label: null }, + changes: [], + }; +} + +function createMutation(id: string): AuditMutationEntry { + return { + ...createEvent(id), + kind: "mutation", + action: "customer.updated", + actor: { type: "user", id: "user-1", label: "user@example.com" }, + scope: { type: "company", id: "company-1", label: null }, + target: { type: "customer", id: "customer-1", label: "Acme" }, + changes: [ + { path: "name", kind: "change", before: "Before", after: "After" }, + { path: "active", kind: "set", before: null, after: true }, + { path: "metadata", kind: "change", before: { tier: 1 }, after: ["a", 2, false] }, + ], + }; +} + +test("SequelizeAuditStore persiste y revierte entradas de auditoría en MariaDB", async (t) => { + const runtime = await createHttpTestRuntime(); + t.after(async () => runtime.stop()); + + const store = new SequelizeAuditStore( + AuditEventModel, + AuditChangeModel, + runtime.transactionContext + ); + const transactionManager = new SequelizeTransactionManager( + runtime.database, + runtime.transactionContext + ); + + const standaloneEvent = createEvent("event-standalone"); + const standaloneResult = await store.append(standaloneEvent); + assert.equal(standaloneResult.isSuccess, true); + assert.equal(await AuditEventModel.count({ where: { id: standaloneEvent.id } }), 1); + assert.equal(await AuditChangeModel.count({ where: { audit_event_id: standaloneEvent.id } }), 0); + + const mutationWithoutTransaction = await store.append( + createMutation("mutation-without-transaction") + ); + assert.equal(mutationWithoutTransaction.isFailure, true); + assert.equal(await AuditEventModel.count({ where: { id: "mutation-without-transaction" } }), 0); + + const committedMutation = createMutation("mutation-committed"); + await transactionManager.complete(async () => { + const result = await store.append(committedMutation); + assert.equal(result.isSuccess, true); + return result; + }); + assert.equal(await AuditEventModel.count({ where: { id: committedMutation.id } }), 1); + assert.equal( + await AuditChangeModel.count({ where: { audit_event_id: committedMutation.id } }), + committedMutation.changes.length + ); + + const persistedChanges = await AuditChangeModel.findAll({ + where: { audit_event_id: committedMutation.id }, + order: [["position", "ASC"]], + }); + assert.deepEqual( + persistedChanges.map((change) => change.position), + [0, 1, 2] + ); + assert.deepEqual(persistedChanges[2]?.before_value, { tier: 1 }); + assert.deepEqual(persistedChanges[2]?.after_value, ["a", 2, false]); + + const persistedEvent = await AuditEventModel.findByPk(standaloneEvent.id); + assert.equal(persistedEvent?.scope_type, null); + assert.equal(persistedEvent?.actor_id, null); + assert.equal( + persistedEvent?.occurred_at.getTime(), + new Date(standaloneEvent.occurredAt).getTime() + ); + + const eventRolledBack = createEvent("event-rollback"); + await assert.rejects( + () => + transactionManager.complete(async () => { + const result = await store.append(eventRolledBack); + assert.equal(result.isSuccess, true); + return Result.fail(new Error("rollback event")); + }), + /rollback event/ + ); + assert.equal(await AuditEventModel.count({ where: { id: eventRolledBack.id } }), 0); + + const mutationRolledBack = createMutation("mutation-rollback"); + await assert.rejects( + () => + transactionManager.complete(async () => { + const result = await store.append(mutationRolledBack); + assert.equal(result.isSuccess, true); + return Result.fail(new Error("rollback mutation")); + }), + /rollback mutation/ + ); + assert.equal(await AuditEventModel.count({ where: { id: mutationRolledBack.id } }), 0); + assert.equal( + await AuditChangeModel.count({ where: { audit_event_id: mutationRolledBack.id } }), + 0 + ); + + const mutationWithInvalidChange = { + ...createMutation("mutation-invalid-change"), + changes: [ + { + path: "x".repeat(256), + kind: "change" as const, + before: "Before", + after: "After", + }, + ], + } satisfies AuditEntry; + await assert.rejects( + () => + transactionManager.complete(async () => { + const result = await store.append(mutationWithInvalidChange); + assert.equal(result.isFailure, true); + return result; + }), + /Unable to append audit entry/ + ); + assert.equal(await AuditEventModel.count({ where: { id: mutationWithInvalidChange.id } }), 0); + assert.equal( + await AuditChangeModel.count({ where: { audit_event_id: mutationWithInvalidChange.id } }), + 0 + ); + + const duplicateEvent = createEvent("event-duplicate"); + assert.equal((await store.append(duplicateEvent)).isSuccess, true); + const duplicateResult = await store.append(duplicateEvent); + assert.equal(duplicateResult.isFailure, true); +}); diff --git a/docs/adit/Sistema-transversal-de-auditoría-erp.md b/docs/adit/Sistema-transversal-de-auditoría-erp.md new file mode 100644 index 00000000..39223fa9 --- /dev/null +++ b/docs/adit/Sistema-transversal-de-auditoría-erp.md @@ -0,0 +1,769 @@ +**DOCUMENTO DE DISEÑO** + +Sistema transversal de auditoría + +**Backend ERP · Estado consolidado** + +Versión de diseño: 1.0 +Fecha: 24 de agosto de 2026 +Estado: DISEÑO CERRADO PARA IMPLEMENTACIÓN + +**Propósito +**Definir una infraestructura de auditoría portable, append-only y fácil de integrar, capaz de registrar eventos y mutaciones con contexto de operación, preservar atomicidad con las transacciones Sequelize del ERP y soportar una futura pantalla administrativa con autorización por scopes o acceso global. + +# 1\. Resumen ejecutivo + +La auditoría se diseña como una capacidad técnica transversal, no como un módulo funcional del ERP. El núcleo portable vive en \`@repo/rdx-audit\`; el host configura una única instancia de \`Audit\` y la distribuye por \`ModuleParams\`, igual que \`database\` y \`logger\`. + +- \`IAudit.record()\` es la operación canónica. \`recordEvent()\` y \`recordMutation()\` son atajos tipados sin lógica alternativa. +- \`@repo/rdx-audit\` no depende de Company, tenant, Express, Sequelize, Criteria, roles, permisos ni del \`OperationContext\` concreto del ERP. +- El ámbito de un hecho se modela como \`scope: { type, id, label } | null\`. \`scope=null\` significa evento sin ámbito, nunca acceso global. +- La autorización de lectura es responsabilidad de la aplicación host y queda separada del scope almacenado en el evento. +- Las mutaciones auditadas en producción deben persistirse en la misma transacción Sequelize que la modificación funcional. +- \`SequelizeTransactionContext\` se introduce por DI y solo expone la transacción activa a infraestructura transversal; repositorios y casos de uso siguen recibiendo la transacción explícitamente. +- El store portable solo conoce \`append(entry)\`. \`SequelizeAuditStore\`, \`FileAuditStore\` o un store externo son adapters intercambiables. +- La lectura administrativa usa un \`SequelizeAuditReader\` server-side apoyado en \`@repo/rdx-criteria/server\`, con whitelist propia de filtros y una política de acceso separada. +- No se usa Event Sourcing. La auditoría tampoco sustituye a logs, tracing, historial de versiones de documentos ni BusinessVersion. + +| **Decisión** | **Resultado** | +| ------------------------------- | -------------------------------------------------------------- | +| Arquitectura | Paquete portable + adapters host-specific | +| Persistencia productiva inicial | MariaDB/Sequelize, tablas \`audit_events\` y \`audit_changes\` | +| Mutaciones | Atómicas con la transacción funcional | +| Eventos | Pueden persistirse con o sin transacción activa según contexto | +| Lectura | Reader server-side; no forma parte del core portable | +| Autorización | Fuera de \`rdx-audit\`; puede ser scoped o unrestricted | +| Adopción | Incremental por módulo y operación | + +# 2\. Objetivos y alcance + +Proporcionar un Audit Trail reutilizable para registrar quién o qué realizó una acción, en qué operación, dentro de qué ámbito, sobre qué recurso y qué cambió cuando exista una mutación. + +## 2.1 Objetivos específicos + +- Integración mínima en cualquier módulo mediante \`IAudit\`. +- Persistencia append-only e identidad propia de cada \`AuditEntry\`. +- Modelo portable y serializable compatible con múltiples stores. +- Diff determinista para mutaciones a partir de \`before\` y \`after\` controlados por el módulo propietario. +- Contexto automático de source, operación, actor y scope. +- Atomicidad fuerte con MariaDB/Sequelize para mutaciones funcionales. +- Lectura eficiente para una futura UI administrativa con filtros, detalle y paginación. +- Diseño compatible con un administrador limitado a uno o varios scopes y con un administrador global. + +## 2.2 Fuera de alcance + +- Event Sourcing o reconstrucción del estado de negocio a partir del audit log. +- BusinessVersion de presupuestos, proformas u otros documentos funcionales. +- DocumentVersion para binarios o gestor documental. +- Auditar automáticamente todas las lecturas o validaciones fallidas. +- Sistema SIEM, alertas o severidades de seguridad. +- Retención legal, archivado, particionado o purge en esta primera versión. +- Búsqueda full-text sobre \`before_value\` / \`after_value\`. +- Histórico granular de arrays o líneas individuales en la primera versión. + +# 3\. Terminología e invariantes + +| **Concepto** | **Semántica** | +| ------------- | ---------------------------------------------------------------------------- | +| AuditEntry | Unidad histórica append-only generada por Audit. | +| Event | Hecho auditable sin diff de estado. | +| Mutation | Hecho auditable con uno o más cambios derivados de before/after. | +| Source | Sistema que produjo el registro, p.ej. \`application:uecko-erp\`. | +| Actor | Quién o qué ejecutó la acción. | +| Scope | Ámbito donde ocurrió; puede ser null. | +| Target | Recurso o categoría sobre la que ocurrió la acción. | +| Operation | Ejecución lógica local que puede producir varios AuditEntry. | +| CorrelationId | Identificador que relaciona varias operaciones, incluso entre sistemas. | +| Action | Identificador histórico estable de la operación, p.ej. \`customer.updated\`. | + +**Invariante de autorización +**\`AuditEntry.scope\` describe dónde ocurrió el hecho. No expresa lo que un usuario puede consultar. \`scope=null\` nunca significa wildcard, superadmin ni acceso global. + +# 4\. Arquitectura + +apps/server +│ +├── OperationContextStore +├── SequelizeTransactionContext +├── Audit +│ ├── IAuditContextProvider +│ └── IAuditStore +│ └── SequelizeAuditStore +│ +└── initModules(ModuleParams) +├── database +├── logger +├── audit +└── transactionContext + +Los módulos no declaran \`dependencies: \["audit"\]\` ni resuelven servicios \`audit:\*\`. \`audit\` es infraestructura transversal suministrada por el runtime. + +## 4.1 Flujo de una mutación + +HTTP / job / integration +│ +▼ +OperationContext +│ +▼ +Use Case / Application Service +│ +▼ +TransactionManager.complete() +│ +├── TransactionContext = T1 +├── repository.save(..., T1) +└── audit.recordMutation(...) +│ +▼ +AuditEntry +│ +▼ +SequelizeAuditStore +│ +└── TransactionContext.current() = T1 +│ +▼ +COMMIT o ROLLBACK + +# 5\. Paquete portable \`@repo/rdx-audit\` + +## 5.1 API pública + +export interface IAudit { +record(input: RecordAuditInput): Promise<Result; +recordEvent(input: RecordAuditEventShortcutInput): Promise<Result; +recordMutation(input: RecordAuditMutationShortcutInput): Promise<Result; +} +
export interface IAuditStore { +append(entry: AuditEntry): Promise<Result; +} + +\`record()\` es la única operación canónica. Los shortcuts añaden \`kind\` y delegan íntegramente en \`record()\`. + +## 5.2 Inputs + +export interface RecordAuditEventInput { +readonly kind: "event"; +readonly action: string; +readonly target: AuditEventTarget; +} +
export interface RecordAuditMutationInput { +readonly kind: "mutation"; +readonly action: string; +readonly target: AuditMutationTarget; +readonly before: AuditState; +readonly after: AuditState; +readonly atomicPaths?: readonly string\[\]; +} +
export type RecordAuditInput = +| RecordAuditEventInput +| RecordAuditMutationInput; + +## 5.3 Contratos estructurales + +export interface AuditSource { +readonly type: string; +readonly id: string; +} +
export interface AuditOperation { +readonly id: string; +readonly correlationId: string; +readonly origin: string; +} +
export interface AuditActor { +readonly type: string; +readonly id: string | null; +readonly label: string | null; +} +
export interface AuditScope { +readonly type: string; +readonly id: string; +readonly label: string | null; +} +
export interface AuditEventTarget { +readonly type: string; +readonly id: string | null; +readonly label: string | null; +} +
export interface AuditMutationTarget { +readonly type: string; +readonly id: string; +readonly label: string | null; +} + +**Decisión KISS +**No se introduce una interfaz base común para source/actor/scope/target. La duplicación es mínima y las invariantes y evolución de cada concepto son distintas. + +# 6\. \`AuditState\` y semántica de diff + +export type AuditScalar = string | number | boolean | null; +export type AuditObject = Readonly<{ \[key: string\]: AuditValue }>; +export type AuditArray = readonly AuditValue\[\]; +export type AuditValue = AuditScalar | AuditObject | AuditArray; +export type AuditState = Readonly<Record; + +- No admite \`undefined\`, \`Date\`, \`Buffer\`, \`bigint\`, funciones, instancias de clase ni ciclos. +- Los números deben ser finitos. +- Las keys auditables no contienen \`.\`; el punto queda reservado para paths. +- Valores exactos como Money/Percentage/Quantity se proyectan a objetos estables con strings cuando corresponda. +- El módulo construye una proyección histórica controlada; Audit nunca introspecciona aggregates, DTOs o modelos Sequelize. +- Secrets no se materializan. Datos personales solo se incluyen cuando exista justificación histórica. + +## 6.1 Reglas de diff + +| **Caso** | **Regla** | +| --------------------------- | --------------------------------------------------- | +| Objetos | Comparación recursiva; el orden de keys no importa. | +| Arrays | Atómicos y order-sensitive en v1. | +| atomicPaths | Permite tratar un objeto completo como una unidad. | +| set | Campo inexistente antes y presente después. | +| unset | Campo presente antes y ausente después. | +| change | Campo presente en ambos con valores diferentes. | +| Cambio de tipo incompatible | Se registra en el path padre. | +| Orden de changes | Lexicográfico por \`path\`. | +| Mutation sin cambios | \`Result.ok()\` y no se crea \`AuditEntry\`. | + +## 6.2 \`AuditChange\` + +export interface AuditChange { +readonly path: string; +readonly kind: "set" | "unset" | "change"; +readonly before: AuditValue | null; +readonly after: AuditValue | null; +} + +# 7\. Modelo portable \`AuditEntry\` + +interface AuditEntryBase { +readonly id: string; +readonly occurredAt: string; +readonly source: AuditSource; +readonly operation: AuditOperation; +readonly actor: AuditActor; +readonly scope: AuditScope | null; +readonly action: string; +} +
export interface AuditEventEntry extends AuditEntryBase { +readonly kind: "event"; +readonly target: AuditEventTarget; +readonly changes: readonly \[\]; +} +
export interface AuditMutationEntry extends AuditEntryBase { +readonly kind: "mutation"; +readonly target: AuditMutationTarget; +readonly changes: readonly AuditChange\[\]; +} +
export type AuditEntry = AuditEventEntry | AuditMutationEntry; + +- \`event\` exige \`changes=\[\]\`. +- \`mutation\` exige \`changes.length > 0\` en runtime y target identificado. +- El entry completo es serializable y portable entre MariaDB, JSONL, servicio HTTP, Kafka u otro store. +- El store nunca recibe \`before\`, \`after\`, \`atomicPaths\` ni \`OperationContext\`; recibe el \`AuditEntry\` definitivo. + +# 8\. \`action\`, source, actor, scope y target + +## 8.1 Action + +\`action\` es un string validado y un contrato histórico estable propiedad del módulo funcional. No existe enum global ni registry. + +- Convención: segmentos lowercase separados por punto, con mínimo dos segmentos. +- El primer segmento suele identificar el área o recurso propietario: \`customer.updated\`, \`proforma.issued\`, \`document.version.created\`. +- No se incluyen IDs ni datos variables en la acción. +- No se guarda \`actionLabel\`; la UI resuelve i18n y usa el identificador como fallback. +- No se persisten \`category\`, \`namespace\` ni \`severity\` en v1. +- Una acción publicada en producción no se renombra por razones estéticas; si cambia la semántica, se crea otra acción. + +## 8.2 Source + +Identifica el productor técnico del registro y se configura una sola vez al construir \`Audit\`. Ejemplo: \`{ type: "application", id: "uecko-erp" }\`. + +## 8.3 Actor + +Genérico: \`{ type, id|null, label|null }\`. El ERP puede usar \`user\`, \`system\`, \`integration\`, \`job\`, \`anonymous\`, sin que \`rdx-audit\` los enumere. + +## 8.4 Scope + +Genérico y opcional: company, organization, workspace, project u otro ámbito. Si existe, \`id\` es obligatorio. \`scope=null\` significa que el evento no tiene un ámbito concreto. + +## 8.5 Target + +Identifica la categoría/recurso afectado. Un Event puede tener \`id=null\`; una Mutation requiere un target identificado. + +# 9\. Identificadores y contexto de operación + +| **Identificador** | **Responsable** | **Semántica** | +| ----------------- | ------------------------ | ------------------------------------------------------- | +| AuditEntry.id | Audit | Identidad global e inmutable del registro. | +| Operation.id | OperationContext host | Identidad de una ejecución local. | +| correlationId | Contexto host / upstream | Relaciona operaciones; puede propagarse entre sistemas. | + +- Los tres son strings opacos en el contrato portable. +- \`AuditEntry.id\` se genera antes del store y no usa AUTO_INCREMENT como identidad lógica. +- \`operation.id\` lo genera siempre el backend; no se confía en un ID de request aportado por cliente. +- \`correlationId\` puede recibirse de upstream si se valida; si no existe, se genera. +- \`correlationId\` es obligatorio; no se usa null. +- \`occurredAt\` lo genera Audit en ISO-8601 UTC y precisión de milisegundos. +- No se introduce \`causationId\` ni \`idempotencyKey\` en v1. + +# 10\. OperationContext e \`IAuditContextProvider\` + +export interface AuditContext { +readonly operation: AuditOperation; +readonly actor: AuditActor; +readonly scope: AuditScope | null; +} +
export interface IAuditContextProvider { +get(): AuditContext | null; +} + +\`rdx-audit\` no conoce \`AsyncLocalStorage\` ni el \`OperationContextStore\` concreto. El host adapta su contexto al puerto mínimo anterior. + +- HTTP abre el contexto con actor anónimo y scope null; autenticación puede enriquecer actor y tenancy puede enriquecer scope. +- \`operationId\`, \`correlationId\` y \`origin\` son inmutables dentro de una operación. +- La transición de scope permitida es null -> A. A -> B está prohibida dentro de la misma operación. +- Jobs, integraciones y migraciones deben abrir su propio contexto explícito. +- Ausencia de contexto al auditar es fail-fast. + +# 11\. \`SequelizeTransactionContext\` + +Es infraestructura host-specific, independiente de \`OperationContext\` y fuera de \`@repo/rdx-audit\`. Se distribuye por DI y se implementa con \`AsyncLocalStorage\`. + +export interface ISequelizeTransactionContext { +run<T>(transaction: Transaction, callback: () => T): T; +current(): Transaction | null; +} + +- Los repositorios y servicios funcionales continúan recibiendo \`Transaction\` explícitamente. +- \`SequelizeTransactionManager.complete()\` abre el scope del context alrededor del callback. +- Debe conservarse el comportamiento actual: un \`Result.fail\` dentro del callback provoca rollback. +- Las transacciones anidadas quedan prohibidas inicialmente; no hay evidencia productiva que requiera savepoints o nesting. +- El contexto se inyecta; no se utiliza singleton global. + +**Deuda técnica detectada +**El manager actual declara protección frente a anidamiento, pero su \`complete()\` no mantiene \`\_transaction\`; esa incoherencia debe corregirse o cubrirse con la nueva política de nesting, sin mezclar una refactorización mayor con audit. + +# 12\. Stores y consistencia + +## 12.1 Puerto portable + +export interface IAuditStore { +append(entry: AuditEntry): Promise<Result; +} + +- Solo append. No update, delete ni upsert. +- La lectura usa otro adapter; no se mezcla en el write port. +- El entry se genera completamente antes del store. +- El store debe preservar el orden de \`changes\`. +- Un fallo del store no genera a su vez otro AuditEntry. + +## 12.2 Adapters previstos + +| **Adapter** | **Garantía** | +| ------------------- | ---------------------------------------------------------------------- | +| SequelizeAuditStore | Store productivo principal; puede compartir la transacción funcional. | +| FileAuditStore | Útil para desarrollo/best-effort; no es ACID respecto al negocio. | +| ExternalAuditStore | Portable, pero no puede garantizar atomicidad con MariaDB por sí solo. | +| Outbox + external | Evolución futura si se necesita entrega externa fuerte sin 2PC. | + +**Regla de producción +**Una configuración que exija consistencia fuerte no debe aceptar como store primario un FileAuditStore o un external store no transaccional. La validación de esa combinación pertenece al bootstrap del host, no a \`rdx-audit\`. + +# 13\. \`SequelizeAuditStore\` y esquema MariaDB + +La representación relacional utiliza dos tablas. No existe \`audit_change_sets\`. + +audit_events +1 ─────── 0..N audit_changes + +## 13.1 \`audit_events\` + +| **Columna** | **Tipo propuesto** | **Nulabilidad** | +| -------------- | ------------------ | --------------- | +| id | VARCHAR(64) | NOT NULL, PK | +| kind | VARCHAR(32) | NOT NULL | +| occurred_at | DATETIME(3) | NOT NULL | +| source_type | VARCHAR(64) | NOT NULL | +| source_id | VARCHAR(128) | NOT NULL | +| operation_id | VARCHAR(128) | NOT NULL | +| correlation_id | VARCHAR(128) | NOT NULL | +| origin | VARCHAR(64) | NOT NULL | +| actor_type | VARCHAR(64) | NOT NULL | +| actor_id | VARCHAR(128) | NULL | +| actor_label | VARCHAR(255) | NULL | +| scope_type | VARCHAR(64) | NULL | +| scope_id | VARCHAR(128) | NULL | +| scope_label | VARCHAR(255) | NULL | +| action | VARCHAR(191) | NOT NULL | +| target_type | VARCHAR(64) | NOT NULL | +| target_id | VARCHAR(128) | NULL | +| target_label | VARCHAR(255) | NULL | + +## 13.2 \`audit_changes\` + +| **Columna** | **Tipo propuesto** | **Notas** | +| -------------- | ------------------ | ------------------------ | +| audit_event_id | VARCHAR(64) | PK/FK -> audit_events.id | +| position | INT UNSIGNED | PK; preserva orden | +| path | VARCHAR(255) | NOT NULL | +| kind | VARCHAR(32) | set/unset/change | +| before_value | JSON | NULL | +| after_value | JSON | NULL | + +- MariaDB \`JSON\` es adecuado para presentar valores históricos; no se diseña como mecanismo principal de búsqueda. +- No existen FKs a Company, User, Customer, Proforma u otros recursos funcionales. +- \`audit_changes -> audit_events\` sí tiene integridad referencial interna; \`ON DELETE CASCADE\` puede reservarse para retención administrativa futura. +- No se usan timestamps Sequelize automáticos \`createdAt/updatedAt\`. +- No se añade \`stored_at\`, soft delete, particionado, metadata genérica ni índices JSON en v1. + +## 13.3 Constraints + +- \`audit_events.kind IN (event, mutation)\`. +- \`audit_changes.kind IN (set, unset, change)\`. +- Scope: o todos sus campos son null, o al menos \`scope_type\` y \`scope_id\` están presentes. +- \`kind=mutation\` implica \`target_id IS NOT NULL\`. +- Las invariantes se validan también antes de persistir; las CHECK constraints son defensa adicional. + +## 13.4 Índices iniciales + +PRIMARY KEY (id) +INDEX (occurred_at, id) +INDEX (scope_type, scope_id, occurred_at, id) +INDEX (target_type, target_id, occurred_at, id) +INDEX (actor_type, actor_id, occurred_at, id) +INDEX (operation_id) +INDEX (correlation_id) + +No se indexan inicialmente \`kind\`, \`path\`, JSON values, source ni action. Se añadirán solo si las consultas reales justifican el coste de escritura. + +# 14\. Lectura administrativa + +\`IAuditReader\` no forma parte del core portable porque el modelo actual de Criteria se exporta desde \`@repo/rdx-criteria/server\` y arrastra Sequelize. La lectura se implementa en infraestructura server-side. + +interface IAuditReader { +search( +criteria: Criteria, +access: AuditReadAccess, +): Promise<Result; +
findById( +id: string, +access: AuditReadAccess, +): Promise<Result; +} + +## 14.1 \`AuditReadAccess\` + +type AuditReadAccess = +| Readonly<{ mode: "unrestricted" }> +| Readonly<{ +mode: "scoped"; +scopes: readonly AuditScopeIdentity\[\]; +includeUnscoped: boolean; +}>; +
interface AuditScopeIdentity { +readonly type: string; +readonly id: string; +} + +- Administrador limitado: \`mode=scoped\`, uno o varios scopes autorizados. +- Administrador global: \`mode=unrestricted\` y no se añade predicado de scope. +- \`includeUnscoped\` es explícito; no se infiere. +- El access model no utiliza \`OperationContext.scope\` como autorización automática. + +## 14.2 Criteria + +- Se reutiliza \`@repo/rdx-criteria/server\` solo dentro del reader Sequelize. +- El reader mantiene whitelist propia de campos y operadores porque el converter actual puede utilizar un field desconocido como columna Sequelize. +- Los mappings lógico -> columna son explícitos para todos los fields auditables. +- \`strictMode\` se activa para sorting, pero no sustituye la whitelist de filtros. +- Paginación inicial: \`pageNumber/pageSize\`, zero-based, coherente con el backend actual. +- Orden por defecto: \`occurredAt DESC\`; el reader añade \`id DESC\` como desempate técnico. +- El máximo de \`pageSize\` se valida en el boundary porque el MAX actual no está aplicado de forma global. + +## 14.3 Filtros iniciales + +| **Campo** | **Operadores previstos** | +| -------------------------------- | ---------------------------------- | +| id | EQUALS | +| kind | EQUALS | +| occurredAt | EQUALS, >, >=, <, <= | +| sourceType/sourceId | EQUALS | +| operationId/correlationId/origin | EQUALS | +| actorType/actorId | EQUALS; null checks cuando aplique | +| scopeType/scopeId | EQUALS, IS_NULL, IS_NOT_NULL | +| action | EQUALS; búsqueda libre separada | +| targetType/targetId | EQUALS; null checks cuando aplique | + +## 14.4 Quick search + +Primera versión: quick search específico con LIKE sobre \`action\`, \`actor_label\`, \`scope_label\` y \`target_label\`. No se crean FULLTEXT indexes ni se busca dentro de JSON en v1. + +## 14.5 Limitaciones actuales de Criteria + +- No hay \`IN\`, \`STARTS_WITH\` ni múltiples órdenes expresables en Criteria. +- La autorización multi-scope se construye directamente como \`WhereOptions\`, no mediante Criteria. +- El prefijo \`customer.\*\` no es requisito de v1; si aparece necesidad real puede añadirse \`STARTS_WITH\` como extensión genérica. +- Existe una incoherencia \`NOT_EQUALS\` / \`NOT_EQUAL\` en \`rdx-criteria\`; se registra como deuda técnica independiente. + +# 15\. Futura API/UI administrativa + +La pantalla aún no está definida, pero el backend queda preparado para list/detail sin comprometer la autorización futura. + +GET /admin/audit?...criteria... +GET /admin/audit/:audit_event_id + +Las rutas son orientativas; la ubicación definitiva se decidirá al diseñar el módulo de administración. + +- El listado consulta solo \`audit_events\` y devuelve un summary sin \`changes\`. +- El detalle consulta \`audit_events\` con \`id + access\` en la misma query y después carga \`audit_changes\` ordenados por position. +- No se realiza un \`findById\` unrestricted seguido de verificación posterior. +- Los HTTP DTOs siguen separados del modelo portable \`AuditEntry\` y se validan con los patrones DTO/Zod vigentes del ERP. +- La UI resuelve labels de acciones mediante i18n y usa el action estable como fallback. + +# 16\. Integración con \`ModuleParams\` y módulos + +interface ModuleParams { +// contratos actuales... +readonly audit: IAudit; +readonly transactionContext: ISequelizeTransactionContext; +} + +No se acomete ahora un refactor general del tipado débil de \`ModuleParams\`; audit solo añade las dependencias necesarias. + +- \`transactionContext\` se pasa a los 14 callers actuales de \`buildTransactionManager\` mediante DI explícita. +- \`audit\` solo se inyecta en use cases/application services que auditan operaciones. +- No se inyecta en repositories, mappers, domain entities ni controllers. +- Cross-module services que posean una acción auditada reciben \`IAudit\` en su propio composition root; el caller no pasa audit como argumento. +- Cada action tiene un único owner funcional para evitar duplicados. + +# 17\. Patrones normativos por operación + +| **Operación** | **before** | **after** | **Action** | +| ----------------- | --------------- | ----------------- | --------------------------------------------- | +| Create | {} | Estado persistido | \`\*.created\` | +| Update ordinario | Estado anterior | Estado nuevo | \`\*.updated\` | +| Update semántico | Estado anterior | Estado nuevo | Acción propia, p.ej. \`customer.deactivated\` | +| Delete físico | Estado anterior | {} | \`\*.deleted\` | +| Evento no mutante | — | — | \`recordEvent()\` | + +## 17.1 Secuencia de mutation + +load aggregate +\-> build BEFORE AuditState +\-> apply domain mutation +\-> persist +\-> build AFTER AuditState +\-> audit.recordMutation() +\-> commit + +**Orden obligatorio +**Audit se registra después de que la persistencia funcional haya sido aceptada, pero antes de abandonar el callback transaccional. Si el audit falla, la transacción completa debe hacer rollback. + +## 17.2 Proyección por módulo + +- No se usa \`aggregate.toPrimitives()\` automáticamente. +- Cada módulo define una proyección histórica explícita y determinista. +- Referencias legibles pueden representarse como \`{ id, label }\` y marcarse en \`atomicPaths\`. +- Arrays se normalizan en el módulo cuando su orden no sea semántico. +- Create usa \`before={}\` para obtener un diff de campos establecidos. +- Delete captura target y before antes de borrar físicamente. + +# 18\. Errores y atomicidad + +- \`recordMutation()\` y \`recordEvent()\` devuelven \`Result\`; no son fire-and-forget. +- El use case propaga \`Result.fail\` sin exponer detalles SQL. +- En una mutation productiva: audit failure -> use case failure -> \`TransactionManager.complete()\` detecta Result.fail -> rollback. +- \`SequelizeAuditStore\` requiere transacción activa para mutations bajo política strong/required; no abre silenciosamente otra transacción. +- Un Event puede reutilizar una transacción activa o realizar una escritura autónoma si la política host lo permite. +- \`rdx-audit\` no contiene flags \`required/bestEffort\`; la combinación store/consistency se valida al construir el runtime. + +# 19\. Seguridad, privacidad y observabilidad + +- No se almacenan passwords, tokens, secretos, claves privadas ni credenciales. +- PII se limita a valores que tengan utilidad histórica justificada; labels son snapshots auxiliares, no descripciones completas. +- No existen FKs a recursos funcionales para preservar el registro histórico aunque el recurso cambie o desaparezca. +- La auditoría no sustituye al logging. Reads, validation failures y errores técnicos se observan mediante logs/tracing salvo eventos explícitos de negocio/seguridad. +- No se persisten roles/permisos del actor en cada entry. La futura autorización de lectura usa el estado de autorización del host. +- El usuario DB de runtime debería aplicar menor privilegio cuando la infraestructura general lo permita; no se introducen triggers anti-update en v1. + +# 20\. Estrategia de pruebas + +## 20.1 \`rdx-audit\` + +- Validación de AuditState y rechazo de valores no portables. +- Diff recursivo de objetos, arrays atómicos, atomicPaths y orden lexicográfico. +- Event siempre con changes vacío. +- Mutation sin cambios devuelve éxito sin append. +- Mutation con target no identificado se rechaza. +- Context inexistente produce error fail-fast. +- IDs y clock deterministas en tests mediante dependencias sustituibles si se introducen. + +## 20.2 TransactionContext + +- \`current()\` es null fuera de \`complete()\`. +- Dentro de \`complete()\` devuelve la misma Transaction que recibe el callback. +- Restauración del scope al finalizar y también ante error. +- \`Result.fail\` sigue provocando rollback. +- Nesting prohibido se detecta explícitamente. + +## 20.3 SequelizeAuditStore + +- Event inserta solo \`audit_events\`. +- Mutation inserta event + changes de forma atómica. +- Fallos en changes provocan rollback completo. +- Mutation sin transaction activa bajo política required falla. +- Mapping JSON y orden \`position\` son correctos. + +## 20.4 Módulos consumidores + +- Verificar action, target, before y after en las operaciones instrumentadas. +- Los unit tests mockean/fakean \`IAudit\`; no necesitan conocer store, OperationContext ni TransactionContext. +- La atomicidad completa se prueba en infraestructura y en uno o dos flujos representativos, no repetida en cada use case. + +# 21\. Plan de implementación incremental + +1. Crear \`@repo/rdx-audit\` con tipos portables, \`Audit\`, validación y diff. +2. Introducir \`OperationContext\`/adapter \`IAuditContextProvider\` según el contrato acordado. +3. Crear \`ISequelizeTransactionContext\` e implementación con AsyncLocalStorage. +4. Adaptar \`SequelizeTransactionManager.complete()\` conservando rollback de \`Result.fail\` y política de no nesting. +5. Construir una única instancia de transaction context en \`apps/server\` y pasarla por \`ModuleParams\`/runtime de tests. +6. Actualizar los 14 callers de \`buildTransactionManager\`. +7. Implementar modelos, migración y \`SequelizeAuditStore\`. +8. Configurar \`Audit\` una vez en \`apps/server\` y pasarlo a módulos. +9. Instrumentar un módulo piloto con create/update/status change y validar ergonomía. +10. Extender progresivamente la cobertura a payment terms, customers, proformas, issued invoices y resto. +11. Implementar \`SequelizeAuditReader\` y autorización cuando se diseñe la pantalla administrativa. + +# 22\. Cobertura y ownership por módulo + +La adopción es explícita. Un módulo solo se considera cubierto cuando se documentan sus operaciones mutables auditadas. + +Customers audit coverage +\[x\] create +\[x\] update +\[x\] deactivate +\[x\] reactivate +\[ \] delete - no existe + +No se crea un registry global de acciones. El owner funcional puede usar constantes locales para evitar typos cuando haya varias acciones. + +# 23\. Alternativas descartadas + +| **Alternativa** | **Motivo de descarte** | +| ------------------------------------ | ------------------------------------------------------------------------------------------------ | +| \`modules/audit\` + service registry | Audit es infraestructura transversal; añadiría wiring funcional artificial. | +| \`record(input, transaction)\` | Filtra semántica Sequelize al contrato portable y empeora stores alternativos. | +| Transaction context singleton | Reduce 14 cambios a costa de estado global oculto y peor testabilidad. | +| Audit Sequelize hooks | No conocen adecuadamente la semántica funcional y pueden duplicar/registrar cambios no deseados. | +| Audit desde Domain | Acopla dominio a infraestructura transversal. | +| Audit desde controllers | No posee la semántica ni atomicidad funcional completa. | +| Criteria dentro de \`rdx-audit\` | El Criteria actual server-side depende de Sequelize; duplicar DSL tampoco aporta valor. | +| \`scope=null\` = acceso global | Mezcla dato histórico con autorización y hace ambiguas las consultas. | +| Arrays granulares v1 | Complejidad innecesaria; arrays atómicos cubren el alcance inicial. | +| Event Sourcing | No es requisito y cambiaría radicalmente el modelo de persistencia funcional. | + +# 24\. Evoluciones futuras, no bloqueantes + +- \`IAuditReader\` con cursor/keyset si el volumen hace costoso el offset. +- Operadores genéricos \`STARTS_WITH\`, \`IN\` y orden múltiple en \`rdx-criteria\` si aparecen casos reales. +- Full-text o índices adicionales según métricas reales de la UI administrativa. +- Retención, purge administrativo, exportación y archivado. +- Outbox para envío fiable a store externo. +- Diff granular de Collections/arrays con keys estables si aporta valor. +- \`causationId\` en flujos event-driven futuros. +- \`source.version\` o environment si un mismo store mezcla despliegues/sistemas y aparece necesidad operativa. +- BusinessVersion y DocumentVersion como capacidades separadas, no absorbidas por Audit. + +# 25\. Checklist de decisiones cerradas + +| **Tema** | **Decisión** | +| ------------------- | ------------------------------------------------------- | +| Core portable | @repo/rdx-audit | +| IAudit | record + recordEvent + recordMutation | +| Store | append-only | +| Scope | genérico; null permitido | +| Autorización | host-specific y separada | +| Transacción | ambient solo para infraestructura; explícita en negocio | +| Transaction context | DI, no singleton | +| Nesting | prohibido inicialmente | +| Persistencia SQL | audit_events + audit_changes | +| Reader | server-side con Criteria | +| Arrays | atómicos v1 | +| Create | before={} | +| Delete | after={} | +| No-op mutation | no AuditEntry | +| Lecturas | no audit por defecto | +| Adopción | incremental | + +# Anexo A. Ejemplo de integración + +return this.transactionManager.complete(async transaction => { +const customerResult = await this.customerRepository.findById( +customerId, +transaction, +); +
if (customerResult.isFailure) { +return customerResult; +} +
const customer = customerResult.value; +const before = toCustomerAuditState(customer); +
const updateResult = customer.update(input); +if (updateResult.isFailure) { +return updateResult; +} +
const saveResult = await this.customerRepository.save( +customer, +transaction, +); +
if (saveResult.isFailure) { +return saveResult; +} +
const auditResult = await this.audit.recordMutation({ +action: "customer.updated", +target: toCustomerAuditTarget(customer), +before, +after: toCustomerAuditState(customer), +}); +
if (auditResult.isFailure) { +return auditResult; +} +
return Result.ok(customer); +}); + +# Anexo B. Ejemplo de \`AuditEntry\` + +{ +"id": "01J...", +"kind": "mutation", +"occurredAt": "2026-08-24T17:20:10.123Z", +"source": { "type": "application", "id": "uecko-erp" }, +"operation": { +"id": "OP1", +"correlationId": "CORR1", +"origin": "http" +}, +"actor": { +"type": "user", +"id": "U1", +"label": "" +}, +"scope": { +"type": "company", +"id": "C1", +"label": "Empresa A" +}, +"action": "customer.updated", +"target": { +"type": "customer", +"id": "CU1", +"label": "ACME" +}, +"changes": \[ +{ +"path": "email", +"kind": "change", +"before": "", +"after": "" +} +\] +} \ No newline at end of file diff --git a/modules/catalogs/src/api/infrastructure/payment-methods/di/payment-methods.di.ts b/modules/catalogs/src/api/infrastructure/payment-methods/di/payment-methods.di.ts index 5cbb22e5..406121ba 100644 --- a/modules/catalogs/src/api/infrastructure/payment-methods/di/payment-methods.di.ts +++ b/modules/catalogs/src/api/infrastructure/payment-methods/di/payment-methods.di.ts @@ -47,10 +47,10 @@ export type PaymentMethodsInternalDeps = { export const buildPaymentMethodsDependencies = ( params: ModuleParams ): PaymentMethodsInternalDeps => { - const { database } = params; + const { database, transactionContext } = params; // Infrastructure - const transactionManager = buildTransactionManager(database as Sequelize); + const transactionManager = buildTransactionManager(database as Sequelize, transactionContext); const persistenceMappers = buildPaymentMethodPersistenceMappers(); const repository = buildPaymentMethodRepository({ database, mappers: persistenceMappers }); diff --git a/modules/catalogs/src/api/infrastructure/payment-terms/di/payment-terms.di.ts b/modules/catalogs/src/api/infrastructure/payment-terms/di/payment-terms.di.ts index 9960490f..a31a4fe5 100644 --- a/modules/catalogs/src/api/infrastructure/payment-terms/di/payment-terms.di.ts +++ b/modules/catalogs/src/api/infrastructure/payment-terms/di/payment-terms.di.ts @@ -43,9 +43,9 @@ export type PaymentTermsInternalDeps = { }; export const buildPaymentTermsDependencies = (params: ModuleParams): PaymentTermsInternalDeps => { - const { database } = params; + const { database, transactionContext } = params; - const transactionManager = buildTransactionManager(database as Sequelize); + const transactionManager = buildTransactionManager(database as Sequelize, transactionContext); const persistenceMappers = buildPaymentTermPersistenceMappers(); const repository = buildPaymentTermRepository({ database, mappers: persistenceMappers }); diff --git a/modules/catalogs/src/api/infrastructure/tax-definitions/di/tax-definitions.di.ts b/modules/catalogs/src/api/infrastructure/tax-definitions/di/tax-definitions.di.ts index 18040f4a..524e2e56 100644 --- a/modules/catalogs/src/api/infrastructure/tax-definitions/di/tax-definitions.di.ts +++ b/modules/catalogs/src/api/infrastructure/tax-definitions/di/tax-definitions.di.ts @@ -43,9 +43,9 @@ export type TaxDefinitionsInternalDeps = { export const buildTaxDefinitionsDependencies = ( params: ModuleParams ): TaxDefinitionsInternalDeps => { - const { database } = params; + const { database, transactionContext } = params; - const transactionManager = buildTransactionManager(database as Sequelize); + const transactionManager = buildTransactionManager(database as Sequelize, transactionContext); const persistenceMappers = buildTaxDefinitionPersistenceMappers(); const repository = buildTaxDefinitionRepository({ database, mappers: persistenceMappers }); diff --git a/modules/catalogs/src/api/infrastructure/tax-regimes/di/tax-regimes.di.ts b/modules/catalogs/src/api/infrastructure/tax-regimes/di/tax-regimes.di.ts index 2e7ed0cc..d8fbac1b 100644 --- a/modules/catalogs/src/api/infrastructure/tax-regimes/di/tax-regimes.di.ts +++ b/modules/catalogs/src/api/infrastructure/tax-regimes/di/tax-regimes.di.ts @@ -40,9 +40,9 @@ export type TaxRegimesInternalDeps = { }; export const buildTaxRegimesDependencies = (params: ModuleParams): TaxRegimesInternalDeps => { - const { database } = params; + const { database, transactionContext } = params; - const transactionManager = buildTransactionManager(database as Sequelize); + const transactionManager = buildTransactionManager(database as Sequelize, transactionContext); const persistenceMappers = buildTaxRegimePersistenceMappers(); const repository = buildTaxRegimeRepository({ database, mappers: persistenceMappers }); diff --git a/modules/companies/src/api/infrastructure/di/companies.di.ts b/modules/companies/src/api/infrastructure/di/companies.di.ts index 93e41249..0de8ba46 100644 --- a/modules/companies/src/api/infrastructure/di/companies.di.ts +++ b/modules/companies/src/api/infrastructure/di/companies.di.ts @@ -40,8 +40,8 @@ export type CompaniesInternalDeps = { }; export const buildCompaniesDependencies = (params: ModuleParams): CompaniesInternalDeps => { - const { database, getService } = params; - const transactionManager = buildTransactionManager(database as Sequelize); + const { database, getService, transactionContext } = params; + const transactionManager = buildTransactionManager(database as Sequelize, transactionContext); const responseMapper = new CompanyPublicModelMapper(); const identityServices = getService("identity:general"); const catalogsDeps = resolveCompanyCatalogsDeps(params); diff --git a/modules/core/package.json b/modules/core/package.json index 68c39466..22e8b6c1 100644 --- a/modules/core/package.json +++ b/modules/core/package.json @@ -5,6 +5,7 @@ "type": "module", "sideEffects": false, "scripts": { + "test": "node --import tsx --test", "typecheck": "tsc -p tsconfig.json --noEmit", "check": "biome check .", "lint": "biome lint .", @@ -27,6 +28,7 @@ "@hookform/devtools": "^4.4.0", "@types/dinero.js": "1.9.1", "@types/express": "^4.17.21", + "@types/node": "^25.6.0", "@types/mime-types": "^3.0.1", "@types/react": "^19.2.14", "react": "^19.2.5", @@ -40,6 +42,7 @@ "@repo/rdx-criteria": "workspace:*", "@repo/rdx-ddd": "workspace:*", "@repo/rdx-logger": "workspace:*", + "@repo/rdx-audit": "workspace:*", "@repo/rdx-ui": "workspace:*", "@repo/rdx-utils": "workspace:*", "@repo/shadcn-ui": "workspace:*", @@ -56,4 +59,4 @@ "sequelize": "^6.37.8", "zod": "^4.3.6" } -} \ No newline at end of file +} diff --git a/modules/core/src/api/infrastructure/context/index.ts b/modules/core/src/api/infrastructure/context/index.ts new file mode 100644 index 00000000..c3cd1bf1 --- /dev/null +++ b/modules/core/src/api/infrastructure/context/index.ts @@ -0,0 +1,3 @@ +export * from "./operation-audit-context-provider"; +export * from "./operation-context.types"; +export * from "./operation-context-store"; diff --git a/modules/core/src/api/infrastructure/context/operation-audit-context-provider.test.ts b/modules/core/src/api/infrastructure/context/operation-audit-context-provider.test.ts new file mode 100644 index 00000000..1ad3e6f4 --- /dev/null +++ b/modules/core/src/api/infrastructure/context/operation-audit-context-provider.test.ts @@ -0,0 +1,55 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { OperationAuditContextProvider } from "./operation-audit-context-provider"; +import { AsyncLocalStorageOperationContextStore } from "./operation-context-store"; + +test("OperationAuditContextProvider devuelve null fuera de una operación", () => { + const provider = new OperationAuditContextProvider(new AsyncLocalStorageOperationContextStore()); + + assert.equal(provider.get(), null); +}); + +test("OperationAuditContextProvider adapta el contexto técnico al puerto de auditoría", () => { + const store = new AsyncLocalStorageOperationContextStore(); + const provider = new OperationAuditContextProvider(store); + + store.run( + { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "http", + actor: { type: "user", id: "user-1", label: "Ada" }, + scope: { type: "company", id: "company-1", label: "ACME" }, + }, + () => { + assert.deepEqual(provider.get(), { + operation: { + id: "operation-1", + correlationId: "correlation-1", + origin: "http", + }, + actor: { type: "user", id: "user-1", label: "Ada" }, + scope: { type: "company", id: "company-1", label: "ACME" }, + }); + } + ); +}); + +test("OperationAuditContextProvider conserva scope null", () => { + const store = new AsyncLocalStorageOperationContextStore(); + const provider = new OperationAuditContextProvider(store); + + store.run( + { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "job", + actor: { type: "system", id: "scheduler", label: null }, + scope: null, + }, + () => { + assert.equal(provider.get()?.scope, null); + } + ); +}); diff --git a/modules/core/src/api/infrastructure/context/operation-audit-context-provider.ts b/modules/core/src/api/infrastructure/context/operation-audit-context-provider.ts new file mode 100644 index 00000000..80ed6a9c --- /dev/null +++ b/modules/core/src/api/infrastructure/context/operation-audit-context-provider.ts @@ -0,0 +1,23 @@ +import type { AuditContext, IAuditContextProvider } from "@repo/rdx-audit"; + +import type { IOperationContextStore } from "./operation-context.types"; + +/** Adapta el contexto técnico de operación al puerto portable de auditoría. */ +export class OperationAuditContextProvider implements IAuditContextProvider { + constructor(private readonly contextStore: IOperationContextStore) {} + + get(): AuditContext | null { + const context = this.contextStore.get(); + if (!context) return null; + + return { + operation: { + id: context.operationId, + correlationId: context.correlationId, + origin: context.origin, + }, + actor: { ...context.actor }, + scope: context.scope ? { ...context.scope } : null, + }; + } +} diff --git a/modules/core/src/api/infrastructure/context/operation-context-store.test.ts b/modules/core/src/api/infrastructure/context/operation-context-store.test.ts new file mode 100644 index 00000000..4ebee466 --- /dev/null +++ b/modules/core/src/api/infrastructure/context/operation-context-store.test.ts @@ -0,0 +1,94 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { OperationContext } from "./operation-context.types"; +import { + AsyncLocalStorageOperationContextStore, + OperationContextEnrichmentError, + OperationContextUnavailableError, +} from "./operation-context-store"; + +const anonymousContext: OperationContext = { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "http", + actor: { + type: "anonymous", + id: null, + label: null, + }, + scope: null, +}; + +test("OperationContext conserva el contexto tras await y restaura scopes anidados", async () => { + const store = new AsyncLocalStorageOperationContextStore(); + const innerContext: OperationContext = { + ...anonymousContext, + operationId: "operation-2", + correlationId: "correlation-2", + }; + + await store.run(anonymousContext, async () => { + assert.equal(store.get()?.operationId, "operation-1"); + await Promise.resolve(); + assert.equal(store.get()?.operationId, "operation-1"); + + await store.run(innerContext, async () => { + assert.equal(store.get()?.operationId, "operation-2"); + await Promise.resolve(); + assert.equal(store.get()?.operationId, "operation-2"); + }); + + assert.equal(store.get()?.operationId, "operation-1"); + }); + + assert.equal(store.get(), null); +}); + +test("OperationContext restaura el contexto exterior cuando el interior falla", async () => { + const store = new AsyncLocalStorageOperationContextStore(); + + await store.run(anonymousContext, async () => { + await assert.rejects( + () => + store.run( + { ...anonymousContext, operationId: "operation-2", correlationId: "correlation-2" }, + async () => { + throw new Error("inner failure"); + } + ), + /inner failure/ + ); + + assert.equal(store.get()?.operationId, "operation-1"); + }); +}); + +test("OperationContext permite enriquecer anonymous y scope una sola vez", () => { + const store = new AsyncLocalStorageOperationContextStore(); + + assert.throws(() => store.setActor({ type: "user", id: "user-1", label: "Ada" }), { + name: OperationContextUnavailableError.name, + }); + assert.throws(() => store.setScope({ type: "company", id: "company-1", label: "ACME" }), { + name: OperationContextUnavailableError.name, + }); + + store.run(anonymousContext, () => { + store.setActor({ type: "user", id: "user-1", label: "Ada" }); + store.setActor({ type: "user", id: "user-1", label: "Other label" }); + store.setScope({ type: "company", id: "company-1", label: "ACME" }); + store.setScope({ type: "company", id: "company-1", label: "Other label" }); + + assert.deepEqual(store.get()?.actor, { type: "user", id: "user-1", label: "Ada" }); + assert.deepEqual(store.get()?.scope, { type: "company", id: "company-1", label: "ACME" }); + assert.throws( + () => store.setActor({ type: "user", id: "user-2", label: "Grace" }), + OperationContextEnrichmentError + ); + assert.throws( + () => store.setScope({ type: "company", id: "company-2", label: "Other" }), + OperationContextEnrichmentError + ); + }); +}); diff --git a/modules/core/src/api/infrastructure/context/operation-context-store.ts b/modules/core/src/api/infrastructure/context/operation-context-store.ts new file mode 100644 index 00000000..8cdb3e7d --- /dev/null +++ b/modules/core/src/api/infrastructure/context/operation-context-store.ts @@ -0,0 +1,128 @@ +import { AsyncLocalStorage } from "node:async_hooks"; + +import { InfrastructureError } from "../errors"; + +import type { + IOperationContextStore, + OperationActor, + OperationContext, + OperationScope, +} from "./operation-context.types"; + +type ContextHolder = { context: OperationContext }; + +export class OperationContextUnavailableError extends InfrastructureError { + constructor() { + super("[OperationContext] No active operation context."); + this.name = new.target.name; + } +} + +export class OperationContextEnrichmentError extends InfrastructureError { + constructor(message: string) { + super(message); + this.name = new.target.name; + } +} + +/** + * Mantiene metadatos de una operación asíncrona. Solo actor y scope pueden + * enriquecerse; los identificadores de operación permanecen inmutables. + */ +export class AsyncLocalStorageOperationContextStore implements IOperationContextStore { + private readonly storage = new AsyncLocalStorage(); + + run(context: OperationContext, callback: () => T): T { + return this.storage.run({ context: cloneContext(context) }, callback); + } + + get(): OperationContext | null { + return this.storage.getStore()?.context ?? null; + } + + setActor(actor: OperationActor): void { + const holder = this.getHolder(); + assertActor(actor); + const currentActor = holder.context.actor; + + if (sameActor(currentActor, actor)) return; + if (!isAnonymous(currentActor)) { + throw new OperationContextEnrichmentError( + "[OperationContext] A concrete actor cannot be replaced." + ); + } + + holder.context = { ...holder.context, actor: { ...actor } }; + } + + setScope(scope: OperationScope): void { + const holder = this.getHolder(); + assertScope(scope); + const currentScope = holder.context.scope; + + if (currentScope && sameScope(currentScope, scope)) return; + if (currentScope) { + throw new OperationContextEnrichmentError( + "[OperationContext] A concrete scope cannot be replaced." + ); + } + + holder.context = { ...holder.context, scope: { ...scope } }; + } + + private getHolder(): ContextHolder { + const holder = this.storage.getStore(); + if (!holder) throw new OperationContextUnavailableError(); + return holder; + } +} + +function cloneContext(context: OperationContext): OperationContext { + assertNonEmpty(context.operationId, "operationId"); + assertNonEmpty(context.correlationId, "correlationId"); + assertNonEmpty(context.origin, "origin"); + assertActor(context.actor); + if (context.scope) assertScope(context.scope); + + return { + operationId: context.operationId, + correlationId: context.correlationId, + origin: context.origin, + actor: { ...context.actor }, + scope: context.scope ? { ...context.scope } : null, + }; +} + +function assertActor(actor: OperationActor): void { + assertNonEmpty(actor.type, "actor.type"); + assertNullableValue(actor.id, "actor.id"); + assertNullableValue(actor.label, "actor.label"); +} + +function assertScope(scope: OperationScope): void { + assertNonEmpty(scope.type, "scope.type"); + assertNonEmpty(scope.id, "scope.id"); + assertNullableValue(scope.label, "scope.label"); +} + +function assertNullableValue(value: string | null, name: string): void { + if (value !== null) assertNonEmpty(value, name); +} + +function assertNonEmpty(value: string, name: string): void { + if (typeof value !== "string" || !value.trim()) { + throw new OperationContextEnrichmentError(`[OperationContext] ${name} is required.`); + } +} + +function isAnonymous(actor: OperationActor): boolean { + return actor.type === "anonymous" && actor.id === null; +} + +function sameActor(left: OperationActor, right: OperationActor): boolean { + return left.type === right.type && left.id === right.id; +} + +function sameScope(left: OperationScope, right: OperationScope): boolean { + return left.type === right.type && left.id === right.id; +} diff --git a/modules/core/src/api/infrastructure/context/operation-context.types.ts b/modules/core/src/api/infrastructure/context/operation-context.types.ts new file mode 100644 index 00000000..7e1bfa32 --- /dev/null +++ b/modules/core/src/api/infrastructure/context/operation-context.types.ts @@ -0,0 +1,27 @@ +export interface OperationActor { + readonly type: string; + readonly id: string | null; + readonly label: string | null; +} + +export interface OperationScope { + readonly type: string; + readonly id: string; + readonly label: string | null; +} + +/** Describe la operación actual, sin conocer transporte, dominio ni persistencia. */ +export interface OperationContext { + readonly operationId: string; + readonly correlationId: string; + readonly origin: string; + readonly actor: OperationActor; + readonly scope: OperationScope | null; +} + +export interface IOperationContextStore { + run(context: OperationContext, callback: () => T): T; + get(): OperationContext | null; + setActor(actor: OperationActor): void; + setScope(scope: OperationScope): void; +} diff --git a/modules/core/src/api/infrastructure/di/transactions.di.ts b/modules/core/src/api/infrastructure/di/transactions.di.ts index 74bc7c1b..b6f5a7b2 100644 --- a/modules/core/src/api/infrastructure/di/transactions.di.ts +++ b/modules/core/src/api/infrastructure/di/transactions.di.ts @@ -1,6 +1,9 @@ import type { Sequelize } from "sequelize"; +import type { ISequelizeTransactionContext } from "../persistence/sequelize"; import { SequelizeTransactionManager } from "../persistence/sequelize"; -export const buildTransactionManager = (database: Sequelize) => - new SequelizeTransactionManager(database); +export const buildTransactionManager = ( + database: Sequelize, + transactionContext: ISequelizeTransactionContext +) => new SequelizeTransactionManager(database, transactionContext); diff --git a/modules/core/src/api/infrastructure/errors/index.ts b/modules/core/src/api/infrastructure/errors/index.ts index 95be62e0..b1a19b16 100644 --- a/modules/core/src/api/infrastructure/errors/index.ts +++ b/modules/core/src/api/infrastructure/errors/index.ts @@ -1,3 +1,4 @@ export * from "./infrastructure-errors"; export * from "./infrastructure-repository-error"; export * from "./infrastructure-unavailable-error"; +export * from "./nested-transaction-not-allowed-error"; diff --git a/modules/core/src/api/infrastructure/errors/nested-transaction-not-allowed-error.ts b/modules/core/src/api/infrastructure/errors/nested-transaction-not-allowed-error.ts new file mode 100644 index 00000000..43cdaca8 --- /dev/null +++ b/modules/core/src/api/infrastructure/errors/nested-transaction-not-allowed-error.ts @@ -0,0 +1,7 @@ +import { InfrastructureError } from "./infrastructure-errors"; + +export class NestedTransactionNotAllowedError extends InfrastructureError { + constructor() { + super("[SequelizeTransactionManager] Nested transactions are not allowed."); + } +} diff --git a/modules/core/src/api/infrastructure/express/middlewares/index.ts b/modules/core/src/api/infrastructure/express/middlewares/index.ts index b15e3be9..54119a43 100644 --- a/modules/core/src/api/infrastructure/express/middlewares/index.ts +++ b/modules/core/src/api/infrastructure/express/middlewares/index.ts @@ -1,2 +1,3 @@ export * from "./global-error-handler"; +export * from "./operation-context.middleware"; export * from "./validate-request.middleware"; diff --git a/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.test.ts b/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.test.ts new file mode 100644 index 00000000..6bac4993 --- /dev/null +++ b/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.test.ts @@ -0,0 +1,73 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { Request, Response } from "express"; + +import { + AsyncLocalStorageOperationContextStore, + OperationAuditContextProvider, +} from "../../context"; + +import { createOperationContextMiddleware } from "./operation-context.middleware"; + +type CapturedRequest = { + readonly context: ReturnType; + readonly headers: Map; +}; + +async function executeRequest(correlationId?: string): Promise { + const store = new AsyncLocalStorageOperationContextStore(); + const provider = new OperationAuditContextProvider(store); + const headers = new Map(); + const middleware = createOperationContextMiddleware(store); + + const context = await new Promise>((resolve) => { + middleware( + { + get: (name: string) => (name === "x-correlation-id" ? correlationId : undefined), + } as Request, + { + setHeader: (name: string, value: string) => headers.set(name.toLowerCase(), value), + } as unknown as Response, + () => { + Promise.resolve().then(() => resolve(provider.get())); + } + ); + }); + + return { context, headers }; +} + +test("OperationContext HTTP genera identificadores y permanece disponible tras await", async () => { + const result = await executeRequest(); + + assert.equal(result.context?.operation.origin, "http"); + assert.deepEqual(result.context?.actor, { type: "anonymous", id: null, label: null }); + assert.equal(result.context?.scope, null); + assert.ok(result.context?.operation.id); + assert.ok(result.context?.operation.correlationId); + assert.notEqual(result.context?.operation.id, result.context?.operation.correlationId); + assert.equal(result.headers.get("x-request-id"), result.context?.operation.id); + assert.equal(result.headers.get("x-correlation-id"), result.context?.operation.correlationId); +}); + +test("OperationContext HTTP propaga correlation ID válido y reemplaza uno inválido", async () => { + const valid = await executeRequest("external-123"); + const invalid = await executeRequest("x".repeat(201)); + + assert.equal(valid.context?.operation.correlationId, "external-123"); + assert.equal(valid.headers.get("x-correlation-id"), "external-123"); + assert.ok(invalid.context?.operation.correlationId); + assert.notEqual(invalid.context?.operation.correlationId, "x".repeat(201)); +}); + +test("OperationContext HTTP aísla requests concurrentes", async () => { + const [first, second] = await Promise.all([ + executeRequest("correlation-a"), + executeRequest("correlation-b"), + ]); + + assert.equal(first.context?.operation.correlationId, "correlation-a"); + assert.equal(second.context?.operation.correlationId, "correlation-b"); + assert.notEqual(first.context?.operation.id, second.context?.operation.id); +}); diff --git a/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.ts b/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.ts new file mode 100644 index 00000000..1061ff81 --- /dev/null +++ b/modules/core/src/api/infrastructure/express/middlewares/operation-context.middleware.ts @@ -0,0 +1,62 @@ +import { randomUUID } from "node:crypto"; + +import type { RequestHandler } from "express"; + +import type { IOperationContextStore } from "../../context"; + +const CORRELATION_ID_HEADER = "x-correlation-id"; +const MAX_CORRELATION_ID_LENGTH = 200; + +/** + * Crea el scope técnico de cada petición HTTP antes de que intervengan parsers, + * autenticación, rutas o manejo de errores. + */ +export function createOperationContextMiddleware( + operationContext: IOperationContextStore +): RequestHandler { + return (req, res, next) => { + const operationId = randomUUID(); + const correlationId = getCorrelationId(req.get(CORRELATION_ID_HEADER)); + + res.setHeader("X-Request-Id", operationId); + res.setHeader("X-Correlation-Id", correlationId); + + operationContext.run( + { + operationId, + correlationId, + origin: "http", + actor: { + type: "anonymous", + id: null, + label: null, + }, + scope: null, + }, + next + ); + }; +} + +function getCorrelationId(headerValue: string | undefined): string { + if (isValidCorrelationId(headerValue)) return headerValue.trim(); + return randomUUID(); +} + +function isValidCorrelationId(value: string | undefined): value is string { + if (!value) return false; + + const trimmed = value.trim(); + return ( + trimmed.length > 0 && + trimmed.length <= MAX_CORRELATION_ID_LENGTH && + !containsControlCharacter(value) + ); +} + +function containsControlCharacter(value: string): boolean { + return Array.from(value).some((character) => { + const code = character.charCodeAt(0); + return code < 32 || code === 127; + }); +} diff --git a/modules/core/src/api/infrastructure/index.ts b/modules/core/src/api/infrastructure/index.ts index 595b159f..ff3a5dbc 100644 --- a/modules/core/src/api/infrastructure/index.ts +++ b/modules/core/src/api/infrastructure/index.ts @@ -1,4 +1,5 @@ export * from "./config"; +export * from "./context"; export * from "./database"; export * from "./di"; export * from "./documents"; diff --git a/modules/core/src/api/infrastructure/persistence/sequelize/index.ts b/modules/core/src/api/infrastructure/persistence/sequelize/index.ts index 83d01dd1..b172a3c9 100644 --- a/modules/core/src/api/infrastructure/persistence/sequelize/index.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/index.ts @@ -3,3 +3,4 @@ export * from "./sequelize-error-translator"; export * from "./sequelize-func"; export * from "./sequelize-repository"; export * from "./sequelize-transaction-manager"; +export * from "./sequelize-transaction-context"; diff --git a/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.test.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.test.ts new file mode 100644 index 00000000..7fcaa2ae --- /dev/null +++ b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.test.ts @@ -0,0 +1,136 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { Result } from "@repo/rdx-utils"; +import type { Sequelize, Transaction } from "sequelize"; + +import { NestedTransactionNotAllowedError } from "../../errors"; +import { SequelizeTransactionContext } from "./sequelize-transaction-context"; +import { SequelizeTransactionManager } from "./sequelize-transaction-manager"; + +function createTransaction(): Transaction { + return {} as Transaction; +} + +function createDatabase(transaction: Transaction): { + database: Sequelize; + getTransactionCalls(): number; + getCommits(): number; + getRollbacks(): number; +} { + let transactionCalls = 0; + let commits = 0; + let rollbacks = 0; + + const database = { + transaction: async (work: (currentTransaction: Transaction) => Promise): Promise => { + transactionCalls += 1; + try { + const result = await work(transaction); + commits += 1; + return result; + } catch (error) { + rollbacks += 1; + throw error; + } + }, + } as Sequelize; + + return { + database, + getTransactionCalls: () => transactionCalls, + getCommits: () => commits, + getRollbacks: () => rollbacks, + }; +} + +test("SequelizeTransactionContext propaga y restaura la transacción activa", async () => { + const context = new SequelizeTransactionContext(); + const firstTransaction = createTransaction(); + const secondTransaction = createTransaction(); + + await context.run(firstTransaction, async () => { + assert.equal(context.current(), firstTransaction); + await Promise.resolve(); + assert.equal(context.current(), firstTransaction); + + await context.run(secondTransaction, async () => { + assert.equal(context.current(), secondTransaction); + }); + + assert.equal(context.current(), firstTransaction); + }); + + assert.equal(context.current(), null); + + await assert.rejects( + () => + context.run(firstTransaction, async () => { + throw new Error("transaction context failure"); + }), + /transaction context failure/ + ); + assert.equal(context.current(), null); +}); + +test("SequelizeTransactionManager publica la transacción y realiza commit al resolver", async () => { + const context = new SequelizeTransactionContext(); + const transaction = createTransaction(); + const fakeDatabase = createDatabase(transaction); + const manager = new SequelizeTransactionManager(fakeDatabase.database, context); + + const result = await manager.complete(async (receivedTransaction) => { + assert.equal(receivedTransaction, transaction); + assert.equal(context.current(), transaction); + await Promise.resolve(); + assert.equal(context.current(), transaction); + return "completed"; + }); + + assert.equal(result, "completed"); + assert.equal(fakeDatabase.getCommits(), 1); + assert.equal(fakeDatabase.getRollbacks(), 0); + assert.equal(context.current(), null); +}); + +test("SequelizeTransactionManager provoca rollback cuando el callback lanza o devuelve Result.fail", async () => { + const context = new SequelizeTransactionContext(); + const fakeDatabase = createDatabase(createTransaction()); + const manager = new SequelizeTransactionManager(fakeDatabase.database, context); + + await assert.rejects( + () => + manager.complete(async () => { + throw new Error("work failure"); + }), + /work failure/ + ); + await assert.rejects( + () => manager.complete(async () => Result.fail(new Error("result failure"))), + /result failure/ + ); + + assert.equal(fakeDatabase.getCommits(), 0); + assert.equal(fakeDatabase.getRollbacks(), 2); + assert.equal(context.current(), null); +}); + +test("SequelizeTransactionManager rechaza transacciones anidadas entre managers", async () => { + const context = new SequelizeTransactionContext(); + const fakeDatabase = createDatabase(createTransaction()); + const firstManager = new SequelizeTransactionManager(fakeDatabase.database, context); + const secondManager = new SequelizeTransactionManager(fakeDatabase.database, context); + + await firstManager.complete(async () => { + await assert.rejects( + () => secondManager.complete(async () => "unexpected"), + NestedTransactionNotAllowedError + ); + return "completed"; + }); + + assert.equal(fakeDatabase.getTransactionCalls(), 1); + assert.equal(fakeDatabase.getCommits(), 1); + assert.equal(fakeDatabase.getRollbacks(), 0); + assert.equal(context.current(), null); +}); diff --git a/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.ts new file mode 100644 index 00000000..e794fd96 --- /dev/null +++ b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-context.ts @@ -0,0 +1,21 @@ +import { AsyncLocalStorage } from "node:async_hooks"; + +import type { Transaction } from "sequelize"; + +/** Contexto técnico de corta duración que expone exclusivamente la transacción activa. */ +export interface ISequelizeTransactionContext { + run(transaction: Transaction, callback: () => T): T; + current(): Transaction | null; +} + +export class SequelizeTransactionContext implements ISequelizeTransactionContext { + private readonly storage = new AsyncLocalStorage(); + + run(transaction: Transaction, callback: () => T): T { + return this.storage.run(transaction, callback); + } + + current(): Transaction | null { + return this.storage.getStore() ?? null; + } +} diff --git a/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts index 43805760..ad9df10e 100644 --- a/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts +++ b/modules/core/src/api/infrastructure/persistence/sequelize/sequelize-transaction-manager.ts @@ -2,9 +2,15 @@ import { Result } from "@repo/rdx-utils"; import { type Sequelize, Transaction } from "sequelize"; import { TransactionManager } from "../../database"; -import { InfrastructureError, InfrastructureUnavailableError } from "../../errors"; +import { + InfrastructureError, + InfrastructureUnavailableError, + NestedTransactionNotAllowedError, +} from "../../errors"; import { logger } from "../../logger"; +import type { ISequelizeTransactionContext } from "./sequelize-transaction-context"; + export class SequelizeTransactionManager extends TransactionManager { protected _database: Sequelize | null = null; private isolationLevel: Transaction.ISOLATION_LEVELS; @@ -26,7 +32,11 @@ export class SequelizeTransactionManager extends TransactionManager { throw new InfrastructureError("[SequelizeTransactionManager] Database not available"); } - constructor(database: Sequelize, options?: { isolationLevel?: Transaction.ISOLATION_LEVELS }) { + constructor( + database: Sequelize, + private readonly transactionContext: ISequelizeTransactionContext, + options?: { isolationLevel?: Transaction.ISOLATION_LEVELS } + ) { super(); this._database = database; this.isolationLevel = options?.isolationLevel ?? Transaction.ISOLATION_LEVELS.READ_COMMITTED; @@ -52,27 +62,28 @@ export class SequelizeTransactionManager extends TransactionManager { ); } - // Evita transacciones anidadas según la política del TransactionManager base - if (this._transaction) { + // `_transaction` protege la API manual heredada; el contexto compartido cubre + // transacciones abiertas por otros managers. La transacción funcional sigue explícita. + if (this._transaction || this.transactionContext.current()) { logger.error( "❌ Cannot start a new transaction inside another. Nested transactions are not allowed.", { label: "SequelizeTransactionManager.complete" } ); - throw new InfrastructureError( - "[SequelizeTransactionManager] A transaction is already active. Nested transactions are not allowed." - ); + throw new NestedTransactionNotAllowedError(); } try { // Usa la forma gestionada: si `work` resuelve => commit, si lanza excepción => rollback. - const result = await this._database.transaction(async (t) => { - const workResult = await work(t); - if (workResult instanceof Result && workResult.isFailure) { - // Forzamos rollback propagando el error - throw workResult.error; - } - return workResult; - }); + const result = await this._database.transaction(async (transaction) => + this.transactionContext.run(transaction, async () => { + const workResult = await work(transaction); + if (workResult instanceof Result && workResult.isFailure) { + // Forzamos rollback propagando el error. + throw workResult.error; + } + return workResult; + }) + ); return result as T; } catch (err) { const error = err as Error; diff --git a/modules/core/src/api/modules/types.ts b/modules/core/src/api/modules/types.ts index 11d9c49c..f3125e80 100644 --- a/modules/core/src/api/modules/types.ts +++ b/modules/core/src/api/modules/types.ts @@ -1,5 +1,9 @@ +import type { IAudit } from "@repo/rdx-audit"; import type { Model, ModelStatic, Sequelize } from "sequelize"; +import type { IOperationContextStore } from "../infrastructure/context"; +import type { ISequelizeTransactionContext } from "../infrastructure/persistence/sequelize"; + export interface SequelizeModel extends Model { initialize: (sequelize: Sequelize) => void; associate?: (models: { [key: string]: ModelStatic }) => void; @@ -12,6 +16,15 @@ export type ModuleParams = { */ getService: (serviceName: string) => T; + /** Contexto técnico compartido para publicar la transacción Sequelize activa. */ + transactionContext: ISequelizeTransactionContext; + + /** Contexto técnico compartido para describir la operación activa. */ + operationContext: IOperationContextStore; + + /** Puerto transversal para registrar auditoría sin acoplar módulos a su infraestructura. */ + audit: IAudit; + [key: string]: any; }; diff --git a/modules/customer-invoices/src/api/infrastructure/company-verifactu-profile/di/company-verifactu-profile.di.ts b/modules/customer-invoices/src/api/infrastructure/company-verifactu-profile/di/company-verifactu-profile.di.ts index 582b41d2..c82dc9dc 100644 --- a/modules/customer-invoices/src/api/infrastructure/company-verifactu-profile/di/company-verifactu-profile.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/company-verifactu-profile/di/company-verifactu-profile.di.ts @@ -22,7 +22,7 @@ export type CompanyVerifactuProfileInternalDeps = { export function buildCompanyVerifactuProfileDependencies( params: ModuleParams ): CompanyVerifactuProfileInternalDeps { - const transactionManager = buildTransactionManager(params.database); + const transactionManager = buildTransactionManager(params.database, params.transactionContext); const repository = new SequelizeCompanyVerifactuProfileRepository( new CompanyVerifactuProfileDomainMapper(), params.database diff --git a/modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.ts b/modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.ts index eeea8b6f..239b1c4c 100644 --- a/modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/issued-invoices/di/issued-invoices.di.ts @@ -23,10 +23,10 @@ export type IssuedInvoicesInternalDeps = { }; export function buildIssuedInvoicesDependencies(params: ModuleParams): IssuedInvoicesInternalDeps { - const { database } = params; + const { database, transactionContext } = params; // Infrastructure - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const catalogs = buildCatalogs(); /** * Fase 1C: IssuedInvoice queda conectada a V2. diff --git a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts index dcdcded8..b2376602 100644 --- a/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts +++ b/modules/customer-invoices/src/api/infrastructure/proformas/di/proformas.di.ts @@ -79,7 +79,7 @@ export type ProformasInternalDeps = { }; export function buildProformasDependencies(params: ModuleParams): ProformasInternalDeps { - const { database } = params; + const { database, transactionContext } = params; const catalogs = resolveProformaCatalogsDeps(params); const companiesServices = params.getService("companies:general"); @@ -87,7 +87,7 @@ export function buildProformasDependencies(params: ModuleParams): ProformasInter const documentSeriesServices = params.getService("document-series:general"); - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); /** * Fase 1C: el wiring operativo principal ya usa V2. * Requiere que existan `proformas`, `proforma_items` y `proforma_taxes` en la BD real. diff --git a/modules/customers/src/api/infrastructure/di/customers.di.ts b/modules/customers/src/api/infrastructure/di/customers.di.ts index 9757a73b..a70c2ef7 100644 --- a/modules/customers/src/api/infrastructure/di/customers.di.ts +++ b/modules/customers/src/api/infrastructure/di/customers.di.ts @@ -42,12 +42,12 @@ export type CustomersInternalDeps = { }; export function buildCustomersDependencies(params: ModuleParams): CustomersInternalDeps { - const { database } = params; + const { database, transactionContext } = params; const catalogs = resolveCustomerCatalogsDeps(params); const companies = resolveCustomerCompanyDeps(params); - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const persistenceMappers = buildCustomerPersistenceMappers(); const repository = buildCustomerRepository({ database, mappers: persistenceMappers }); diff --git a/modules/document-series/src/api/infrastructure/di/document-series.di.ts b/modules/document-series/src/api/infrastructure/di/document-series.di.ts index 3219154f..167de082 100644 --- a/modules/document-series/src/api/infrastructure/di/document-series.di.ts +++ b/modules/document-series/src/api/infrastructure/di/document-series.di.ts @@ -15,9 +15,9 @@ export type DocumentSeriesInternalDeps = { }; export function buildDocumentSeriesDependencies(params: ModuleParams): DocumentSeriesInternalDeps { - const { database } = params; + const { database, transactionContext } = params; - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const mapper = new SequelizeDocumentSeriesDomainMapper(); const repository: IDocumentSeriesRepository = new SequelizeDocumentSeriesRepository( mapper, diff --git a/modules/document-series/src/api/infrastructure/express/document-series.routes.ts b/modules/document-series/src/api/infrastructure/express/document-series.routes.ts index c65e46cc..af1d7e76 100644 --- a/modules/document-series/src/api/infrastructure/express/document-series.routes.ts +++ b/modules/document-series/src/api/infrastructure/express/document-series.routes.ts @@ -21,10 +21,10 @@ import { } from "./controllers"; export const documentSeriesRouter = (params: StartParams) => { - const { app, config, getInternal, database } = params; + const { app, config, getInternal, database, transactionContext } = params; const deps = getInternal("document-series"); const router: Router = Router({ mergeParams: true }); - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); router.use(...requireIdentityTenant(params)); diff --git a/modules/factuges/src/api/infraestructure/di/factuges.di.ts b/modules/factuges/src/api/infraestructure/di/factuges.di.ts index 11e88e15..cf7aa7ab 100644 --- a/modules/factuges/src/api/infraestructure/di/factuges.di.ts +++ b/modules/factuges/src/api/infraestructure/di/factuges.di.ts @@ -26,12 +26,12 @@ export type FactugesInternalDeps = { }; export function buildFactugesDependencies(params: SetupParams): FactugesInternalDeps { - const { database } = params; + const { database, transactionContext } = params; const catalogs = resolveFactuGESCatalogsDeps(params); // Infrastructure - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const inputMappers = buildFactugesInputMappers(); const persistenceMappers = buildFactuGESPersistenceMappers(); diff --git a/modules/identity/src/api/application/services/identity-auth-public-services.interface.ts b/modules/identity/src/api/application/services/identity-auth-public-services.interface.ts index 53f0f532..a824346a 100644 --- a/modules/identity/src/api/application/services/identity-auth-public-services.interface.ts +++ b/modules/identity/src/api/application/services/identity-auth-public-services.interface.ts @@ -1,3 +1,4 @@ +import type { IOperationContextStore } from "@erp/core/api"; import type { Result } from "@repo/rdx-utils"; import type { RequestHandler } from "express"; @@ -15,9 +16,12 @@ export interface IIdentityTenantCompanyFinder { } export interface IIdentityAuthPublicServices { - authenticatedOnly(): AuthenticatedOnlyMiddlewares; + authenticatedOnly(params: { + operationContext: IOperationContextStore; + }): AuthenticatedOnlyMiddlewares; tenantRequired(params: { companyAccessChecker: IIdentityCompanyAccessChecker; companyFinder: IIdentityTenantCompanyFinder; + operationContext: IOperationContextStore; }): TenantRequiredMiddlewares; } diff --git a/modules/identity/src/api/infrastructure/di/identity-public-services.ts b/modules/identity/src/api/infrastructure/di/identity-public-services.ts index 13b809b4..9722d8f9 100644 --- a/modules/identity/src/api/infrastructure/di/identity-public-services.ts +++ b/modules/identity/src/api/infrastructure/di/identity-public-services.ts @@ -1,3 +1,5 @@ +import type { IOperationContextStore } from "@erp/core/api"; + import type { IIdentityAuthPublicServices, IIdentityCompanyAccessChecker, @@ -17,16 +19,20 @@ class IdentityAuthPublicServices implements IIdentityAuthPublicServices { private readonly deps: IdentityInternalDeps["auth"]["authenticateUserDependencies"] ) {} - public authenticatedOnly() { - return [authenticateUser(this.deps), requireAuthenticated()] as const; + public authenticatedOnly(params: { operationContext: IOperationContextStore }) { + return [ + authenticateUser({ ...this.deps, operationContext: params.operationContext }), + requireAuthenticated(), + ] as const; } public tenantRequired(params: { companyAccessChecker: IIdentityCompanyAccessChecker; companyFinder: IIdentityTenantCompanyFinder; + operationContext: IOperationContextStore; }) { return [ - authenticateUser(this.deps), + authenticateUser({ ...this.deps, operationContext: params.operationContext }), requireAuthenticated(), requireCompanyContext(params), ] as const; diff --git a/modules/identity/src/api/infrastructure/di/identity.di.ts b/modules/identity/src/api/infrastructure/di/identity.di.ts index d775b2d2..861950dc 100644 --- a/modules/identity/src/api/infrastructure/di/identity.di.ts +++ b/modules/identity/src/api/infrastructure/di/identity.di.ts @@ -71,9 +71,9 @@ export type IdentityInternalDeps = { }; export const buildIdentityDependencies = (params: ModuleParams): IdentityInternalDeps => { - const { database, config } = params; + const { database, config, transactionContext } = params; - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const accountRepository = new SequelizeAccountRepository( new SequelizeAccountDomainMapper(), diff --git a/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.test.ts b/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.test.ts new file mode 100644 index 00000000..f1f3f6a3 --- /dev/null +++ b/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.test.ts @@ -0,0 +1,60 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + AsyncLocalStorageOperationContextStore, + OperationAuditContextProvider, + type RequestWithAuth, +} from "@erp/core/api"; +import { UniqueID } from "@repo/rdx-ddd"; +import { Maybe, Result } from "@repo/rdx-utils"; +import type { Request, Response } from "express"; + +import { authenticateUser } from "./authenticate-user.middleware"; + +test("authenticateUser enriquece actor solo después de validar la identidad", async () => { + const operationContext = new AsyncLocalStorageOperationContextStore(); + const auditContextProvider = new OperationAuditContextProvider(operationContext); + const accountId = UniqueID.create("11111111-1111-4111-8111-111111111111").data; + const request = { + get: (name: string) => (name === "authorization" ? "Bearer valid-token" : undefined), + } as Request; + const middleware = authenticateUser({ + accessTokenVerifier: { + verify: async () => + Result.ok({ accountId: accountId.toPrimitive(), email: "ada@example.com" }), + }, + accountRepository: { + findById: async () => + Result.ok( + Maybe.some({ + id: accountId, + canAuthenticate: () => Result.ok(), + }) + ), + } as never, + operationContext, + }); + + await operationContext.run( + { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "http", + actor: { type: "anonymous", id: null, label: null }, + scope: null, + }, + async () => { + await new Promise((resolve) => { + middleware(request, {} as Response, resolve); + }); + + assert.deepEqual(auditContextProvider.get()?.actor, { + type: "user", + id: accountId.toPrimitive(), + label: "ada@example.com", + }); + assert.equal((request as RequestWithAuth).user.userId, accountId); + } + ); +}); diff --git a/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.ts b/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.ts index 8fd6e68f..2d0ddfb7 100644 --- a/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.ts +++ b/modules/identity/src/api/infrastructure/express/middlewares/authenticate-user.middleware.ts @@ -1,5 +1,6 @@ import { ExpressController, + type IOperationContextStore, type RequestWithAuth, UnauthorizedApiError, } from "@erp/core/api"; @@ -24,6 +25,7 @@ function parseBearerToken(authorization?: string): string | undefined { export function authenticateUser(params: { accessTokenVerifier: IAccessTokenVerifier; accountRepository: IAccountRepository; + operationContext: IOperationContextStore; }) { return async (req: Request, res: Response, next: NextFunction) => { try { @@ -66,6 +68,12 @@ export function authenticateUser(params: { roles: [], }; + params.operationContext.setActor({ + type: "user", + id: account.id.toPrimitive(), + label: emailResult.data.toString(), + }); + return next(); } catch { return ExpressController.errorResponse(new UnauthorizedApiError("Unauthorized"), req, res); diff --git a/modules/identity/src/api/infrastructure/express/middlewares/identity-authenticate-request.ts b/modules/identity/src/api/infrastructure/express/middlewares/identity-authenticate-request.ts index 8729535f..bd7b4bc6 100644 --- a/modules/identity/src/api/infrastructure/express/middlewares/identity-authenticate-request.ts +++ b/modules/identity/src/api/infrastructure/express/middlewares/identity-authenticate-request.ts @@ -1,9 +1,13 @@ +import type { IOperationContextStore } from "@erp/core/api"; + import type { IAccessTokenVerifier, IAccountRepository } from "../../../application"; + import { authenticateUser } from "./authenticate-user.middleware"; export function identityAuthenticateRequest(params: { accessTokenVerifier: IAccessTokenVerifier; accountRepository: IAccountRepository; + operationContext: IOperationContextStore; }) { return authenticateUser(params); } diff --git a/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.test.ts b/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.test.ts new file mode 100644 index 00000000..c54b46e2 --- /dev/null +++ b/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.test.ts @@ -0,0 +1,88 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + AsyncLocalStorageOperationContextStore, + OperationAuditContextProvider, + type RequestWithAuth, +} from "@erp/core/api"; +import { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; +import type { Request, Response } from "express"; + +import { requireCompanyContext } from "./require-company-context.middleware"; + +test("requireCompanyContext establece scope después de validar membership", async () => { + const operationContext = new AsyncLocalStorageOperationContextStore(); + const auditContextProvider = new OperationAuditContextProvider(operationContext); + const accountId = UniqueID.create("11111111-1111-4111-8111-111111111111").data; + const companyId = "22222222-2222-4222-8222-222222222222"; + const request = { + user: { userId: accountId }, + get: (name: string) => (name === "X-Company-Id" ? companyId : undefined), + } as RequestWithAuth; + const middleware = requireCompanyContext({ + companyAccessChecker: { + canAccessCompany: async () => Result.ok(true), + }, + companyFinder: { + existsActiveById: async () => Result.ok(true), + }, + operationContext, + }); + + await operationContext.run( + { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "http", + actor: { type: "user", id: accountId.toPrimitive(), label: "ada@example.com" }, + scope: null, + }, + async () => { + await new Promise((resolve) => { + middleware(request as Request, {} as Response, resolve); + }); + + assert.deepEqual(auditContextProvider.get()?.scope, { + type: "company", + id: companyId, + label: null, + }); + } + ); +}); + +test("requireCompanyContext no establece scope cuando la membership no está autorizada", async () => { + const operationContext = new AsyncLocalStorageOperationContextStore(); + const accountId = UniqueID.create("11111111-1111-4111-8111-111111111111").data; + const request = { + user: { userId: accountId }, + get: () => "22222222-2222-4222-8222-222222222222", + } as RequestWithAuth; + const response = { + type: () => response, + status: () => response, + json: () => response, + } as Response; + const middleware = requireCompanyContext({ + companyAccessChecker: { + canAccessCompany: async () => Result.ok(false), + }, + operationContext, + }); + + await operationContext.run( + { + operationId: "operation-1", + correlationId: "correlation-1", + origin: "http", + actor: { type: "user", id: accountId.toPrimitive(), label: "ada@example.com" }, + scope: null, + }, + async () => { + await middleware(request as Request, response, () => undefined); + assert.equal(operationContext.get()?.scope, null); + } + ); +}); diff --git a/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.ts b/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.ts index 32948e04..6cdd27a2 100644 --- a/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.ts +++ b/modules/identity/src/api/infrastructure/express/middlewares/require-company-context.middleware.ts @@ -1,20 +1,26 @@ import { ExpressController, ForbiddenApiError, + type IOperationContextStore, InternalApiError, type RequestWithAuth, ValidationApiError, } from "@erp/core/api"; import { UniqueID } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; import type { NextFunction, Request, Response } from "express"; -import type { IIdentityCompanyAccessChecker, IIdentityTenantCompanyFinder } from "../../../application"; +import type { + IIdentityCompanyAccessChecker, + IIdentityTenantCompanyFinder, +} from "../../../application"; const COMPANY_HEADER_NAME = "X-Company-Id"; export function requireCompanyContext(params: { companyAccessChecker: IIdentityCompanyAccessChecker; companyFinder?: IIdentityTenantCompanyFinder; + operationContext: IOperationContextStore; }) { return async (req: Request, res: Response, next: NextFunction) => { const user = (req as RequestWithAuth).user; @@ -71,31 +77,39 @@ export function requireCompanyContext(params: { ); } - // Algunos módulos solo necesitan validar membership y no pueden depender - // de companies sin introducir un ciclo de módulos. - if (params.companyFinder) { - const companyExistsResult = await params.companyFinder.existsActiveById({ - companyId, - }); - if (companyExistsResult.isFailure) { - return ExpressController.errorResponse( - new InternalApiError(companyExistsResult.error.message), - req, - res - ); - } + const companyExistsResult = await findActiveCompany(params.companyFinder, companyId); + if (companyExistsResult.isFailure) { + return ExpressController.errorResponse( + new InternalApiError(companyExistsResult.error.message), + req, + res + ); + } - if (!companyExistsResult.data) { - return ExpressController.errorResponse( - new ForbiddenApiError("Company context required"), - req, - res - ); - } + if (!companyExistsResult.data) { + return ExpressController.errorResponse( + new ForbiddenApiError("Company context required"), + req, + res + ); } user.companyId = companyId; + params.operationContext.setScope({ + type: "company", + id: companyId.toPrimitive(), + label: null, + }); next(); }; } + +async function findActiveCompany( + companyFinder: IIdentityTenantCompanyFinder | undefined, + companyId: UniqueID +): Promise> { + // Algunos módulos solo validan membership para no introducir un ciclo con companies. + if (!companyFinder) return Result.ok(true); + return await companyFinder.existsActiveById({ companyId }); +} diff --git a/modules/identity/src/api/infrastructure/express/middlewares/require-identity-middlewares.ts b/modules/identity/src/api/infrastructure/express/middlewares/require-identity-middlewares.ts index db9647c6..4ac384b0 100644 --- a/modules/identity/src/api/infrastructure/express/middlewares/require-identity-middlewares.ts +++ b/modules/identity/src/api/infrastructure/express/middlewares/require-identity-middlewares.ts @@ -2,6 +2,7 @@ import type { StartParams } from "@erp/core/api"; import type { RequestHandler } from "express"; import type { IIdentityPublicServices } from "../../../application"; + import { requireCompanyContext } from "./require-company-context.middleware"; type CompaniesGeneralServices = { @@ -15,7 +16,9 @@ type CompaniesGeneralServices = { export function requireIdentityAuthenticated(params: StartParams): RequestHandler[] { const identityServices = params.getService("identity:general"); - return [...identityServices.auth.authenticatedOnly()]; + return [ + ...identityServices.auth.authenticatedOnly({ operationContext: params.operationContext }), + ]; } export function requireIdentityTenant(params: StartParams): RequestHandler[] { @@ -25,6 +28,7 @@ export function requireIdentityTenant(params: StartParams): RequestHandler[] { ...identityServices.auth.tenantRequired({ companyAccessChecker: identityServices.companyAccess, companyFinder: companiesServices.finder, + operationContext: params.operationContext, }), ]; } @@ -33,9 +37,10 @@ export function requireIdentityTenantMembership(params: StartParams): RequestHan const identityServices = params.getService("identity:general"); return [ - ...identityServices.auth.authenticatedOnly(), + ...identityServices.auth.authenticatedOnly({ operationContext: params.operationContext }), requireCompanyContext({ companyAccessChecker: identityServices.companyAccess, + operationContext: params.operationContext, }), ]; } diff --git a/modules/suppliers/src/api/infrastructure/di/suppliers.di.ts b/modules/suppliers/src/api/infrastructure/di/suppliers.di.ts index 1dddd494..ca4b7e11 100644 --- a/modules/suppliers/src/api/infrastructure/di/suppliers.di.ts +++ b/modules/suppliers/src/api/infrastructure/di/suppliers.di.ts @@ -29,10 +29,10 @@ export type SuppliersInternalDeps = { }; export function buildSuppliersDependencies(params: ModuleParams): SuppliersInternalDeps { - const { database } = params; + const { database, transactionContext } = params; // Infrastructure - const transactionManager = buildTransactionManager(database); + const transactionManager = buildTransactionManager(database, transactionContext); const persistenceMappers = buildSupplierPersistenceMappers(); const repository = buildSupplierRepository({ database, mappers: persistenceMappers }); diff --git a/packages/rdx-audit/package.json b/packages/rdx-audit/package.json new file mode 100644 index 00000000..ebe7f742 --- /dev/null +++ b/packages/rdx-audit/package.json @@ -0,0 +1,27 @@ +{ + "name": "@repo/rdx-audit", + "version": "0.10.5", + "private": true, + "type": "module", + "sideEffects": false, + "scripts": { + "test": "node --import tsx --test", + "typecheck": "tsc -p tsconfig.json --noEmit", + "check": "biome check .", + "lint": "biome lint .", + "clean": "rimraf .turbo node_modules dist" + }, + "exports": { + ".": "./src/index.ts" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@types/node": "^25.6.0", + "rimraf": "^6.1.3", + "tsx": "^4.21.0", + "typescript": "^6.0.2" + }, + "dependencies": { + "@repo/rdx-utils": "workspace:*" + } +} diff --git a/packages/rdx-audit/src/audit-state.types.ts b/packages/rdx-audit/src/audit-state.types.ts new file mode 100644 index 00000000..4981eba7 --- /dev/null +++ b/packages/rdx-audit/src/audit-state.types.ts @@ -0,0 +1,11 @@ +export type AuditScalar = string | number | boolean | null; + +export type AuditValue = AuditScalar | AuditObject | AuditArray; + +export interface AuditObject { + readonly [key: string]: AuditValue; +} + +export type AuditArray = readonly AuditValue[]; + +export type AuditState = Readonly>; diff --git a/packages/rdx-audit/src/audit.test.ts b/packages/rdx-audit/src/audit.test.ts new file mode 100644 index 00000000..8041001e --- /dev/null +++ b/packages/rdx-audit/src/audit.test.ts @@ -0,0 +1,173 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { Result } from "@repo/rdx-utils"; + +import { Audit } from "./audit"; +import type { + AuditContext, + AuditEntry, + IAuditContextProvider, + IAuditStore, + RecordAuditEventShortcutInput, + RecordAuditMutationShortcutInput, +} from "./audit.types"; +import { + AuditContextUnavailableError, + AuditStoreError, + InvalidAuditInputError, + InvalidAuditStateError, +} from "./errors"; + +const context: AuditContext = { + operation: { id: "operation-1", correlationId: "correlation-1", origin: "http" }, + actor: { type: "user", id: "user-1", label: "Ada" }, + scope: { type: "company", id: "company-1", label: "Acme" }, +}; + +class StubContextProvider implements IAuditContextProvider { + constructor(private readonly value: AuditContext | null) {} + + get(): AuditContext | null { + return this.value; + } +} + +class InMemoryAuditStore implements IAuditStore { + readonly entries: AuditEntry[] = []; + readonly error: AuditStoreError | null; + + constructor(error: AuditStoreError | null = null) { + this.error = error; + } + + async append(entry: AuditEntry) { + if (this.error) return Result.fail(this.error); + this.entries.push(entry); + return Result.ok(); + } +} + +function buildAudit(store = new InMemoryAuditStore(), provider: IAuditContextProvider = new StubContextProvider(context)) { + return { + audit: new Audit({ + source: { type: "application", id: "test-app" }, + contextProvider: provider, + store, + generateId: () => "audit-1", + now: () => new Date("2026-08-24T17:30:12.123Z"), + }), + store, + }; +} + +const eventInput: RecordAuditEventShortcutInput = { + action: "document.downloaded", + target: { type: "document", id: "document-1", label: "invoice.pdf" }, +}; + +const mutationInput: RecordAuditMutationShortcutInput = { + action: "customer.updated", + target: { type: "customer", id: "customer-1", label: "Ada Lovelace" }, + before: { name: "Ada", status: "active" }, + after: { name: "Grace", status: "active" }, +}; + +test("records an event with source, context, deterministic id and timestamp", async () => { + const { audit, store } = buildAudit(); + const result = await audit.recordEvent(eventInput); + + assert.equal(result.isSuccess, true); + assert.equal(store.entries.length, 1); + assert.deepEqual(store.entries[0], { + id: "audit-1", + occurredAt: "2026-08-24T17:30:12.123Z", + source: { type: "application", id: "test-app" }, + operation: context.operation, + actor: context.actor, + scope: context.scope, + kind: "event", + action: "document.downloaded", + target: eventInput.target, + changes: [], + }); +}); + +test("records a mutation with a deterministic diff", async () => { + const { audit, store } = buildAudit(); + const result = await audit.recordMutation(mutationInput); + + assert.equal(result.isSuccess, true); + assert.equal(store.entries.length, 1); + assert.deepEqual(store.entries[0]?.kind, "mutation"); + assert.deepEqual(store.entries[0]?.changes, [ + { path: "name", kind: "change", before: "Ada", after: "Grace" }, + ]); +}); + +test("does not append mutation no-ops", async () => { + const { audit, store } = buildAudit(); + const result = await audit.recordMutation({ ...mutationInput, after: mutationInput.before }); + + assert.equal(result.isSuccess, true); + assert.equal(store.entries.length, 0); +}); + +test("honours mutation atomic paths", async () => { + const { audit, store } = buildAudit(); + const result = await audit.recordMutation({ + ...mutationInput, + before: { paymentTerm: { id: "PT1", label: "30 days" } }, + after: { paymentTerm: { id: "PT2", label: "60 days" } }, + atomicPaths: ["paymentTerm"], + }); + + assert.equal(result.isSuccess, true); + assert.deepEqual(store.entries[0]?.changes, [ + { + path: "paymentTerm", + kind: "change", + before: { id: "PT1", label: "30 days" }, + after: { id: "PT2", label: "60 days" }, + }, + ]); +}); + +test("shortcuts are equivalent to the canonical record method", async () => { + const first = buildAudit(); + const second = buildAudit(); + await first.audit.recordEvent(eventInput); + await second.audit.record({ kind: "event", ...eventInput }); + assert.deepEqual(first.store.entries, second.store.entries); + + const third = buildAudit(); + const fourth = buildAudit(); + await third.audit.recordMutation(mutationInput); + await fourth.audit.record({ kind: "mutation", ...mutationInput }); + assert.deepEqual(third.store.entries, fourth.store.entries); +}); + +test("propagates store failures and fails without context", async () => { + const storeError = new AuditStoreError("store unavailable"); + const failedStore = buildAudit(new InMemoryAuditStore(storeError)); + const storeResult = await failedStore.audit.recordEvent(eventInput); + assert.equal(storeResult.isFailure, true); + assert.equal(storeResult.error, storeError); + + const missingContext = buildAudit(new InMemoryAuditStore(), new StubContextProvider(null)); + const contextResult = await missingContext.audit.recordEvent(eventInput); + assert.equal(contextResult.isFailure, true); + assert.ok(contextResult.error instanceof AuditContextUnavailableError); +}); + +test("rejects invalid actions, targets and mutation states", async () => { + const { audit } = buildAudit(); + const actionResult = await audit.recordEvent({ ...eventInput, action: "Document.Downloaded" }); + assert.ok(actionResult.error instanceof InvalidAuditInputError); + + const targetResult = await audit.recordMutation({ ...mutationInput, target: { ...mutationInput.target, id: "" } }); + assert.ok(targetResult.error instanceof InvalidAuditInputError); + + const stateResult = await audit.recordMutation({ ...mutationInput, before: { bad: new Date() as never } }); + assert.ok(stateResult.error instanceof InvalidAuditStateError); +}); diff --git a/packages/rdx-audit/src/audit.ts b/packages/rdx-audit/src/audit.ts new file mode 100644 index 00000000..56fa4cbb --- /dev/null +++ b/packages/rdx-audit/src/audit.ts @@ -0,0 +1,120 @@ +import { randomUUID } from "node:crypto"; + +import { Result } from "@repo/rdx-utils"; + +import type { + AuditContext, + AuditEntry, + AuditEventEntry, + AuditMutationEntry, + AuditSource, + IAudit, + IAuditContextProvider, + IAuditStore, + RecordAuditEventShortcutInput, + RecordAuditInput, + RecordAuditMutationShortcutInput, +} from "./audit.types"; +import { AuditContextUnavailableError, AuditError, AuditStoreError } from "./errors"; +import { diffAuditState } from "./internal/audit-diff"; +import { + validateAtomicPaths, + validateContext, + validateRecordInput, + validateSource, +} from "./internal/audit-input-validator"; +import { validateAuditState } from "./internal/audit-state-validator"; + +export interface AuditDependencies { + readonly source: AuditSource; + readonly contextProvider: IAuditContextProvider; + readonly store: IAuditStore; + readonly generateId?: () => string; + readonly now?: () => Date; +} + +/** + * Implementación portable append-only. Los shortcuts delegan en `record` para + * mantener una única ruta de validación, diff y persistencia. + */ +export class Audit implements IAudit { + private readonly generateId: () => string; + private readonly now: () => Date; + + constructor(private readonly deps: AuditDependencies) { + validateSource(deps.source); + this.generateId = deps.generateId ?? randomUUID; + this.now = deps.now ?? (() => new Date()); + } + + async record(input: RecordAuditInput): Promise> { + try { + validateRecordInput(input); + + if (input.kind === "mutation") { + validateAuditState(input.before, "before"); + validateAuditState(input.after, "after"); + const atomicPaths = validateAtomicPaths(input.atomicPaths); + const changes = diffAuditState(input.before, input.after, atomicPaths); + if (changes.length === 0) return Result.ok(); + + const context = this.getContext(); + const entry: AuditMutationEntry = { + ...this.buildBaseEntry(context, input.action), + kind: "mutation", + target: input.target, + changes, + }; + return await this.append(entry); + } + + const context = this.getContext(); + const entry: AuditEventEntry = { + ...this.buildBaseEntry(context, input.action), + kind: "event", + target: input.target, + changes: [], + }; + return await this.append(entry); + } catch (error: unknown) { + return Result.fail(this.toAuditError(error)); + } + } + + async recordEvent(input: RecordAuditEventShortcutInput): Promise> { + return await this.record({ kind: "event", ...input }); + } + + async recordMutation(input: RecordAuditMutationShortcutInput): Promise> { + return await this.record({ kind: "mutation", ...input }); + } + + private getContext(): AuditContext { + const context = this.deps.contextProvider.get(); + if (!context) throw new AuditContextUnavailableError(); + validateContext(context); + return context; + } + + private buildBaseEntry(context: AuditContext, action: string) { + return { + id: this.generateId(), + occurredAt: this.now().toISOString(), + source: this.deps.source, + operation: context.operation, + actor: context.actor, + scope: context.scope, + action, + }; + } + + private async append(entry: AuditEntry): Promise> { + const result = await this.deps.store.append(entry); + return result.isFailure ? Result.fail(result.error) : Result.ok(); + } + + private toAuditError(error: unknown): AuditError { + if (error instanceof AuditError) return error; + return new AuditStoreError(error instanceof Error ? error.message : "Audit store failed."); + } +} diff --git a/packages/rdx-audit/src/audit.types.ts b/packages/rdx-audit/src/audit.types.ts new file mode 100644 index 00000000..84dc0247 --- /dev/null +++ b/packages/rdx-audit/src/audit.types.ts @@ -0,0 +1,114 @@ +import type { Result } from "@repo/rdx-utils"; + +import type { AuditError, AuditStoreError } from "./errors"; +import type { AuditState, AuditValue } from "./audit-state.types"; + +export interface AuditSource { + readonly type: string; + readonly id: string; +} + +export interface AuditOperation { + readonly id: string; + readonly correlationId: string; + readonly origin: string; +} + +export interface AuditActor { + readonly type: string; + readonly id: string | null; + readonly label: string | null; +} + +export interface AuditScope { + readonly type: string; + readonly id: string; + readonly label: string | null; +} + +export interface AuditContext { + readonly operation: AuditOperation; + readonly actor: AuditActor; + readonly scope: AuditScope | null; +} + +export interface IAuditContextProvider { + get(): AuditContext | null; +} + +export interface AuditEventTarget { + readonly type: string; + readonly id: string | null; + readonly label: string | null; +} + +export interface AuditMutationTarget { + readonly type: string; + readonly id: string; + readonly label: string | null; +} + +export interface RecordAuditEventInput { + readonly kind: "event"; + readonly action: string; + readonly target: AuditEventTarget; +} + +export interface RecordAuditMutationInput { + readonly kind: "mutation"; + readonly action: string; + readonly target: AuditMutationTarget; + readonly before: AuditState; + readonly after: AuditState; + readonly atomicPaths?: readonly string[]; +} + +export type RecordAuditInput = RecordAuditEventInput | RecordAuditMutationInput; + +export type RecordAuditEventShortcutInput = Omit; + +export type RecordAuditMutationShortcutInput = Omit; + +export type AuditChangeKind = "set" | "unset" | "change"; + +export interface AuditChange { + readonly path: string; + readonly kind: AuditChangeKind; + readonly before: AuditValue | null; + readonly after: AuditValue | null; +} + +interface AuditEntryBase { + readonly id: string; + readonly occurredAt: string; + readonly source: AuditSource; + readonly operation: AuditOperation; + readonly actor: AuditActor; + readonly scope: AuditScope | null; + readonly action: string; +} + +export interface AuditEventEntry extends AuditEntryBase { + readonly kind: "event"; + readonly target: AuditEventTarget; + readonly changes: readonly []; +} + +export interface AuditMutationEntry extends AuditEntryBase { + readonly kind: "mutation"; + readonly target: AuditMutationTarget; + readonly changes: readonly AuditChange[]; +} + +export type AuditEntry = AuditEventEntry | AuditMutationEntry; + +export interface IAuditStore { + append(entry: AuditEntry): Promise>; +} + +/** Registra eventos y mutaciones mediante un puerto append-only. */ +export interface IAudit { + record(input: RecordAuditInput): Promise>; + recordEvent(input: RecordAuditEventShortcutInput): Promise>; + recordMutation(input: RecordAuditMutationShortcutInput): Promise>; +} diff --git a/packages/rdx-audit/src/errors/audit-error.ts b/packages/rdx-audit/src/errors/audit-error.ts new file mode 100644 index 00000000..cac2448c --- /dev/null +++ b/packages/rdx-audit/src/errors/audit-error.ts @@ -0,0 +1,18 @@ +export abstract class AuditError extends Error { + constructor(message: string) { + super(message); + this.name = new.target.name; + } +} + +export class InvalidAuditInputError extends AuditError {} + +export class InvalidAuditStateError extends AuditError {} + +export class AuditContextUnavailableError extends AuditError { + constructor() { + super("Audit context is unavailable."); + } +} + +export class AuditStoreError extends AuditError {} diff --git a/packages/rdx-audit/src/errors/index.ts b/packages/rdx-audit/src/errors/index.ts new file mode 100644 index 00000000..25cd0cbc --- /dev/null +++ b/packages/rdx-audit/src/errors/index.ts @@ -0,0 +1 @@ +export * from "./audit-error"; diff --git a/packages/rdx-audit/src/index.ts b/packages/rdx-audit/src/index.ts new file mode 100644 index 00000000..7a4d2ba1 --- /dev/null +++ b/packages/rdx-audit/src/index.ts @@ -0,0 +1,31 @@ +export { Audit, type AuditDependencies } from "./audit"; +export type { AuditArray, AuditObject, AuditScalar, AuditState, AuditValue } from "./audit-state.types"; +export type { + AuditActor, + AuditChange, + AuditChangeKind, + AuditContext, + AuditEntry, + AuditEventEntry, + AuditEventTarget, + AuditMutationEntry, + AuditMutationTarget, + AuditOperation, + AuditScope, + AuditSource, + IAudit, + IAuditContextProvider, + IAuditStore, + RecordAuditEventInput, + RecordAuditEventShortcutInput, + RecordAuditInput, + RecordAuditMutationInput, + RecordAuditMutationShortcutInput, +} from "./audit.types"; +export { + AuditContextUnavailableError, + AuditError, + AuditStoreError, + InvalidAuditInputError, + InvalidAuditStateError, +} from "./errors"; diff --git a/packages/rdx-audit/src/internal/audit-diff.test.ts b/packages/rdx-audit/src/internal/audit-diff.test.ts new file mode 100644 index 00000000..c77211bb --- /dev/null +++ b/packages/rdx-audit/src/internal/audit-diff.test.ts @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { AuditState } from "../audit-state.types"; +import { InvalidAuditInputError } from "../errors"; +import { diffAuditState } from "./audit-diff"; +import { validateAtomicPaths } from "./audit-input-validator"; + +function diff(before: AuditState, after: AuditState, paths: readonly string[] = []) { + return diffAuditState(before, after, validateAtomicPaths(paths)); +} + +test("returns no changes for equivalent values regardless of object key order", () => { + assert.deepEqual( + diff( + { profile: { id: "1", label: "A" }, tags: ["one", "two"] }, + { tags: ["one", "two"], profile: { label: "A", id: "1" } } + ), + [] + ); +}); + +test("creates scalar set, unset and change records", () => { + assert.deepEqual(diff({}, { name: "Ada" }), [ + { path: "name", kind: "set", before: null, after: "Ada" }, + ]); + assert.deepEqual(diff({ name: "Ada" }, {}), [ + { path: "name", kind: "unset", before: "Ada", after: null }, + ]); + assert.deepEqual(diff({ name: "Ada" }, { name: "Grace" }), [ + { path: "name", kind: "change", before: "Ada", after: "Grace" }, + ]); +}); + +test("diffs nested objects recursively and sorts paths", () => { + assert.deepEqual( + diff( + { address: { city: "Madrid", street: "A" }, email: "old@example.com" }, + { address: { city: "Barcelona", zip: "08001" }, email: "new@example.com" } + ), + [ + { path: "address.city", kind: "change", before: "Madrid", after: "Barcelona" }, + { path: "address.street", kind: "unset", before: "A", after: null }, + { path: "address.zip", kind: "set", before: null, after: "08001" }, + { path: "email", kind: "change", before: "old@example.com", after: "new@example.com" }, + ] + ); +}); + +test("treats arrays as atomic and distinguishes null from missing", () => { + assert.deepEqual(diff({ tags: ["one"] }, { tags: ["two"] }), [ + { path: "tags", kind: "change", before: ["one"], after: ["two"] }, + ]); + assert.deepEqual(diff({}, { value: null }), [ + { path: "value", kind: "set", before: null, after: null }, + ]); + assert.deepEqual(diff({ value: null }, {}), [ + { path: "value", kind: "unset", before: null, after: null }, + ]); +}); + +test("records incompatible structural changes at their current path", () => { + assert.deepEqual(diff({ address: { city: "Madrid" }, value: "text" }, { address: "Madrid", value: { text: "x" } }), [ + { path: "address", kind: "change", before: { city: "Madrid" }, after: "Madrid" }, + { path: "value", kind: "change", before: "text", after: { text: "x" } }, + ]); +}); + +test("honours atomic paths including nested paths", () => { + const before = { paymentTerm: { id: "PT1", label: "30 days" }, address: { city: "Madrid", zip: "1" } }; + const after = { paymentTerm: { id: "PT2", label: "60 days" }, address: { city: "Barcelona", zip: "1" } }; + + assert.equal(diff(before, after).length, 3); + assert.deepEqual(diff(before, after, ["paymentTerm", "address.city"]), [ + { path: "address.city", kind: "change", before: "Madrid", after: "Barcelona" }, + { + path: "paymentTerm", + kind: "change", + before: { id: "PT1", label: "30 days" }, + after: { id: "PT2", label: "60 days" }, + }, + ]); +}); + +test("rejects invalid or duplicate atomic paths", () => { + assert.throws(() => validateAtomicPaths(["", "name"]), InvalidAuditInputError); + assert.throws(() => validateAtomicPaths(["address..city"]), InvalidAuditInputError); + assert.throws(() => validateAtomicPaths(["name", "name"]), InvalidAuditInputError); +}); diff --git a/packages/rdx-audit/src/internal/audit-diff.ts b/packages/rdx-audit/src/internal/audit-diff.ts new file mode 100644 index 00000000..1af1a0af --- /dev/null +++ b/packages/rdx-audit/src/internal/audit-diff.ts @@ -0,0 +1,95 @@ +import type { AuditChange } from "../audit.types"; +import type { AuditObject, AuditState, AuditValue } from "../audit-state.types"; + +const MISSING = Symbol("missing-audit-value"); + +export function diffAuditState( + before: AuditState, + after: AuditState, + atomicPaths: ReadonlySet +): readonly AuditChange[] { + const changes: AuditChange[] = []; + compareObjects(before, after, "", atomicPaths, changes); + return changes.sort((left, right) => left.path.localeCompare(right.path)); +} + +function compareObjects( + before: AuditObject, + after: AuditObject, + prefix: string, + atomicPaths: ReadonlySet, + changes: AuditChange[] +): void { + const keys = new Set([...Object.keys(before), ...Object.keys(after)]); + for (const key of [...keys].sort((left, right) => left.localeCompare(right))) { + const path = prefix ? `${prefix}.${key}` : key; + const beforeValue = getOwnValue(before, key); + const afterValue = getOwnValue(after, key); + compareValues(beforeValue, afterValue, path, atomicPaths, changes); + } +} + +function compareValues( + before: AuditValue | typeof MISSING, + after: AuditValue | typeof MISSING, + path: string, + atomicPaths: ReadonlySet, + changes: AuditChange[] +): void { + if (before === MISSING) { + changes.push({ path, kind: "set", before: null, after: after as AuditValue }); + return; + } + if (after === MISSING) { + changes.push({ path, kind: "unset", before, after: null }); + return; + } + if (deepEqual(before, after)) return; + if (atomicPaths.has(path) || !isObject(before) || !isObject(after)) { + changes.push({ path, kind: "change", before, after }); + return; + } + + compareObjects(before, after, path, atomicPaths, changes); +} + +function deepEqual(left: AuditValue, right: AuditValue): boolean { + if (left === right) return true; + if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) { + return false; + } + if (Array.isArray(left) || Array.isArray(right)) { + if (!(Array.isArray(left) && Array.isArray(right)) || left.length !== right.length) return false; + return left.every((value, index) => { + const rightValue = right[index]; + return rightValue !== undefined && deepEqual(value, rightValue); + }); + } + if (!(isObject(left) && isObject(right))) return false; + + const leftKeys = Object.keys(left).sort(); + const rightKeys = Object.keys(right).sort(); + if (leftKeys.length !== rightKeys.length) return false; + return leftKeys.every((key, index) => { + const rightKey = rightKeys[index]; + const leftValue = left[key]; + const rightValue = right[key]; + return ( + rightKey !== undefined && + leftValue !== undefined && + rightValue !== undefined && + key === rightKey && + deepEqual(leftValue, rightValue) + ); + }); +} + +function getOwnValue(object: AuditObject, key: string): AuditValue | typeof MISSING { + if (!Object.hasOwn(object, key)) return MISSING; + const value = object[key]; + return value === undefined ? MISSING : value; +} + +function isObject(value: AuditValue): value is AuditObject { + return value !== null && typeof value === "object" && !Array.isArray(value); +} diff --git a/packages/rdx-audit/src/internal/audit-input-validator.ts b/packages/rdx-audit/src/internal/audit-input-validator.ts new file mode 100644 index 00000000..d82c8737 --- /dev/null +++ b/packages/rdx-audit/src/internal/audit-input-validator.ts @@ -0,0 +1,85 @@ +import type { + AuditContext, + AuditEventTarget, + AuditMutationTarget, + AuditSource, + RecordAuditInput, +} from "../audit.types"; +import { InvalidAuditInputError } from "../errors"; + +const MAX_ACTION_LENGTH = 200; +const MAX_IDENTIFIER_LENGTH = 255; +const ACTION_PATTERN = /^[a-z0-9_-]+(?:\.[a-z0-9_-]+)+$/; + +export function validateSource(source: AuditSource): void { + validateIdentifier(source.type, "source.type"); + validateIdentifier(source.id, "source.id"); +} + +export function validateRecordInput(input: RecordAuditInput): void { + validateAction(input.action); + if (input.kind === "event") { + validateEventTarget(input.target); + return; + } + validateMutationTarget(input.target); +} + +export function validateContext(context: AuditContext): void { + validateIdentifier(context.operation.id, "operation.id"); + validateIdentifier(context.operation.correlationId, "operation.correlationId"); + validateIdentifier(context.operation.origin, "operation.origin"); + validateIdentifier(context.actor.type, "actor.type"); + validateOptionalIdentifier(context.actor.id, "actor.id"); + validateOptionalLabel(context.actor.label, "actor.label"); + + if (context.scope) { + validateIdentifier(context.scope.type, "scope.type"); + validateIdentifier(context.scope.id, "scope.id"); + validateOptionalLabel(context.scope.label, "scope.label"); + } +} + +export function validateAtomicPaths(paths: readonly string[] | undefined): ReadonlySet { + const normalized = new Set(); + for (const path of paths ?? []) { + if (typeof path !== "string" || !path || path.split(".").some((segment) => !segment)) { + throw new InvalidAuditInputError("atomicPaths must contain non-empty dot-separated paths."); + } + if (normalized.has(path)) throw new InvalidAuditInputError(`Duplicate atomic path "${path}".`); + normalized.add(path); + } + return normalized; +} + +function validateAction(action: string): void { + if (typeof action !== "string" || action.length > MAX_ACTION_LENGTH || !ACTION_PATTERN.test(action)) { + throw new InvalidAuditInputError("action must use lowercase dot-separated segments."); + } +} + +function validateEventTarget(target: AuditEventTarget): void { + validateIdentifier(target.type, "target.type"); + validateOptionalIdentifier(target.id, "target.id"); + validateOptionalLabel(target.label, "target.label"); +} + +function validateMutationTarget(target: AuditMutationTarget): void { + validateIdentifier(target.type, "target.type"); + validateIdentifier(target.id, "target.id"); + validateOptionalLabel(target.label, "target.label"); +} + +function validateOptionalIdentifier(value: string | null, name: string): void { + if (value !== null) validateIdentifier(value, name); +} + +function validateOptionalLabel(value: string | null, name: string): void { + if (value !== null) validateIdentifier(value, name); +} + +function validateIdentifier(value: string, name: string): void { + if (typeof value !== "string" || !value.trim() || value.length > MAX_IDENTIFIER_LENGTH) { + throw new InvalidAuditInputError(`${name} must be a non-empty string up to ${MAX_IDENTIFIER_LENGTH} characters.`); + } +} diff --git a/packages/rdx-audit/src/internal/audit-state-validator.test.ts b/packages/rdx-audit/src/internal/audit-state-validator.test.ts new file mode 100644 index 00000000..f546d7d7 --- /dev/null +++ b/packages/rdx-audit/src/internal/audit-state-validator.test.ts @@ -0,0 +1,62 @@ +import { Buffer } from "node:buffer"; +import assert from "node:assert/strict"; +import test from "node:test"; + +import { InvalidAuditStateError } from "../errors"; +import { validateAuditState } from "./audit-state-validator"; + +test("accepts supported audit state values", () => { + assert.doesNotThrow(() => + validateAuditState( + { + text: "value", + enabled: true, + empty: null, + zero: 0, + negative: -1.5, + object: { nested: { label: "A" } }, + array: ["x", { y: false }], + emptyObject: {}, + emptyArray: [], + }, + "state" + ) + ); +}); + +for (const [name, value] of [ + ["undefined", undefined], + ["NaN", Number.NaN], + ["Infinity", Number.POSITIVE_INFINITY], + ["-Infinity", Number.NEGATIVE_INFINITY], + ["Date", new Date()], + ["Buffer", Buffer.from("audit")], + ["bigint", 1n], + ["function", () => undefined], + ["class instance", new (class StateValue {})()], + ["Map", new Map()], + ["Set", new Set()], +] as const) { + test(`rejects ${name}`, () => { + assert.throws(() => validateAuditState({ value: value as never }, "state"), InvalidAuditStateError); + }); +} + +test("rejects invalid nested values and keys", () => { + assert.throws( + () => validateAuditState({ nested: { value: new Date() as never } }, "state"), + InvalidAuditStateError + ); + assert.throws( + () => validateAuditState({ "address.city": "Madrid" }, "state"), + InvalidAuditStateError + ); + assert.throws(() => validateAuditState({ "": "value" }, "state"), InvalidAuditStateError); +}); + +test("rejects circular references", () => { + const state: Record = {}; + state.self = state; + + assert.throws(() => validateAuditState(state as never, "state"), InvalidAuditStateError); +}); diff --git a/packages/rdx-audit/src/internal/audit-state-validator.ts b/packages/rdx-audit/src/internal/audit-state-validator.ts new file mode 100644 index 00000000..17abeeaa --- /dev/null +++ b/packages/rdx-audit/src/internal/audit-state-validator.ts @@ -0,0 +1,57 @@ +import { InvalidAuditStateError } from "../errors"; +import type { AuditState, AuditValue } from "../audit-state.types"; + +export function validateAuditState(state: AuditState, name: string): void { + validateObject(state, name, new WeakSet()); +} + +function validateValue(value: unknown, path: string, ancestors: WeakSet): asserts value is AuditValue { + if (value === null || typeof value === "string" || typeof value === "boolean") return; + + if (typeof value === "number") { + if (Number.isFinite(value)) return; + throw new InvalidAuditStateError(`${path} must contain a finite number.`); + } + + if (Array.isArray(value)) { + if (ancestors.has(value)) throw new InvalidAuditStateError(`${path} contains a circular reference.`); + ancestors.add(value); + value.forEach((item, index) => { + validateValue(item, `${path}[${index}]`, ancestors); + }); + ancestors.delete(value); + return; + } + + if (isPlainObject(value)) { + validateObject(value, path, ancestors); + return; + } + + throw new InvalidAuditStateError(`${path} contains an unsupported value.`); +} + +function validateObject(value: object, path: string, ancestors: WeakSet): void { + if (!isPlainObject(value)) throw new InvalidAuditStateError(`${path} must be a plain object.`); + if (ancestors.has(value)) throw new InvalidAuditStateError(`${path} contains a circular reference.`); + if (Object.getOwnPropertySymbols(value).length > 0) { + throw new InvalidAuditStateError(`${path} must not contain symbol keys.`); + } + + ancestors.add(value); + for (const [key, nestedValue] of Object.entries(value)) { + if (!key) throw new InvalidAuditStateError(`${path} must not contain an empty key.`); + if (key.includes(".")) throw new InvalidAuditStateError(`${path}.${key} must not contain '.'.`); + validateValue(nestedValue, `${path}.${key}`, ancestors); + } + ancestors.delete(value); +} + +export function isPlainObject(value: unknown): value is Readonly> { + if (value === null || typeof value !== "object" || Array.isArray(value) || Buffer.isBuffer(value)) { + return false; + } + + const prototype = Object.getPrototypeOf(value); + return prototype === Object.prototype || prototype === null; +} diff --git a/packages/rdx-audit/tsconfig.json b/packages/rdx-audit/tsconfig.json new file mode 100644 index 00000000..a2619a62 --- /dev/null +++ b/packages/rdx-audit/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@repo/typescript-config/buildless.json", + "compilerOptions": { + "rootDir": "src", + "types": ["node"] + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c17d1785..880adbfc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,10 +19,10 @@ importers: version: 4.3.0 '@typescript-eslint/eslint-plugin': specifier: ^8.58.1 - version: 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) + version: 8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) '@typescript-eslint/parser': specifier: ^8.58.1 - version: 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) + version: 8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) baseline-browser-mapping: specifier: ^2.10.18 version: 2.10.32 @@ -83,6 +83,12 @@ importers: '@erp/identity': specifier: workspace:* version: link:../../modules/identity + '@erp/suppliers': + specifier: workspace:* + version: link:../../modules/suppliers + '@repo/rdx-audit': + specifier: workspace:* + version: link:../../packages/rdx-audit '@repo/rdx-logger': specifier: workspace:* version: link:../../packages/rdx-logger @@ -106,7 +112,7 @@ importers: version: 17.4.2 express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) express-list-routes: specifier: ^1.3.2 version: 1.3.2 @@ -151,7 +157,7 @@ importers: version: 2.3.4 sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) shallow-equal-object: specifier: ^1.1.1 version: 1.1.1 @@ -221,7 +227,7 @@ importers: version: 4.2.0 tsup: specifier: 8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.25)(tsx@4.21.0)(typescript@6.0.2) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.25)(supports-color@7.2.0)(tsx@4.21.0)(typescript@6.0.2) tsx: specifier: 4.21.0 version: 4.21.0 @@ -252,6 +258,9 @@ importers: '@erp/identity': specifier: workspace:* version: link:../../modules/identity + '@erp/suppliers': + specifier: workspace:* + version: link:../../modules/suppliers '@fontsource-variable/geist': specifier: ^5.2.8 version: 5.2.9 @@ -272,13 +281,13 @@ importers: version: link:../../packages/shadcn-ui '@tailwindcss/vite': specifier: ^4.2.2 - version: 4.3.0(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)) + version: 4.3.0(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)) '@tanstack/react-query': specifier: ^5.98.0 version: 5.100.14(react@19.2.6) axios: specifier: ^1.15.0 - version: 1.16.1 + version: 1.16.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) date-fns: specifier: ^4.1.0 version: 4.3.0 @@ -302,7 +311,7 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -314,7 +323,7 @@ importers: version: 1.3.2 sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) tailwindcss: specifier: ^4.2.2 version: 4.3.0 @@ -323,7 +332,7 @@ importers: version: 1.4.0 vite-plugin-html: specifier: ^3.2.2 - version: 3.2.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)) + version: 3.2.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)) devDependencies: '@biomejs/biome': specifier: ^2.4.11 @@ -348,13 +357,13 @@ importers: version: 19.2.3(@types/react@19.2.15) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)) + version: 6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)) typescript: specifier: ~6.0.2 version: 6.0.2 vite: specifier: ^8.0.8 - version: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0) + version: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0) modules/auth: dependencies: @@ -442,7 +451,7 @@ importers: version: 5.100.14(react@19.2.6) express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) lucide-react: specifier: ^1.8.0 version: 1.16.0(react@19.2.6) @@ -451,13 +460,13 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router-dom: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) use-debounce: specifier: ^10.1.1 version: 10.1.1(react@19.2.6) @@ -524,7 +533,7 @@ importers: version: 5.100.14(react@19.2.6) express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) lucide-react: specifier: ^1.8.0 version: 1.16.0(react@19.2.6) @@ -533,13 +542,13 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router-dom: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 @@ -574,6 +583,9 @@ importers: '@repo/i18next': specifier: workspace:* version: link:../../packages/i18n + '@repo/rdx-audit': + specifier: workspace:* + version: link:../../packages/rdx-audit '@repo/rdx-criteria': specifier: workspace:* version: link:../../packages/rdx-criteria @@ -597,13 +609,13 @@ importers: version: 5.100.14(react@19.2.6) axios: specifier: ^1.15.0 - version: 1.16.1 + version: 1.16.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0) dinero.js: specifier: 1.9.1 version: 1.9.1 express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) http-status: specifier: ^2.1.0 version: 2.1.0 @@ -618,20 +630,20 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router-dom: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 devDependencies: '@hookform/devtools': specifier: ^4.4.0 - version: 4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0) '@types/dinero.js': specifier: 1.9.1 version: 1.9.1 @@ -641,6 +653,9 @@ importers: '@types/mime-types': specifier: ^3.0.1 version: 3.0.1 + '@types/node': + specifier: ^25.6.0 + version: 25.9.1 '@types/react': specifier: ^19.2.14 version: 19.2.15 @@ -727,7 +742,7 @@ importers: version: 1.9.1 express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) libphonenumber-js: specifier: ^1.12.41 version: 1.13.3 @@ -742,7 +757,7 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-qr-code: specifier: ^2.0.18 version: 2.0.21(react@19.2.6) @@ -751,14 +766,14 @@ importers: version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 devDependencies: '@hookform/devtools': specifier: ^4.4.0 - version: 4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0) '@types/dinero.js': specifier: 1.9.1 version: 1.9.1 @@ -836,7 +851,7 @@ importers: version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) lucide-react: specifier: ^1.8.0 version: 1.16.0(react@19.2.6) @@ -845,13 +860,13 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router-dom: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) use-debounce: specifier: ^10.1.1 version: 10.1.1(react@19.2.6) @@ -900,10 +915,10 @@ importers: version: link:../../packages/rdx-utils express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 @@ -952,10 +967,10 @@ importers: version: link:../../packages/rdx-utils express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 @@ -1004,7 +1019,7 @@ importers: version: 6.0.0 express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) jsonwebtoken: specifier: ^9.0.3 version: 9.0.3 @@ -1013,7 +1028,7 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router-dom: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1022,7 +1037,7 @@ importers: version: 1.3.2 sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 @@ -1058,91 +1073,6 @@ importers: specifier: ^6.0.2 version: 6.0.2 - modules/supplier: - dependencies: - '@erp/auth': - specifier: workspace:* - version: link:../auth - '@erp/core': - specifier: workspace:* - version: link:../core - '@hookform/resolvers': - specifier: ^5.2.2 - version: 5.4.0(react-hook-form@7.76.1(react@19.2.6)) - '@repo/i18next': - specifier: workspace:* - version: link:../../packages/i18n - '@repo/rdx-criteria': - specifier: workspace:* - version: link:../../packages/rdx-criteria - '@repo/rdx-ddd': - specifier: workspace:* - version: link:../../packages/rdx-ddd - '@repo/rdx-logger': - specifier: workspace:* - version: link:../../packages/rdx-logger - '@repo/rdx-ui': - specifier: workspace:* - version: link:../../packages/rdx-ui - '@repo/rdx-utils': - specifier: workspace:* - version: link:../../packages/rdx-utils - '@repo/shadcn-ui': - specifier: workspace:* - version: link:../../packages/shadcn-ui - '@tanstack/react-query': - specifier: ^5.98.0 - version: 5.100.14(react@19.2.6) - '@tanstack/react-table': - specifier: ^8.21.3 - version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - express: - specifier: ^4.22.1 - version: 4.22.2 - lucide-react: - specifier: ^1.8.0 - version: 1.16.0(react@19.2.6) - react-hook-form: - specifier: ^7.72.1 - version: 7.76.1(react@19.2.6) - react-i18next: - specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) - react-router-dom: - specifier: ^7.14.0 - version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - sequelize: - specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) - use-debounce: - specifier: ^10.1.1 - version: 10.1.1(react@19.2.6) - zod: - specifier: ^4.3.6 - version: 4.4.3 - devDependencies: - '@types/express': - specifier: ^4.17.21 - version: 4.17.25 - '@types/react': - specifier: ^19.2.14 - version: 19.2.15 - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 - react: - specifier: ^19.2.5 - version: 19.2.6 - react-dom: - specifier: ^19.2.5 - version: 19.2.6(react@19.2.6) - rimraf: - specifier: ^6.1.3 - version: 6.1.3 - typescript: - specifier: ^6.0.2 - version: 6.0.2 - modules/supplier-invoices: dependencies: '@erp/auth': @@ -1168,10 +1098,10 @@ importers: version: link:../../packages/rdx-utils express: specifier: ^4.22.1 - version: 4.22.2 + version: 4.22.2(supports-color@7.2.0) sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) zod: specifier: ^4.3.6 version: 4.4.3 @@ -1186,6 +1116,91 @@ importers: specifier: ^6.0.2 version: 6.0.2 + modules/suppliers: + dependencies: + '@erp/auth': + specifier: workspace:* + version: link:../auth + '@erp/core': + specifier: workspace:* + version: link:../core + '@hookform/resolvers': + specifier: ^5.2.2 + version: 5.4.0(react-hook-form@7.76.1(react@19.2.6)) + '@repo/i18next': + specifier: workspace:* + version: link:../../packages/i18n + '@repo/rdx-criteria': + specifier: workspace:* + version: link:../../packages/rdx-criteria + '@repo/rdx-ddd': + specifier: workspace:* + version: link:../../packages/rdx-ddd + '@repo/rdx-logger': + specifier: workspace:* + version: link:../../packages/rdx-logger + '@repo/rdx-ui': + specifier: workspace:* + version: link:../../packages/rdx-ui + '@repo/rdx-utils': + specifier: workspace:* + version: link:../../packages/rdx-utils + '@repo/shadcn-ui': + specifier: workspace:* + version: link:../../packages/shadcn-ui + '@tanstack/react-query': + specifier: ^5.98.0 + version: 5.100.14(react@19.2.6) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + express: + specifier: ^4.22.1 + version: 4.22.2(supports-color@7.2.0) + lucide-react: + specifier: ^1.8.0 + version: 1.16.0(react@19.2.6) + react-hook-form: + specifier: ^7.72.1 + version: 7.76.1(react@19.2.6) + react-i18next: + specifier: ^17.0.2 + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + react-router-dom: + specifier: ^7.14.0 + version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + sequelize: + specifier: ^6.37.8 + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) + use-debounce: + specifier: ^10.1.1 + version: 10.1.1(react@19.2.6) + zod: + specifier: ^4.3.6 + version: 4.4.3 + devDependencies: + '@types/express': + specifier: ^4.17.21 + version: 4.17.25 + '@types/react': + specifier: ^19.2.14 + version: 19.2.15 + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 + react: + specifier: ^19.2.5 + version: 19.2.6 + react-dom: + specifier: ^19.2.5 + version: 19.2.6(react@19.2.6) + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + packages/i18n: dependencies: i18next: @@ -1214,11 +1229,33 @@ importers: specifier: ^6.0.2 version: 6.0.2 + packages/rdx-audit: + dependencies: + '@repo/rdx-utils': + specifier: workspace:* + version: link:../rdx-utils + devDependencies: + '@repo/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@types/node': + specifier: ^25.6.0 + version: 25.9.1 + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^6.0.2 + version: 6.0.2 + packages/rdx-criteria: dependencies: sequelize: specifier: ^6.37.8 - version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4) + version: 6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0) devDependencies: '@repo/typescript-config': specifier: workspace:* @@ -1332,7 +1369,7 @@ importers: version: 7.76.1(react@19.2.6) react-i18next: specifier: ^17.0.2 - version: 17.0.8(i18next@26.0.4(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) + version: 17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2) react-router: specifier: ^7.14.0 version: 7.15.1(react-dom@19.2.6(react@19.2.6))(react@19.2.6) @@ -1372,7 +1409,7 @@ importers: version: 19.2.3(@types/react@19.2.15) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)) + version: 6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)) date-fns: specifier: ^4.1.0 version: 4.3.0 @@ -1390,7 +1427,7 @@ importers: version: 4.3.0 tsup: specifier: ^8.5.1 - version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.21.0)(typescript@6.0.2) + version: 8.5.1(jiti@2.7.0)(postcss@8.5.15)(supports-color@7.2.0)(tsx@4.21.0)(typescript@6.0.2) tsx: specifier: ^4.21.0 version: 4.21.0 @@ -1402,10 +1439,10 @@ importers: version: 6.0.2 typescript-plugin-css-modules: specifier: ^5.2.0 - version: 5.2.0(ts-node@10.9.2(@types/node@25.9.1)(typescript@6.0.2))(typescript@6.0.2) + version: 5.2.0(supports-color@7.2.0)(ts-node@10.9.2(@types/node@25.9.1)(typescript@6.0.2))(typescript@6.0.2) vite-tsconfig-paths: specifier: ^6.1.1 - version: 6.1.1(typescript@6.0.2)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)) + version: 6.1.1(supports-color@7.2.0)(typescript@6.0.2)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)) packages/rdx-utils: dependencies: @@ -1481,7 +1518,7 @@ importers: version: 10.33.4 radix-ui: specifier: latest - version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 1.6.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react-day-picker: specifier: 9.14.0 version: 9.14.0(react@19.2.6) @@ -1548,7 +1585,7 @@ importers: version: 19.2.6(react@19.2.6) shadcn: specifier: ^4.2.0 - version: 4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.2) + version: 4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(supports-color@7.2.0)(typescript@6.0.2) tw-animate-css: specifier: ^1.4.0 version: 1.4.0 @@ -2761,14 +2798,17 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@radix-ui/number@1.1.3': + resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} '@radix-ui/primitive@1.1.3': resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - '@radix-ui/react-accessible-icon@1.1.7': - resolution: {integrity: sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==} + '@radix-ui/primitive@1.1.7': + resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} + + '@radix-ui/react-accessible-icon@1.1.15': + resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2780,8 +2820,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-accordion@1.2.12': - resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + '@radix-ui/react-accordion@1.2.20': + resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2793,8 +2833,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-alert-dialog@1.1.15': - resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + '@radix-ui/react-alert-dialog@1.1.23': + resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2806,8 +2846,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + '@radix-ui/react-arrow@1.1.15': + resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2819,8 +2859,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-aspect-ratio@1.1.7': - resolution: {integrity: sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==} + '@radix-ui/react-aspect-ratio@1.1.15': + resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2832,8 +2872,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.1.10': - resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + '@radix-ui/react-avatar@1.2.6': + resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2845,8 +2885,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-checkbox@1.3.3': - resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + '@radix-ui/react-checkbox@1.3.11': + resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2858,8 +2898,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collapsible@1.1.12': - resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + '@radix-ui/react-collapsible@1.1.20': + resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.15': + resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2893,8 +2946,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-context-menu@2.2.16': - resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + '@radix-ui/react-compose-refs@1.1.5': + resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.3.7': + resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2915,6 +2977,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-context@1.2.2': + resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dialog@1.1.15': resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: @@ -2928,6 +2999,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dialog@1.1.23': + resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: @@ -2937,6 +3021,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-direction@1.1.4': + resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-dismissable-layer@1.1.11': resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: @@ -2950,8 +3043,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.16': - resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + '@radix-ui/react-dismissable-layer@1.1.19': + resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.24': + resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2972,6 +3078,28 @@ packages: '@types/react': optional: true + '@radix-ui/react-focus-guards@1.1.6': + resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.16': + resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-scope@1.1.7': resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: @@ -2985,8 +3113,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-form@0.1.8': - resolution: {integrity: sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==} + '@radix-ui/react-form@0.1.16': + resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -2998,8 +3126,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-hover-card@1.1.15': - resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + '@radix-ui/react-hover-card@1.1.23': + resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3020,8 +3148,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.7': - resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + '@radix-ui/react-id@1.1.4': + resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.15': + resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3033,8 +3170,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menu@2.1.16': - resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + '@radix-ui/react-menu@2.1.24': + resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3046,8 +3183,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menubar@1.1.16': - resolution: {integrity: sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==} + '@radix-ui/react-menubar@1.1.24': + resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3059,8 +3196,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-navigation-menu@1.2.14': - resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + '@radix-ui/react-navigation-menu@1.2.22': + resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3072,8 +3209,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-one-time-password-field@0.1.8': - resolution: {integrity: sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==} + '@radix-ui/react-one-time-password-field@0.1.16': + resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3085,8 +3222,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-password-toggle-field@0.1.3': - resolution: {integrity: sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==} + '@radix-ui/react-password-toggle-field@0.1.11': + resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3098,8 +3235,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.15': - resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + '@radix-ui/react-popover@1.1.23': + resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3111,8 +3248,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + '@radix-ui/react-popper@1.3.7': + resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.17': + resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3137,6 +3287,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-presence@1.1.10': + resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-presence@1.1.5': resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: @@ -3150,6 +3313,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-primitive@2.1.10': + resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: @@ -3176,8 +3352,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-progress@1.1.7': - resolution: {integrity: sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==} + '@radix-ui/react-progress@1.1.16': + resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3189,8 +3365,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.3.8': - resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + '@radix-ui/react-radio-group@1.4.7': + resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3215,8 +3391,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.10': - resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + '@radix-ui/react-roving-focus@1.1.19': + resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3228,8 +3404,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-select@2.2.6': - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + '@radix-ui/react-scroll-area@1.2.18': + resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3241,8 +3417,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-separator@1.1.7': - resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + '@radix-ui/react-select@2.3.7': + resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3254,8 +3430,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slider@1.3.6': - resolution: {integrity: sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==} + '@radix-ui/react-separator@1.1.15': + resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slider@1.4.7': + resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3285,8 +3474,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.2.6': - resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + '@radix-ui/react-slot@1.3.3': + resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.3.7': + resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3298,8 +3496,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tabs@1.1.13': - resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + '@radix-ui/react-tabs@1.1.21': + resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3311,8 +3509,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toast@1.2.15': - resolution: {integrity: sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==} + '@radix-ui/react-toast@1.2.23': + resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3337,6 +3535,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-toggle-group@1.1.19': + resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toggle@1.1.10': resolution: {integrity: sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==} peerDependencies: @@ -3350,8 +3561,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-toolbar@1.1.11': - resolution: {integrity: sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==} + '@radix-ui/react-toggle@1.1.18': + resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3363,8 +3574,21 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-tooltip@1.2.8': - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + '@radix-ui/react-toolbar@1.1.19': + resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.16': + resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3385,6 +3609,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-callback-ref@1.1.4': + resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: @@ -3394,6 +3627,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-controllable-state@1.2.6': + resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: @@ -3403,6 +3645,15 @@ packages: '@types/react': optional: true + '@radix-ui/react-use-effect-event@0.0.5': + resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@radix-ui/react-use-escape-keydown@1.1.1': resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: @@ -3412,8 +3663,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-is-hydrated@0.1.0': - resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + '@radix-ui/react-use-escape-keydown@1.1.5': + resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.3': + resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3430,8 +3690,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-previous@1.1.1': - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + '@radix-ui/react-use-layout-effect@1.1.4': + resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3439,8 +3699,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + '@radix-ui/react-use-previous@1.1.4': + resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3448,8 +3708,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + '@radix-ui/react-use-rect@1.1.4': + resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -3457,8 +3717,17 @@ packages: '@types/react': optional: true - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + '@radix-ui/react-use-size@1.1.4': + resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.11': + resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -3470,8 +3739,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@radix-ui/rect@1.1.3': + resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} '@reduxjs/toolkit@2.12.0': resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} @@ -4599,6 +4868,7 @@ packages: crypto-js@4.2.0: resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} + deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained. css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -5365,6 +5635,14 @@ packages: typescript: optional: true + i18next@26.4.0: + resolution: {integrity: sha512-rsmK5bFqsD1AetSFSIa43wtNR4WpvvH4p0tLEsTxkC7QTrfdFm06nbQ95bh8Og4wwaCnUEcm9DVYL2cgxitiQg==} + peerDependencies: + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + typescript: + optional: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -6462,8 +6740,8 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - radix-ui@1.4.3: - resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} + radix-ui@1.6.7: + resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -7108,6 +7386,7 @@ packages: tsconfck@3.1.6: resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} engines: {node: ^18 || >=20} + deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -7499,20 +7778,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7': + '@babel/core@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.7 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7539,41 +7818,41 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@7.2.0) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) + '@babel/traverse': 7.29.7(supports-color@7.2.0) semver: 6.3.1 transitivePeerDependencies: - supports-color '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.29.7': + '@babel/helper-member-expression-to-functions@7.29.7(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7': + '@babel/helper-module-imports@7.29.7(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -7583,18 +7862,18 @@ snapshots: '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@7.2.0) '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@7.2.0)': dependencies: - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.7(supports-color@7.2.0) '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color @@ -7614,43 +7893,43 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/core': 7.29.7(supports-color@7.2.0) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@7.2.0) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0)': dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/helper-plugin-utils': 7.29.7 '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0)) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -7662,7 +7941,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/types': 7.29.7 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.7(supports-color@7.2.0)': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.7 @@ -7670,7 +7949,7 @@ snapshots: '@babel/parser': 7.29.7 '@babel/template': 7.29.7 '@babel/types': 7.29.7 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -7819,9 +8098,9 @@ snapshots: tslib: 2.8.1 optional: true - '@emotion/babel-plugin@11.13.5': + '@emotion/babel-plugin@11.13.5(supports-color@7.2.0)': dependencies: - '@babel/helper-module-imports': 7.29.7 + '@babel/helper-module-imports': 7.29.7(supports-color@7.2.0) '@babel/runtime': 7.29.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 @@ -7851,10 +8130,10 @@ snapshots: '@emotion/memoize@0.9.0': {} - '@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6)': + '@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0)': dependencies: '@babel/runtime': 7.29.7 - '@emotion/babel-plugin': 11.13.5 + '@emotion/babel-plugin': 11.13.5(supports-color@7.2.0) '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) @@ -7877,12 +8156,12 @@ snapshots: '@emotion/sheet@1.4.0': {} - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6)': + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0))(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0)': dependencies: '@babel/runtime': 7.29.7 - '@emotion/babel-plugin': 11.13.5 + '@emotion/babel-plugin': 11.13.5(supports-color@7.2.0) '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.6) + '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0) '@emotion/serialize': 1.3.3 '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.6) '@emotion/utils': 1.4.2 @@ -8058,17 +8337,17 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))': dependencies: - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.0(jiti@2.7.0)(supports-color@7.2.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.23.5': + '@eslint/config-array@0.23.5(supports-color@7.2.0)': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.5 transitivePeerDependencies: - supports-color @@ -8129,10 +8408,10 @@ snapshots: dependencies: hono: 4.12.23 - '@hookform/devtools@4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@hookform/devtools@4.4.0(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(supports-color@7.2.0)': dependencies: - '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.6) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6))(@types/react@19.2.15)(react@19.2.6) + '@emotion/react': 11.14.0(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0))(@types/react@19.2.15)(react@19.2.6)(supports-color@7.2.0) '@types/lodash': 4.17.24 little-state-machine: 4.8.1(react@19.2.6) lodash: 4.18.1 @@ -8443,7 +8722,7 @@ snapshots: dependencies: react: 19.2.6 - '@modelcontextprotocol/sdk@1.29.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.29.0(supports-color@7.2.0)(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.14(hono@4.12.23) ajv: 8.20.0 @@ -8453,8 +8732,8 @@ snapshots: cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.8 - express: 5.2.1 - express-rate-limit: 8.5.2(express@5.2.1) + express: 5.2.1(supports-color@7.2.0) + express-rate-limit: 8.5.2(express@5.2.1(supports-color@7.2.0)) hono: 4.12.23 jose: 6.2.3 json-schema-typed: 8.0.2 @@ -8574,107 +8853,120 @@ snapshots: '@parcel/watcher-win32-ia32': 2.5.6 '@parcel/watcher-win32-x64': 2.5.6 - '@radix-ui/number@1.1.1': {} + '@radix-ui/number@1.1.3': {} '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accessible-icon@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/primitive@1.1.7': {} + + '@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-aspect-ratio@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8699,14 +8991,19 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8719,6 +9016,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-context@1.2.2(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -8741,12 +9044,41 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + aria-hidden: 1.2.6 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.15)(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-direction@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-direction@1.1.4(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -8760,15 +9092,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8781,6 +9126,23 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -8792,31 +9154,31 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-form@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-form@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8830,33 +9192,40 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-id@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -8865,97 +9234,97 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-menubar@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-one-time-password-field@0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-password-toggle-field@0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -8964,18 +9333,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@floating-ui/react-dom': 2.1.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/rect': 1.1.1 + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/rect': 1.1.3 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -8992,6 +9371,15 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) @@ -9002,6 +9390,15 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) @@ -9020,28 +9417,27 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-progress@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -9065,44 +9461,64 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-select@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) aria-hidden: 1.2.6 react: 19.2.6 react-dom: 19.2.6(react@19.2.6) @@ -9111,28 +9527,28 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-separator@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-slider@1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/number': 1.1.3 + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -9153,51 +9569,57 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-slot@1.3.3(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toast@1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -9219,6 +9641,21 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + '@radix-ui/react-toggle@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 @@ -9230,35 +9667,47 @@ snapshots: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-toolbar@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.15 + '@types/react-dom': 19.2.3(@types/react@19.2.15) + + '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -9271,6 +9720,12 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.15)(react@19.2.6)': + dependencies: + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) @@ -9279,6 +9734,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) @@ -9286,6 +9750,13 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 + '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.15)(react@19.2.6)': dependencies: '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) @@ -9293,10 +9764,16 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.15)(react@19.2.6)': + '@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.15)(react@19.2.6)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 - use-sync-external-store: 1.6.0(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 @@ -9306,36 +9783,42 @@ snapshots: optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.15)(react@19.2.6)': + '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: react: 19.2.6 optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.15)(react@19.2.6)': + '@radix-ui/react-use-previous@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/rect': 1.1.1 react: 19.2.6 optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.15)(react@19.2.6)': + '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/rect': 1.1.3 react: 19.2.6 optionalDependencies: '@types/react': 19.2.15 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@radix-ui/react-use-size@1.1.4(@types/react@19.2.15)(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + react: 19.2.6 + optionalDependencies: + '@types/react': 19.2.15 + + '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: '@types/react': 19.2.15 '@types/react-dom': 19.2.3(@types/react@19.2.15) - '@radix-ui/rect@1.1.1': {} + '@radix-ui/rect@1.1.3': {} '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.15)(react@19.2.6)(redux@5.0.1))(react@19.2.6)': dependencies: @@ -9574,12 +10057,12 @@ snapshots: postcss: 8.5.15 tailwindcss: 4.3.0 - '@tailwindcss/vite@4.3.0(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0))': + '@tailwindcss/vite@4.3.0(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0))': dependencies: '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 tailwindcss: 4.3.0 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0) '@tanstack/query-core@5.100.14': {} @@ -9849,15 +10332,15 @@ snapshots: '@types/validator@13.15.10': {} - '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2))(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2))(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/parser': 8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) '@typescript-eslint/scope-manager': 8.60.0 - '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) + '@typescript-eslint/type-utils': 8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.60.0 - eslint: 10.4.0(jiti@2.7.0) + eslint: 10.4.0(jiti@2.7.0)(supports-color@7.2.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.2) @@ -9865,23 +10348,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/parser@8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': dependencies: '@typescript-eslint/scope-manager': 8.60.0 '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.2) + '@typescript-eslint/typescript-estree': 8.60.0(supports-color@7.2.0)(typescript@6.0.2) '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + debug: 4.4.3(supports-color@7.2.0) + eslint: 10.4.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 6.0.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.60.0(typescript@6.0.2)': + '@typescript-eslint/project-service@8.60.0(supports-color@7.2.0)(typescript@6.0.2)': dependencies: '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.2) '@typescript-eslint/types': 8.60.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -9895,13 +10378,13 @@ snapshots: dependencies: typescript: 6.0.2 - '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/type-utils@8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': dependencies: '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.2) - '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2) - debug: 4.4.3 - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/typescript-estree': 8.60.0(supports-color@7.2.0)(typescript@6.0.2) + '@typescript-eslint/utils': 8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2) + debug: 4.4.3(supports-color@7.2.0) + eslint: 10.4.0(jiti@2.7.0)(supports-color@7.2.0) ts-api-utils: 2.5.0(typescript@6.0.2) typescript: 6.0.2 transitivePeerDependencies: @@ -9909,13 +10392,13 @@ snapshots: '@typescript-eslint/types@8.60.0': {} - '@typescript-eslint/typescript-estree@8.60.0(typescript@6.0.2)': + '@typescript-eslint/typescript-estree@8.60.0(supports-color@7.2.0)(typescript@6.0.2)': dependencies: - '@typescript-eslint/project-service': 8.60.0(typescript@6.0.2) + '@typescript-eslint/project-service': 8.60.0(supports-color@7.2.0)(typescript@6.0.2) '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@6.0.2) '@typescript-eslint/types': 8.60.0 '@typescript-eslint/visitor-keys': 8.60.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) minimatch: 10.2.5 semver: 7.8.1 tinyglobby: 0.2.16 @@ -9924,13 +10407,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0))(typescript@6.0.2)': + '@typescript-eslint/utils@8.60.0(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0))(supports-color@7.2.0)(typescript@6.0.2)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0)) '@typescript-eslint/scope-manager': 8.60.0 '@typescript-eslint/types': 8.60.0 - '@typescript-eslint/typescript-estree': 8.60.0(typescript@6.0.2) - eslint: 10.4.0(jiti@2.7.0) + '@typescript-eslint/typescript-estree': 8.60.0(supports-color@7.2.0)(typescript@6.0.2) + eslint: 10.4.0(jiti@2.7.0)(supports-color@7.2.0) typescript: 6.0.2 transitivePeerDependencies: - supports-color @@ -9940,10 +10423,10 @@ snapshots: '@typescript-eslint/types': 8.60.0 eslint-visitor-keys: 5.0.1 - '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0) accepts@1.3.8: dependencies: @@ -9967,9 +10450,9 @@ snapshots: add@2.0.6: {} - agent-base@6.0.2: + agent-base@6.0.2(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -10042,11 +10525,11 @@ snapshots: aws-ssl-profiles@1.1.2: {} - axios@1.16.1: + axios@1.16.1(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0): dependencies: - follow-redirects: 1.16.0 + follow-redirects: 1.16.0(debug@4.4.3(supports-color@7.2.0)) form-data: 4.0.5 - https-proxy-agent: 5.0.1 + https-proxy-agent: 5.0.1(supports-color@7.2.0) proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -10077,11 +10560,11 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.5: + body-parser@1.20.5(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 destroy: 1.2.0 http-errors: 2.0.1 @@ -10094,11 +10577,11 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@2.2.2: + body-parser@2.2.2(supports-color@7.2.0): dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 @@ -10414,13 +10897,17 @@ snapshots: date-fns@4.3.0: {} - debug@2.6.9: + debug@2.6.9(supports-color@7.2.0): dependencies: ms: 2.0.0 + optionalDependencies: + supports-color: 7.2.0 - debug@4.4.3: + debug@4.4.3(supports-color@7.2.0): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 7.2.0 decimal.js-light@2.5.1: {} @@ -10671,11 +11158,11 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.4.0(jiti@2.7.0): + eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)(supports-color@7.2.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5 + '@eslint/config-array': 0.23.5(supports-color@7.2.0) '@eslint/config-helpers': 0.6.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.1 @@ -10685,7 +11172,7 @@ snapshots: '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -10773,26 +11260,26 @@ snapshots: express-list-routes@1.3.2: {} - express-rate-limit@8.5.2(express@5.2.1): + express-rate-limit@8.5.2(express@5.2.1(supports-color@7.2.0)): dependencies: - express: 5.2.1 + express: 5.2.1(supports-color@7.2.0) ip-address: 10.2.0 - express@4.22.2: + express@4.22.2(supports-color@7.2.0): dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.5 + body-parser: 1.20.5(supports-color@7.2.0) content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.0.7 - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.2 + finalhandler: 1.3.2(supports-color@7.2.0) fresh: 0.5.2 http-errors: 2.0.1 merge-descriptors: 1.0.3 @@ -10804,8 +11291,8 @@ snapshots: qs: 6.15.2 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.2 - serve-static: 1.16.3 + send: 0.19.2(supports-color@7.2.0) + serve-static: 1.16.3(supports-color@7.2.0) setprototypeof: 1.2.0 statuses: 2.0.2 type-is: 1.6.18 @@ -10814,20 +11301,20 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.2.1: + express@5.2.1(supports-color@7.2.0): dependencies: accepts: 2.0.0 - body-parser: 2.2.2 + body-parser: 2.2.2(supports-color@7.2.0) content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 2.1.1 + finalhandler: 2.1.1(supports-color@7.2.0) fresh: 2.0.0 http-errors: 2.0.1 merge-descriptors: 2.0.0 @@ -10838,9 +11325,9 @@ snapshots: proxy-addr: 2.0.7 qs: 6.15.2 range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 + router: 2.2.0(supports-color@7.2.0) + send: 1.2.1(supports-color@7.2.0) + serve-static: 2.2.1(supports-color@7.2.0) statuses: 2.0.2 type-is: 2.1.0 vary: 1.1.2 @@ -10910,9 +11397,9 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.2: + finalhandler@1.3.2(supports-color@7.2.0): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -10922,9 +11409,9 @@ snapshots: transitivePeerDependencies: - supports-color - finalhandler@2.1.1: + finalhandler@2.1.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -10972,7 +11459,9 @@ snapshots: fn.name@1.1.0: {} - follow-redirects@1.16.0: {} + follow-redirects@1.16.0(debug@4.4.3(supports-color@7.2.0)): + optionalDependencies: + debug: 4.4.3(supports-color@7.2.0) for-in@1.0.2: {} @@ -11191,17 +11680,17 @@ snapshots: http@0.0.1-security: {} - https-proxy-agent@5.0.1: + https-proxy-agent@5.0.1(supports-color@7.2.0): dependencies: - agent-base: 6.0.2 - debug: 4.4.3 + agent-base: 6.0.2(supports-color@7.2.0) + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@7.2.0): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -11225,6 +11714,10 @@ snapshots: optionalDependencies: typescript: 6.0.2 + i18next@26.4.0(typescript@6.0.2): + optionalDependencies: + typescript: 6.0.2 + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -12221,63 +12714,63 @@ snapshots: queue-microtask@1.2.3: {} - radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + radix-ui@1.6.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-accessible-icon': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-alert-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-aspect-ratio': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-avatar': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-checkbox': 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-context-menu': 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-form': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-hover-card': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-menubar': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-one-time-password-field': 0.1.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-password-toggle-field': 0.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-progress': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-radio-group': 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-select': 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-separator': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slider': 1.3.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-switch': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toast': 1.2.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toggle-group': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-toolbar': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.15)(react@19.2.6) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/primitive': 1.1.7 + '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context': 1.2.2(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-direction': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.3.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.15)(react@19.2.6) + '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.15))(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) react: 19.2.6 react-dom: 19.2.6(react@19.2.6) optionalDependencies: @@ -12332,6 +12825,17 @@ snapshots: react-dom: 19.2.6(react@19.2.6) typescript: 6.0.2 + react-i18next@17.0.8(i18next@26.4.0(typescript@6.0.2))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(typescript@6.0.2): + dependencies: + '@babel/runtime': 7.29.7 + html-parse-stringify: 3.0.1 + i18next: 26.4.0(typescript@6.0.2) + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + react-dom: 19.2.6(react@19.2.6) + typescript: 6.0.2 + react-is@16.13.1: {} react-qr-code@2.0.21(react@19.2.6): @@ -12584,9 +13088,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 - router@2.2.0: + router@2.2.0(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -12637,9 +13141,9 @@ snapshots: semver@7.8.1: {} - send@0.19.2: + send@0.19.2(supports-color@7.2.0): dependencies: - debug: 2.6.9 + debug: 2.6.9(supports-color@7.2.0) depd: 2.0.0 destroy: 1.2.0 encodeurl: 2.0.0 @@ -12655,9 +13159,9 @@ snapshots: transitivePeerDependencies: - supports-color - send@1.2.1: + send@1.2.1(supports-color@7.2.0): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -12673,11 +13177,11 @@ snapshots: sequelize-pool@7.1.0: {} - sequelize@6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4): + sequelize@6.37.8(mysql2@3.22.3(@types/node@25.9.1))(pg-hstore@2.3.4)(supports-color@7.2.0): dependencies: '@types/debug': 4.1.13 '@types/validator': 13.15.10 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) dottie: 2.0.7 inflection: 1.13.4 lodash: 4.18.1 @@ -12697,21 +13201,21 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.16.3: + serve-static@1.16.3(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.2 + send: 0.19.2(supports-color@7.2.0) transitivePeerDependencies: - supports-color - serve-static@2.2.1: + serve-static@2.2.1(supports-color@7.2.0): dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1 + send: 1.2.1(supports-color@7.2.0) transitivePeerDependencies: - supports-color @@ -12721,14 +13225,14 @@ snapshots: setprototypeof@1.2.0: {} - shadcn@4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(typescript@6.0.2): + shadcn@4.8.0(@types/node@25.9.1)(babel-plugin-macros@3.1.0)(supports-color@7.2.0)(typescript@6.0.2): dependencies: - '@babel/core': 7.29.7 + '@babel/core': 7.29.7(supports-color@7.2.0) '@babel/parser': 7.29.7 - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@7.2.0))(supports-color@7.2.0) '@dotenvx/dotenvx': 1.69.1 - '@modelcontextprotocol/sdk': 1.29.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.29.0(supports-color@7.2.0)(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.28.2 commander: 14.0.3 @@ -12740,7 +13244,7 @@ snapshots: fast-glob: 3.3.3 fs-extra: 11.3.5 fuzzysort: 3.1.0 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@7.2.0) kleur: 4.1.5 msw: 2.14.6(@types/node@25.9.1)(typescript@6.0.2) node-fetch: 3.3.2 @@ -12874,10 +13378,10 @@ snapshots: stylis@4.2.0: {} - stylus@0.62.0: + stylus@0.62.0(supports-color@7.2.0): dependencies: '@adobe/css-tools': 4.3.3 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) glob: 7.2.3 sax: 1.3.0 source-map: 0.7.6 @@ -13006,13 +13510,13 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(tsx@4.21.0)(typescript@6.0.2): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.15)(supports-color@7.2.0)(tsx@4.21.0)(typescript@6.0.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) esbuild: 0.27.7 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 @@ -13034,13 +13538,13 @@ snapshots: - tsx - yaml - tsup@8.5.1(jiti@2.7.0)(postcss@8.5.25)(tsx@4.21.0)(typescript@6.0.2): + tsup@8.5.1(jiti@2.7.0)(postcss@8.5.25)(supports-color@7.2.0)(tsx@4.21.0)(typescript@6.0.2): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) esbuild: 0.27.7 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 @@ -13101,7 +13605,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript-plugin-css-modules@5.2.0(ts-node@10.9.2(@types/node@25.9.1)(typescript@6.0.2))(typescript@6.0.2): + typescript-plugin-css-modules@5.2.0(supports-color@7.2.0)(ts-node@10.9.2(@types/node@25.9.1)(typescript@6.0.2))(typescript@6.0.2): dependencies: '@types/postcss-modules-local-by-default': 4.0.2 '@types/postcss-modules-scope': 3.0.4 @@ -13120,7 +13624,7 @@ snapshots: tsconfig-paths: 4.2.0 typescript: 6.0.2 optionalDependencies: - stylus: 0.62.0 + stylus: 0.62.0(supports-color@7.2.0) transitivePeerDependencies: - supports-color - ts-node @@ -13231,7 +13735,7 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-html@3.2.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)): + vite-plugin-html@3.2.2(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)): dependencies: '@rollup/pluginutils': 4.2.1 colorette: 2.0.20 @@ -13245,19 +13749,19 @@ snapshots: html-minifier-terser: 6.1.0 node-html-parser: 5.4.2 pathe: 0.2.0 - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0) - vite-tsconfig-paths@6.1.1(typescript@6.0.2)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0)): + vite-tsconfig-paths@6.1.1(supports-color@7.2.0)(typescript@6.0.2)(vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0)): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@7.2.0) globrex: 0.1.2 tsconfck: 3.1.6(typescript@6.0.2) - vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0) + vite: 8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color - typescript - vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0)(terser@5.48.0)(tsx@4.21.0): + vite@8.0.14(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.7.0)(less@4.6.4)(sass@1.100.0)(stylus@0.62.0(supports-color@7.2.0))(terser@5.48.0)(tsx@4.21.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -13271,7 +13775,7 @@ snapshots: jiti: 2.7.0 less: 4.6.4 sass: 1.100.0 - stylus: 0.62.0 + stylus: 0.62.0(supports-color@7.2.0) terser: 5.48.0 tsx: 4.21.0