From 2f23d75b6fed9e16325f44025a10f2d4c90c9a5e Mon Sep 17 00:00:00 2001 From: David Date: Mon, 7 Sep 2026 13:46:40 +0200 Subject: [PATCH] feat(onboarding): add initial onboarding module with domain and persistence layers - Created package.json for onboarding module with dependencies and scripts. - Implemented InitialOnboarding aggregate with validation and state management. - Added Sequelize model for initial onboarding with appropriate attributes and validations. - Developed mappers for converting between domain and persistence models. - Included tests for aggregate behavior and Sequelize model validation. - Configured TypeScript settings for the onboarding module. --- modules/onboarding/package.json | 61 ++++ modules/onboarding/src/api/domain/index.ts | 1 + .../api/domain/initial-onboarding/index.ts | 1 + .../initial-onboarding.aggregate.test.ts | 72 ++++ .../initial-onboarding.aggregate.ts | 79 +++++ .../persistence/sequelize/index.ts | 6 + .../persistence/sequelize/mappers/index.ts | 1 + ...e-initial-onboarding-domain.mapper.test.ts | 142 ++++++++ ...uelize-initial-onboarding-domain.mapper.ts | 62 ++++ .../persistence/sequelize/models/index.ts | 1 + ...sequelize-initial-onboarding.model.test.ts | 64 ++++ .../sequelize-initial-onboarding.model.ts | 49 +++ modules/onboarding/tsconfig.json | 28 ++ pnpm-lock.yaml | 332 ++++++++++++++++++ 14 files changed, 899 insertions(+) create mode 100644 modules/onboarding/package.json create mode 100644 modules/onboarding/src/api/domain/index.ts create mode 100644 modules/onboarding/src/api/domain/initial-onboarding/index.ts create mode 100644 modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.test.ts create mode 100644 modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/index.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/index.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.test.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/models/index.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.test.ts create mode 100644 modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.ts create mode 100644 modules/onboarding/tsconfig.json diff --git a/modules/onboarding/package.json b/modules/onboarding/package.json new file mode 100644 index 00000000..ebefac77 --- /dev/null +++ b/modules/onboarding/package.json @@ -0,0 +1,61 @@ +{ + "name": "@erp/onboarding", + "version": "0.10.8", + "private": true, + "type": "module", + "sideEffects": false, + "scripts": { + "typecheck": "tsc -p tsconfig.json --noEmit", + "check": "biome check .", + "lint": "biome lint .", + "clean": "rimraf .turbo node_modules dist" + }, + "exports": { + ".": "./src/common/index.ts", + "./common": "./src/common/index.ts", + "./api": "./src/api/index.ts", + "./api/domain": "./src/api/domain/index.ts", + "./client": "./src/web/manifest.ts", + "./globals.css": "./src/web/globals.css", + "./components": "./src/web/components/index.ts" + }, + "peerDependencies": { + "react": "^19.2.5", + "react-dom": "^19.2.5" + }, + "dependencies": { + "@erp/auth": "workspace:*", + "@erp/core": "workspace:*", + "@erp/identity": "workspace:*", + "@hookform/resolvers": "^5.2.2", + "@repo/i18next": "workspace:*", + "@repo/rdx-audit": "workspace:*", + "@repo/rdx-criteria": "workspace:*", + "@repo/rdx-ddd": "workspace:*", + "@repo/rdx-logger": "workspace:*", + "@repo/rdx-ui": "workspace:*", + "@repo/rdx-utils": "workspace:*", + "@repo/shadcn-ui": "workspace:*", + "@tanstack/react-query": "^5.98.0", + "@tanstack/react-table": "^8.21.3", + "express": "^4.22.1", + "lucide-react": "^1.8.0", + "react-hook-form": "^7.72.1", + "react-i18next": "^17.0.2", + "react-router-dom": "^7.14.0", + "sequelize": "^6.37.8", + "use-debounce": "^10.1.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/jest": "^30.0.0", + "@types/node": "^25.9.1", + "@types/react": "^19.2.14", + "@types/react-router-dom": "^5.3.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "rimraf": "^6.1.3", + "typescript": "^6.0.2" + } +} diff --git a/modules/onboarding/src/api/domain/index.ts b/modules/onboarding/src/api/domain/index.ts new file mode 100644 index 00000000..12594351 --- /dev/null +++ b/modules/onboarding/src/api/domain/index.ts @@ -0,0 +1 @@ +export * from "./initial-onboarding"; diff --git a/modules/onboarding/src/api/domain/initial-onboarding/index.ts b/modules/onboarding/src/api/domain/initial-onboarding/index.ts new file mode 100644 index 00000000..44fbed53 --- /dev/null +++ b/modules/onboarding/src/api/domain/initial-onboarding/index.ts @@ -0,0 +1 @@ +export * from "./initial-onboarding.aggregate"; diff --git a/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.test.ts b/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.test.ts new file mode 100644 index 00000000..624fd44b --- /dev/null +++ b/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.test.ts @@ -0,0 +1,72 @@ +import { test } from "node:test"; + +import { UniqueID, UtcDateTime } from "@repo/rdx-ddd"; +import { Maybe } from "@repo/rdx-utils"; + +import { InitialOnboarding } from "./initial-onboarding.aggregate"; + +const check = (value: unknown) => { + if (!value) throw new Error("Assertion failed"); +}; + +const date = () => UtcDateTime.now(); +const props = ( + status: "pending" | "completed", + companyId = Maybe.none(), + completedAt = Maybe.none() +) => ({ + accountId: UniqueID.generateNewID(), + status, + companyId, + createdAt: date(), + updatedAt: date(), + completedAt, +}); +test("creates pending", () => { + const accountId = UniqueID.generateNewID(); + const r = InitialOnboarding.createPending(accountId); + check(r.isSuccess); + check(r.data.status === "pending"); + check(r.data.companyId.isNone()); + check(r.data.completedAt.isNone()); + check(r.data.accountId.equals(accountId)); +}); +test("rehydrates valid states", () => { + check(InitialOnboarding.rehydrate(props("pending"), UniqueID.generateNewID()).isSuccess); + check( + InitialOnboarding.rehydrate( + props("completed", Maybe.some(UniqueID.generateNewID()), Maybe.some(date())), + UniqueID.generateNewID() + ).isSuccess + ); +}); +test("rejects invalid state combinations", () => { + check( + InitialOnboarding.rehydrate( + props("pending", Maybe.some(UniqueID.generateNewID())), + UniqueID.generateNewID() + ).isFailure + ); + check( + InitialOnboarding.rehydrate( + props("pending", Maybe.none(), Maybe.some(date())), + UniqueID.generateNewID() + ).isFailure + ); + check(InitialOnboarding.rehydrate(props("completed"), UniqueID.generateNewID()).isFailure); + check( + InitialOnboarding.rehydrate( + props("completed", Maybe.some(UniqueID.generateNewID())), + UniqueID.generateNewID() + ).isFailure + ); +}); +test("completes once", () => { + const o = InitialOnboarding.createPending(UniqueID.generateNewID()).data; + const id = UniqueID.generateNewID(); + check(o.complete({ companyId: id, completedAt: date(), updatedAt: date() }).isSuccess); + check(o.status === "completed"); + check(o.companyId.unwrap().equals(id)); + check(o.completedAt.isSome()); + check(o.complete({ companyId: id, completedAt: date(), updatedAt: date() }).isFailure); +}); diff --git a/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.ts b/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.ts new file mode 100644 index 00000000..714d4080 --- /dev/null +++ b/modules/onboarding/src/api/domain/initial-onboarding/initial-onboarding.aggregate.ts @@ -0,0 +1,79 @@ +import { AggregateRoot, UniqueID, type UtcDateTime } from "@repo/rdx-ddd"; +import { Maybe, Result } from "@repo/rdx-utils"; + +export type InitialOnboardingStatus = "pending" | "completed"; +export interface InitialOnboardingProps { + accountId: UniqueID; + status: InitialOnboardingStatus; + companyId: Maybe; + completedAt: Maybe; +} +export class InitialOnboarding extends AggregateRoot { + private static validate(props: InitialOnboardingProps): Result { + const valid = + props.status === "pending" + ? props.companyId.isNone() && props.completedAt.isNone() + : props.companyId.isSome() && props.completedAt.isSome(); + return valid + ? Result.ok(undefined) + : Result.fail(new Error("Invalid initial onboarding state.")); + } + + static createPending(accountId: UniqueID): Result { + return InitialOnboarding.rehydrate( + { + accountId, + status: "pending", + companyId: Maybe.none(), + completedAt: Maybe.none(), + }, + UniqueID.generateNewID() + ); + } + + static rehydrate(props: InitialOnboardingProps, id: UniqueID): Result { + const valid = InitialOnboarding.validate(props); + return valid.isFailure ? Result.fail(valid.error) : Result.ok(new InitialOnboarding(props, id)); + } + + complete(params: { + companyId: UniqueID; + completedAt: UtcDateTime; + updatedAt: UtcDateTime; + }): Result { + if (this.props.status === "completed") + return Result.fail(new Error("Initial onboarding already completed.")); + + const props = { + ...this.props, + status: "completed" as const, + companyId: Maybe.some(params.companyId), + completedAt: Maybe.some(params.completedAt), + updatedAt: params.updatedAt, + }; + + const valid = InitialOnboarding.validate(props); + + if (valid.isFailure) { + return Result.fail(valid.error); + } + this.props = props; + return Result.ok(undefined); + } + + get accountId() { + return this.props.accountId; + } + + get status() { + return this.props.status; + } + + get companyId() { + return this.props.companyId; + } + + get completedAt() { + return this.props.completedAt; + } +} diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/index.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/index.ts new file mode 100644 index 00000000..a7c9624f --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/index.ts @@ -0,0 +1,6 @@ +import initialOnboardingModelInit from "./models/sequelize-initial-onboarding.model"; + +export * from "./mappers"; +export * from "./models"; + +export const models = [initialOnboardingModelInit]; diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/index.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/index.ts new file mode 100644 index 00000000..a2a1e3d2 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/index.ts @@ -0,0 +1 @@ +export * from "./sequelize-initial-onboarding-domain.mapper"; diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.test.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.test.ts new file mode 100644 index 00000000..14614100 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.test.ts @@ -0,0 +1,142 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { UniqueID, UtcDateTime } from "@repo/rdx-ddd"; +import { Maybe } from "@repo/rdx-utils"; +import type { InferAttributes } from "sequelize"; +import { Sequelize } from "sequelize"; + +import type { InitialOnboardingStatus } from "../../../../domain"; +import { InitialOnboarding } from "../../../../domain"; +import initialOnboardingModelInit, { + InitialOnboardingModel, +} from "../models/sequelize-initial-onboarding.model"; + +import { SequelizeInitialOnboardingDomainMapper } from "./sequelize-initial-onboarding-domain.mapper"; + +const database = new Sequelize("mysql://root:pass@localhost:3306/erp_test", { logging: false }); +initialOnboardingModelInit(database); +const mapper = new SequelizeInitialOnboardingDomainMapper(); +const id = "11111111-1111-4111-8111-111111111111"; +const accountId = "22222222-2222-4222-8222-222222222222"; +const companyId = "33333333-3333-4333-8333-333333333333"; +const completedAt = "2026-01-02T10:30:00.789Z"; + +function aggregate(status: InitialOnboardingStatus) { + const result = InitialOnboarding.rehydrate( + { + accountId: UniqueID.create(accountId).data, + status, + companyId: + status === "completed" ? Maybe.some(UniqueID.create(companyId).data) : Maybe.none(), + + completedAt: + status === "completed" + ? Maybe.some(UtcDateTime.createFromISO(completedAt).data) + : Maybe.none(), + }, + UniqueID.create(id).data + ); + assert.equal(result.isSuccess, true); + return result.data; +} + +function row(patch: Partial> = {}) { + return InitialOnboardingModel.build( + { + id, + account_id: accountId, + status: "pending", + company_id: null, + completed_at: null, + ...patch, + }, + { raw: true } + ); +} + +for (const status of ["pending", "completed"] as const) { + test(`toPersistence ${status}`, () => { + const result = mapper.mapToPersistence(aggregate(status)); + assert.equal(result.isSuccess, true); + assert.deepEqual(result.data, { + id, + account_id: accountId, + status, + company_id: status === "completed" ? companyId : null, + completed_at: status === "completed" ? completedAt : null, + }); + }); + + test(`toDomain ${status}`, () => { + const result = mapper.mapToDomain( + row({ + status, + company_id: status === "completed" ? companyId : null, + completed_at: status === "completed" ? completedAt : null, + }) + ); + assert.equal(result.isSuccess, true); + const value = result.data; + assert.ok(value instanceof InitialOnboarding); + assert.ok(value.id instanceof UniqueID); + assert.equal(value.id.toPrimitive(), id); + assert.equal(value.accountId.toPrimitive(), accountId); + assert.equal(value.status, status); + assert.equal(value.companyId.isSome(), status === "completed"); + assert.equal(value.completedAt.isSome(), status === "completed"); + if (status === "completed") { + assert.ok(value.companyId.unwrap() instanceof UniqueID); + assert.ok(value.completedAt.unwrap() instanceof UtcDateTime); + assert.equal(value.companyId.unwrap().toPrimitive(), companyId); + assert.equal(value.completedAt.unwrap().toISOString(), completedAt); + } + }); +} + +const invalidStates: [string, Partial>][] = [ + ["pending + companyId", { company_id: companyId }], + ["pending + completedAt", { completed_at: completedAt }], + ["completed sin companyId", { status: "completed", completed_at: completedAt }], + ["completed sin completedAt", { status: "completed", company_id: companyId }], +]; +for (const [name, patch] of invalidStates) { + test(name, () => { + const result = mapper.mapToDomain(row(patch)); + assert.equal(result.isFailure, true); + assert.equal(result.error.message, "Invalid initial onboarding persistence state."); + }); +} + +test("toDomain accepts Sequelize Date timestamps and preserves UTC milliseconds", () => { + const result = mapper.mapToDomain( + row({ + status: "completed", + company_id: companyId, + completed_at: new Date(completedAt), + }) + ); + assert.equal(result.isSuccess, true); + assert.equal(result.data.completedAt.unwrap().toISOString(), completedAt); +}); + +test("toDomain rejects an unknown status explicitly", () => { + const result = mapper.mapToDomain( + row({ status: "unknown", company_id: companyId, completed_at: completedAt }) + ); + assert.equal(result.isFailure, true); + assert.equal(result.error.message, "Invalid initial onboarding persistence status."); +}); + +test("toDomain propagates invalid identifiers and timestamps as failures", () => { + for (const patch of [ + { id: "invalid" }, + { account_id: "invalid" }, + { status: "completed", company_id: "invalid", completed_at: completedAt }, + { created_at: "invalid" }, + { updated_at: "invalid" }, + { status: "completed", company_id: companyId, completed_at: "invalid" }, + ]) { + assert.equal(mapper.mapToDomain(row(patch)).isFailure, true); + } +}); diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.ts new file mode 100644 index 00000000..67edb158 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/mappers/sequelize-initial-onboarding-domain.mapper.ts @@ -0,0 +1,62 @@ +import { UniqueID, UtcDateTime, maybeFromNullableResult, maybeToNullable } from "@repo/rdx-ddd"; +import { Result } from "@repo/rdx-utils"; + +import { InitialOnboarding } from "../../../../domain"; +import type { InitialOnboardingCreationAttributes, InitialOnboardingModel } from "../models"; + +function toUtcDateTime(value: Date | string): Result { + try { + return UtcDateTime.createFromISO(value instanceof Date ? value.toISOString() : value); + } catch (error: unknown) { + return Result.fail(error instanceof Error ? error : new Error(String(error))); + } +} + +export class SequelizeInitialOnboardingDomainMapper { + public mapToDomain(source: InitialOnboardingModel): Result { + if (source.status !== "pending" && source.status !== "completed") { + return Result.fail(new Error("Invalid initial onboarding persistence status.")); + } + if ( + (source.status === "pending" && + (source.company_id !== null || source.completed_at !== null)) || + (source.status === "completed" && + (source.company_id === null || source.completed_at === null)) + ) { + return Result.fail(new Error("Invalid initial onboarding persistence state.")); + } + + const id = UniqueID.create(source.id); + if (id.isFailure) return Result.fail(id.error); + const accountId = UniqueID.create(source.account_id); + if (accountId.isFailure) return Result.fail(accountId.error); + const companyId = maybeFromNullableResult(source.company_id, (value) => UniqueID.create(value)); + if (companyId.isFailure) return Result.fail(companyId.error); + const completedAt = maybeFromNullableResult(source.completed_at, (value) => + toUtcDateTime(value) + ); + if (completedAt.isFailure) return Result.fail(completedAt.error); + + return InitialOnboarding.rehydrate( + { + accountId: accountId.data, + status: source.status, + companyId: companyId.data, + completedAt: completedAt.data, + }, + id.data + ); + } + + public mapToPersistence( + source: InitialOnboarding + ): Result { + return Result.ok({ + id: source.id.toPrimitive(), + account_id: source.accountId.toPrimitive(), + status: source.status, + company_id: maybeToNullable(source.companyId, (value) => value.toPrimitive()), + completed_at: maybeToNullable(source.completedAt, (value) => value.toISOString()), + }); + } +} diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/index.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/index.ts new file mode 100644 index 00000000..7a4c99d7 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/index.ts @@ -0,0 +1 @@ +export * from "./sequelize-initial-onboarding.model"; diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.test.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.test.ts new file mode 100644 index 00000000..83fb3bf0 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.test.ts @@ -0,0 +1,64 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { DataTypes, Sequelize } from "sequelize"; + +import initialOnboardingModelInit, { + InitialOnboardingModel, +} from "./sequelize-initial-onboarding.model"; + +const database = new Sequelize("mysql://root:pass@localhost:3306/erp_test", { logging: false }); +initialOnboardingModelInit(database); + +test("model declares table, nullability, UUIDs, timestamps and unique account without cross-module FKs", () => { + assert.equal(InitialOnboardingModel.getTableName(), "initial_onboardings"); + assert.equal(InitialOnboardingModel.options.timestamps, false); + const attributes = InitialOnboardingModel.getAttributes(); + assert.deepEqual(Object.keys(attributes).sort(), [ + "account_id", + "company_id", + "completed_at", + "created_at", + "id", + "status", + "updated_at", + ]); + for (const key of ["id", "account_id", "status", "created_at", "updated_at"] as const) { + assert.equal(attributes[key].allowNull, false); + } + for (const key of ["company_id", "completed_at"] as const) { + assert.equal(attributes[key].allowNull, true); + } + for (const key of ["id", "account_id", "company_id"] as const) { + assert.ok(attributes[key].type instanceof DataTypes.UUID); + assert.equal(attributes[key].references, undefined); + } + for (const key of ["created_at", "updated_at", "completed_at"] as const) { + assert.ok(attributes[key].type instanceof DataTypes.DATE); + } + assert.ok(attributes.status.type instanceof DataTypes.STRING); + assert.equal(attributes.id.primaryKey, true); + const unique = InitialOnboardingModel.options.indexes?.find( + (index) => index.name === "uq_initial_onboardings_account_id" + ); + assert.equal(unique?.unique, true); + assert.deepEqual(unique?.fields, ["account_id"]); +}); + +test("model validation accepts only pending and completed", async () => { + for (const status of ["pending", "completed", "processing", "failed", "cancelled", "draft"]) { + const model = InitialOnboardingModel.build({ + id: "11111111-1111-4111-8111-111111111111", + account_id: "22222222-2222-4222-8222-222222222222", + status, + }); + if (status === "pending" || status === "completed") { + await model.validate({ fields: ["status"] }); + } else { + await assert.rejects( + model.validate({ fields: ["status"] }), + /Validation isIn on status failed/ + ); + } + } +}); diff --git a/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.ts b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.ts new file mode 100644 index 00000000..3be56a82 --- /dev/null +++ b/modules/onboarding/src/api/infrastructure/persistence/sequelize/models/sequelize-initial-onboarding.model.ts @@ -0,0 +1,49 @@ +import type { InferAttributes, InferCreationAttributes, Sequelize } from "sequelize"; +import { DataTypes, Model } from "sequelize"; + +export type InitialOnboardingCreationAttributes = InferCreationAttributes; + +export class InitialOnboardingModel extends Model< + InferAttributes, + InferCreationAttributes +> { + declare id: string; + declare account_id: string; + declare status: string; + declare company_id: string | null; + declare completed_at: Date | string | null; +} + +export default (database: Sequelize) => { + InitialOnboardingModel.init( + { + id: { type: DataTypes.UUID, primaryKey: true, allowNull: false }, + // Referencias lógicas entre módulos, sin dependencias de sus modelos ORM. + account_id: { type: DataTypes.UUID, allowNull: false }, + status: { + type: DataTypes.STRING, + allowNull: false, + validate: { isIn: [["pending", "completed"]] }, + }, + company_id: { type: DataTypes.UUID, allowNull: true, defaultValue: null }, + completed_at: { type: DataTypes.DATE, allowNull: true, defaultValue: null }, + }, + { + sequelize: database, + modelName: "InitialOnboardingModel", + tableName: "initial_onboardings", + charset: "utf8mb4", + collate: "utf8mb4_unicode_ci", + underscored: true, + timestamps: true, + indexes: [ + { + name: "uq_initial_onboardings_account_id", + fields: ["account_id"], + unique: true, + }, + ], + } + ); + return InitialOnboardingModel; +}; diff --git a/modules/onboarding/tsconfig.json b/modules/onboarding/tsconfig.json new file mode 100644 index 00000000..c034faa0 --- /dev/null +++ b/modules/onboarding/tsconfig.json @@ -0,0 +1,28 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@erp/onboarding/*": ["./src/*"] + }, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "types": ["jest", "node"] + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4a1ac06..3ad0d392 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1213,6 +1213,103 @@ importers: specifier: ^6.0.2 version: 6.0.2 + modules/onboarding: + dependencies: + '@erp/auth': + specifier: workspace:* + version: link:../auth + '@erp/core': + specifier: workspace:* + version: link:../core + '@erp/identity': + specifier: workspace:* + version: link:../identity + '@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-audit': + specifier: workspace:* + version: link:../../packages/rdx-audit + '@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/jest': + specifier: ^30.0.0 + version: 30.0.0 + '@types/node': + specifier: ^25.9.1 + version: 25.9.1 + '@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': @@ -2773,6 +2870,30 @@ packages: '@types/node': optional: true + '@jest/diff-sequences@30.5.0': + resolution: {integrity: sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.5.1': + resolution: {integrity: sha512-WcRWhHQdTMRDpyWKZ/6MINBmovI7zeD+bL8wFjCncRV3NQOwKy1X45IfyblfHR4k/XciIlNEdFL9QjFO+HNKOg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.5.0': + resolution: {integrity: sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.5.0': + resolution: {integrity: sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/schemas@30.5.0': + resolution: {integrity: sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.5.1': + resolution: {integrity: sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -4145,6 +4266,9 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@sinclair/typebox@0.34.52': + resolution: {integrity: sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==} + '@sindresorhus/merge-streams@4.0.0': resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} @@ -4419,6 +4543,18 @@ packages: '@types/inquirer@9.0.9': resolution: {integrity: sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -4505,6 +4641,9 @@ packages: '@types/set-cookie-parser@2.4.10': resolution: {integrity: sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} @@ -4523,6 +4662,12 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.60.0': resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4662,6 +4807,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -4833,6 +4982,10 @@ packages: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -5330,6 +5483,10 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -5411,6 +5568,10 @@ packages: resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==} engines: {node: '>=0.10.0'} + expect@30.5.1: + resolution: {integrity: sha512-m8YrYgvKe9+9gEnWEuKz+qCGfHqkrff7PPfyDnOFkjsfYRKqiYyOxDFMFieCGAuhtk/VNm63tvNgKZVzVy+Hvg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + express-list-routes@1.3.2: resolution: {integrity: sha512-bwpS3aagDdzmtnIlYb9BDGoaIES3RQaOVNugfBUESM6kqkv+Y1bvztDju8wARzy8FDzuO6/HXT6OCGCiNd9f+g==} @@ -6026,6 +6187,30 @@ packages: engines: {node: '>=10'} hasBin: true + jest-diff@30.5.1: + resolution: {integrity: sha512-e3cNNMpv8Kh20MjjphTXs+3Vz7DQyLM1nft7KJhnh46atFhjVJRa+0Hq0beywuwsACtMQUBihQkFl8zxb7gt1Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.5.1: + resolution: {integrity: sha512-aroZVqwOz/wC2y6pC+obgFWKV9viaQWQTTSB6W5H55+egtUczIfXR/rxExTv92xD/ADYwpqaYfVWx1aqwKg7FA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.5.1: + resolution: {integrity: sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.5.1: + resolution: {integrity: sha512-9fVjc3leUpGID2/by/LU4Dvdcp7PFh9LlxS3QRWK3ABm+KtvEVsG/AEGeLY3gKOZsjkBxyfwGltoAVlW7dygHg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-regex-util@30.5.0: + resolution: {integrity: sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.5.1: + resolution: {integrity: sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -6853,6 +7038,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@30.5.1: + resolution: {integrity: sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -6955,6 +7144,12 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.8: + resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} + react-qr-code@2.0.21: resolution: {integrity: sha512-xaywjo0eaF4S3LOz6ns5eoPbM2E+q9HYl4VATYpxK4bBniOhQ9noY2RJ9G4SnZFhUwzx63FUT6KdHzfKgUwyuQ==} peerDependencies: @@ -7325,6 +7520,10 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} peerDependencies: @@ -7357,6 +7556,10 @@ packages: stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -8838,6 +9041,33 @@ snapshots: optionalDependencies: '@types/node': 25.9.1 + '@jest/diff-sequences@30.5.0': {} + + '@jest/expect-utils@30.5.1': + dependencies: + '@jest/get-type': 30.5.0 + + '@jest/get-type@30.5.0': {} + + '@jest/pattern@30.5.0': + dependencies: + '@types/node': 25.9.1 + jest-regex-util: 30.5.0 + + '@jest/schemas@30.5.0': + dependencies: + '@sinclair/typebox': 0.34.52 + + '@jest/types@30.5.1': + dependencies: + '@jest/pattern': 30.5.0 + '@jest/schemas': 30.5.0 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.9.1 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -10114,6 +10344,8 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} + '@sinclair/typebox@0.34.52': {} + '@sindresorhus/merge-streams@4.0.0': {} '@so-ric/colorspace@1.1.6': @@ -10362,6 +10594,21 @@ snapshots: '@types/through': 0.0.33 rxjs: 7.8.2 + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@30.0.0': + dependencies: + expect: 30.5.1 + pretty-format: 30.5.1 + '@types/json-schema@7.0.15': {} '@types/jsonwebtoken@9.0.10': @@ -10467,6 +10714,8 @@ snapshots: dependencies: '@types/node': 25.9.1 + '@types/stack-utils@2.0.3': {} + '@types/statuses@2.0.6': {} '@types/through@0.0.33': @@ -10481,6 +10730,12 @@ snapshots: '@types/validator@13.15.10': {} + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@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 @@ -10639,6 +10894,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + any-promise@1.3.0: {} arg@4.1.3: {} @@ -10828,6 +11085,8 @@ snapshots: dependencies: readdirp: 5.0.0 + ci-info@4.4.0: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -11294,6 +11553,8 @@ snapshots: escape-html@1.0.3: {} + escape-string-regexp@2.0.0: {} + escape-string-regexp@4.0.0: {} eslint-scope@9.1.2: @@ -11407,6 +11668,15 @@ snapshots: dependencies: homedir-polyfill: 1.0.3 + expect@30.5.1: + dependencies: + '@jest/expect-utils': 30.5.1 + '@jest/get-type': 30.5.0 + jest-matcher-utils: 30.5.1 + jest-message-util: 30.5.1 + jest-mock: 30.5.1 + jest-util: 30.5.1 + express-list-routes@1.3.2: {} express-rate-limit@8.5.2(express@5.2.1(supports-color@7.2.0)): @@ -12047,6 +12317,51 @@ snapshots: filelist: 1.0.6 picocolors: 1.1.1 + jest-diff@30.5.1: + dependencies: + '@jest/diff-sequences': 30.5.0 + '@jest/get-type': 30.5.0 + chalk: 4.1.2 + pretty-format: 30.5.1 + + jest-matcher-utils@30.5.1: + dependencies: + '@jest/get-type': 30.5.0 + chalk: 4.1.2 + jest-diff: 30.5.1 + pretty-format: 30.5.1 + + jest-message-util@30.5.1: + dependencies: + '@babel/code-frame': 7.29.7 + '@jest/types': 30.5.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.5.1 + picomatch: 4.0.4 + pretty-format: 30.5.1 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.5.1: + dependencies: + '@jest/expect-utils': 30.5.1 + '@jest/types': 30.5.1 + '@types/node': 25.9.1 + jest-util: 30.5.1 + + jest-regex-util@30.5.0: {} + + jest-util@30.5.1: + dependencies: + '@jest/types': 30.5.1 + '@types/node': 25.9.1 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + jiti@2.7.0: {} joi@18.2.1: @@ -12828,6 +13143,13 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@30.5.1: + dependencies: + '@jest/react-is-18': react-is@18.3.1 + '@jest/react-is-19': react-is@19.2.8 + '@jest/schemas': 30.5.0 + ansi-styles: 5.2.0 + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -12987,6 +13309,10 @@ snapshots: react-is@16.13.1: {} + react-is@18.3.1: {} + + react-is@19.2.8: {} + react-qr-code@2.0.21(react@19.2.6): dependencies: prop-types: 15.8.1 @@ -13459,6 +13785,8 @@ snapshots: sisteransi@1.0.5: {} + slash@3.0.0: {} + sonner@2.0.7(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: react: 19.2.6 @@ -13481,6 +13809,10 @@ snapshots: stack-trace@0.0.10: {} + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + statuses@2.0.2: {} stdin-discarder@0.2.2: {}