From ac274ba8956965117a3f32d7e74aad91d5faf7c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 14:25:58 +0200 Subject: [PATCH 01/14] build(deps): bump the production-dependencies group with 20 updates (#1264) Bumps the production-dependencies group with 20 updates: | Package | From | To | | --- | --- | --- | | [@ai-sdk/openai](https://github.com/vercel/ai) | `3.0.52` | `3.0.53` | | [@nestjs/cli](https://github.com/nestjs/nest-cli) | `11.0.19` | `11.0.21` | | [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common) | `11.1.18` | `11.1.19` | | [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core) | `11.1.18` | `11.1.19` | | [@nestjs/platform-express](https://github.com/nestjs/nest/tree/HEAD/packages/platform-express) | `11.1.18` | `11.1.19` | | [@nestjs/platform-socket.io](https://github.com/nestjs/nest/tree/HEAD/packages/platform-socket.io) | `11.1.18` | `11.1.19` | | [@nestjs/schedule](https://github.com/nestjs/schedule) | `6.1.1` | `6.1.3` | | [@nestjs/websockets](https://github.com/nestjs/nest/tree/HEAD/packages/websockets) | `11.1.18` | `11.1.19` | | [ai](https://github.com/vercel/ai) | `6.0.158` | `6.0.168` | | [@angular/animations](https://github.com/angular/angular/tree/HEAD/packages/animations) | `21.2.8` | `21.2.9` | | [@angular/cdk](https://github.com/angular/components) | `21.2.6` | `21.2.7` | | [@angular/common](https://github.com/angular/angular/tree/HEAD/packages/common) | `21.2.8` | `21.2.9` | | [@angular/compiler](https://github.com/angular/angular/tree/HEAD/packages/compiler) | `21.2.8` | `21.2.9` | | [@angular/compiler-cli](https://github.com/angular/angular/tree/HEAD/packages/compiler-cli) | `21.2.8` | `21.2.9` | | [@angular/core](https://github.com/angular/angular/tree/HEAD/packages/core) | `21.2.8` | `21.2.9` | | [@angular/forms](https://github.com/angular/angular/tree/HEAD/packages/forms) | `21.2.8` | `21.2.9` | | [@angular/material](https://github.com/angular/components) | `21.2.6` | `21.2.7` | | [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) | `21.2.8` | `21.2.9` | | [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) | `21.2.8` | `21.2.9` | | [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) | `21.2.8` | `21.2.9` | Updates `@ai-sdk/openai` from 3.0.52 to 3.0.53 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/@ai-sdk/openai@3.0.52...@ai-sdk/openai@3.0.53) Updates `@nestjs/cli` from 11.0.19 to 11.0.21 - [Release notes](https://github.com/nestjs/nest-cli/releases) - [Commits](https://github.com/nestjs/nest-cli/compare/11.0.19...11.0.21) Updates `@nestjs/common` from 11.1.18 to 11.1.19 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.19/packages/common) Updates `@nestjs/core` from 11.1.18 to 11.1.19 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.19/packages/core) Updates `@nestjs/platform-express` from 11.1.18 to 11.1.19 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.19/packages/platform-express) Updates `@nestjs/platform-socket.io` from 11.1.18 to 11.1.19 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.19/packages/platform-socket.io) Updates `@nestjs/schedule` from 6.1.1 to 6.1.3 - [Release notes](https://github.com/nestjs/schedule/releases) - [Commits](https://github.com/nestjs/schedule/compare/6.1.1...6.1.3) Updates `@nestjs/websockets` from 11.1.18 to 11.1.19 - [Release notes](https://github.com/nestjs/nest/releases) - [Commits](https://github.com/nestjs/nest/commits/v11.1.19/packages/websockets) Updates `ai` from 6.0.158 to 6.0.168 - [Release notes](https://github.com/vercel/ai/releases) - [Changelog](https://github.com/vercel/ai/blob/main/CHANGELOG.md) - [Commits](https://github.com/vercel/ai/compare/ai@6.0.158...ai@6.0.168) Updates `@angular/animations` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/animations) Updates `@angular/cdk` from 21.2.6 to 21.2.7 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/v21.2.6...v21.2.7) Updates `@angular/common` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/common) Updates `@angular/compiler` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/compiler) Updates `@angular/compiler-cli` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/compiler-cli) Updates `@angular/core` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/core) Updates `@angular/forms` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/forms) Updates `@angular/material` from 21.2.6 to 21.2.7 - [Release notes](https://github.com/angular/components/releases) - [Changelog](https://github.com/angular/components/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/components/compare/v21.2.6...v21.2.7) Updates `@angular/platform-browser` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/platform-browser) Updates `@angular/platform-browser-dynamic` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/platform-browser-dynamic) Updates `@angular/router` from 21.2.8 to 21.2.9 - [Release notes](https://github.com/angular/angular/releases) - [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md) - [Commits](https://github.com/angular/angular/commits/v21.2.9/packages/router) --- updated-dependencies: - dependency-name: "@ai-sdk/openai" dependency-version: 3.0.53 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/cli" dependency-version: 11.0.21 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/common" dependency-version: 11.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/core" dependency-version: 11.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/platform-express" dependency-version: 11.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/platform-socket.io" dependency-version: 11.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/schedule" dependency-version: 6.1.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@nestjs/websockets" dependency-version: 11.1.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: ai dependency-version: 6.0.168 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/animations" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/cdk" dependency-version: 21.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/common" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/compiler" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/compiler-cli" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/core" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/forms" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/material" dependency-version: 21.2.7 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/platform-browser" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/platform-browser-dynamic" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: "@angular/router" dependency-version: 21.2.9 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 551 +++++++++++++++---------------- teammapper-backend/package.json | 18 +- teammapper-frontend/package.json | 24 +- 3 files changed, 281 insertions(+), 312 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6ec0305..154eab18 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,50 +43,50 @@ importers: teammapper-backend: dependencies: '@ai-sdk/openai': - specifier: 3.0.52 - version: 3.0.52(zod@4.3.6) + specifier: 3.0.53 + version: 3.0.53(zod@4.3.6) '@ai-sdk/openai-compatible': specifier: 2.0.41 version: 2.0.41(zod@4.3.6) '@nestjs/cache-manager': specifier: ^3.1.0 - version: 3.1.0(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) + version: 3.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) '@nestjs/cli': - specifier: ^11.0.19 - version: 11.0.19(@types/node@25.5.0) + specifier: ^11.0.21 + version: 11.0.21(@types/node@25.5.0) '@nestjs/common': - specifier: ^11.1.18 - version: 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.19 + version: 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/config': specifier: 4.0.4 - version: 4.0.4(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + version: 4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': - specifier: ^11.1.18 - version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.19 + version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': - specifier: ^11.1.18 - version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + specifier: ^11.1.19 + version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/platform-socket.io': - specifier: ^11.1.18 - version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.18)(rxjs@7.8.2) + specifier: ^11.1.19 + version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) '@nestjs/schedule': - specifier: ^6.1.1 - version: 6.1.1(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + specifier: ^6.1.3 + version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/serve-static': specifier: ^5.0.5 - version: 5.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(express@5.2.1) + version: 5.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1) '@nestjs/typeorm': specifier: ^11.0.1 - version: 11.0.1(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))) + version: 11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))) '@nestjs/websockets': - specifier: ^11.1.18 - version: 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-socket.io@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.19 + version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/uuid': specifier: ^11.0.0 version: 11.0.0 ai: - specifier: 6.0.158 - version: 6.0.158(zod@4.3.6) + specifier: 6.0.168 + version: 6.0.168(zod@4.3.6) cache-manager: specifier: ^7.2.8 version: 7.2.8 @@ -168,7 +168,7 @@ importers: version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.1.17 - version: 11.1.17(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-express@11.1.18) + version: 11.1.17(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@10.1.0(jiti@2.6.1)) @@ -273,46 +273,46 @@ importers: dependencies: '@angular-devkit/build-angular': specifier: 21.2.7 - version: 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) + version: 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) '@angular/animations': - specifier: 21.2.8 - version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + specifier: 21.2.9 + version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@angular/cdk': - specifier: 21.2.6 - version: 21.2.6(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + specifier: 21.2.7 + version: 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/cli': specifier: 21.2.7 version: 21.2.7(@types/node@25.5.0)(chokidar@5.0.0) '@angular/common': - specifier: 21.2.8 - version: 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + specifier: 21.2.9 + version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) '@angular/compiler': - specifier: 21.2.8 - version: 21.2.8 + specifier: 21.2.9 + version: 21.2.9 '@angular/compiler-cli': - specifier: 21.2.8 - version: 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) + specifier: 21.2.9 + version: 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@angular/core': - specifier: 21.2.8 - version: 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + specifier: 21.2.9 + version: 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) '@angular/forms': - specifier: 21.2.8 - version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + specifier: 21.2.9 + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/material': - specifier: 21.2.6 - version: 21.2.6(0a54f93f8328dbd3883663025155f2ad) + specifier: 21.2.7 + version: 21.2.7(0d5a9f31d3db06fde122ff91a62de812) '@angular/platform-browser': - specifier: 21.2.8 - version: 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + specifier: 21.2.9 + version: 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@angular/platform-browser-dynamic': - specifier: 21.2.8 - version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + specifier: 21.2.9 + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) '@angular/router': - specifier: 21.2.8 - version: 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + specifier: 21.2.9 + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@fortawesome/angular-fontawesome': specifier: ^4.0.0 - version: 4.0.0(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@fortawesome/fontawesome-svg-core': specifier: ^7.2.0 version: 7.2.0 @@ -327,16 +327,16 @@ importers: version: 0.14.15 '@ngx-translate/core': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@ngx-translate/http-loader': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@teammapper/mermaid-mindmap-parser': specifier: workspace:^ version: link:packages/mermaid-mindmap-parser ai: - specifier: ^6.0.158 - version: 6.0.158(zod@4.3.6) + specifier: ^6.0.168 + version: 6.0.168(zod@4.3.6) angular2-hotkeys: specifier: ^16.0.1 version: 16.0.1 @@ -357,10 +357,10 @@ importers: version: 1.10.0 ngx-color-picker: specifier: ^20.1.1 - version: 20.1.1(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)) + version: 20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)) ngx-toastr: specifier: ^20.0.5 - version: 20.0.5(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + version: 20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) qr-code-styling: specifier: 1.9.2 version: 1.9.2 @@ -388,7 +388,7 @@ importers: devDependencies: '@angular-builders/jest': specifier: ^21.0.3 - version: 21.0.3(f9c43a5b0ae0e8f8b7af53557fe3cbed) + version: 21.0.3(07bd7b923bb6f14c0b013a3dc874ca68) '@angular-devkit/architect': specifier: 0.2102.5 version: 0.2102.5(chokidar@5.0.0) @@ -475,7 +475,7 @@ importers: version: 30.3.0 jest-preset-angular: specifier: ^16.1.1 - version: 16.1.1(2c44891c7f9de772a02b70e8fba8e9f9) + version: 16.1.1(28bd700369ae87f04923e58e037d4bde) minimist: specifier: ^1.2.8 version: 1.2.8 @@ -526,8 +526,8 @@ packages: '@aduh95/viz.js@3.4.0': resolution: {integrity: sha512-KI2nVf9JdwWCXqK6RVf+9/096G7VWN4Z84mnynlyZKao2xQENW8WNEjLmvdlxS5X8PNWXFC1zqwm7tveOXw/4A==} - '@ai-sdk/gateway@3.0.95': - resolution: {integrity: sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ==} + '@ai-sdk/gateway@3.0.104': + resolution: {integrity: sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -538,8 +538,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/openai@3.0.52': - resolution: {integrity: sha512-4Rr8NCGmfWTz6DCUvixn9UmyZcMatiHn0zWoMzI3JCUe9R1P/vsPOpCBALKoSzVYOjyJnhtnVIbfUKujcS39uw==} + '@ai-sdk/openai@3.0.53': + resolution: {integrity: sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -809,11 +809,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' - '@angular/animations@21.2.8': - resolution: {integrity: sha512-RIqfVmfretQ0x/mXgMXe7Bw0Tpe8+zBV/Mm2OaNVyrmNG+9gYItEn5t/ZnQGcPD5nMNqckgp3+4/ZMc/qkS5ww==} + '@angular/animations@21.2.9': + resolution: {integrity: sha512-wOWbrneivpTYx3xhiPygoNFNC8ZZ1shpgwBe1hYvfky1fkiz1c92XeHIW1V4rPqYw6d3U55aUX5InyTsVe2Xng==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 21.2.8 + '@angular/core': 21.2.9 '@angular/build@21.2.7': resolution: {integrity: sha512-FpSkFqpsJtdN1cROekVYkmeV1QepdP+/d7fyYQEuNmlOlyqXSDh9qJmy4iL9VNbAU0rk+vFCtYM86rO7Pt9cSw==} @@ -861,8 +861,8 @@ packages: vitest: optional: true - '@angular/cdk@21.2.6': - resolution: {integrity: sha512-1PBzFf+um/VZ1dFF6cT72Zsq+9C/ZWF9m5dP0uHJgo4psX3yMBoZlZu5YomBiAQ/ePSkqCuryv1vrelK+yd3Mw==} + '@angular/cdk@21.2.7': + resolution: {integrity: sha512-GHQZ+d5k3nY9JXPNEJpeuLd8FSy03hxXAYsq6IQI4AcTIQow3QZlHj6g3/sk2QlqnzCaEhfRmwx7AO5iXyzdZQ==} peerDependencies: '@angular/common': ^21.0.0 || ^22.0.0 '@angular/core': ^21.0.0 || ^22.0.0 @@ -874,33 +874,33 @@ packages: engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular/common@21.2.8': - resolution: {integrity: sha512-ZvgcxsLPkSG0B1jc2ZXshAWIFBoQ0U9uwIX/zG/RGcfMpoKyEDNAebli6FTIpxIlz/35rtBNV7EGPhinjPTJFQ==} + '@angular/common@21.2.9': + resolution: {integrity: sha512-7spQcF3hPN/fjTx6Pwa32KRRdO0NcixnRuPV4lo50ejtXesjiLVR+fkaX38sawAyGoq89IuuYvUDrbLwCMypmQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 21.2.8 + '@angular/core': 21.2.9 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@21.2.8': - resolution: {integrity: sha512-S0W+6QazCsn/4xWZu0V5VmU9zmKIlqFR2FJSsAQUPReVmpA40SuQSP6A/cyMVIMYaHvO/cAXSHJVgpxBzBSL/Q==} + '@angular/compiler-cli@21.2.9': + resolution: {integrity: sha512-hTTW/OiqTXrwTneS18CMp47OX0XSbLYl2rIomLS3nXVJniSETH6S/k+LqQtGWWgLbzsd3PzUOOckHnvzpTBTsA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 21.2.8 + '@angular/compiler': 21.2.9 typescript: '>=5.9 <6.1' peerDependenciesMeta: typescript: optional: true - '@angular/compiler@21.2.8': - resolution: {integrity: sha512-Il9KlT6qX8rWmun5jY6wMLx56bCQZpOVIFEyHM4ai2wmxvbqyxgRFKDs4iMRNn1h04Tgupl6cKSqP9lecIvH6w==} + '@angular/compiler@21.2.9': + resolution: {integrity: sha512-clsK1EsSPtAuqlRl4CciA/gsvsW7xe0eWcvHxtrMW6DYaUJ6X4AAuDxEEJ5cf/3Mpw4s8KssjIUPPtbrUIGLSQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/core@21.2.8': - resolution: {integrity: sha512-hI7n4t8qgFJaVV55LIaNuzcdP+/IeuqQRu3huSLo47Gf6uZAD0Acj4Ye9SC8YNmhUu5/RiImngm9NOlcI2oCJA==} + '@angular/core@21.2.9': + resolution: {integrity: sha512-uZLq2aedJ+0uEZxyf6a1Nc7y1aZ7akAW7K1Kon8JUDZOvI2IDbk0i00MzkELt8q9uSmSSqg9zNKuhjspFf0Pyw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/compiler': 21.2.8 + '@angular/compiler': 21.2.9 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: @@ -909,56 +909,56 @@ packages: zone.js: optional: true - '@angular/forms@21.2.8': - resolution: {integrity: sha512-tyQAHjfMHcqETRkKQaZHjYqIK9W8uRenPpY2DF/Jl+S7CwcaX4T8t8TKgzvTynNzQW9QGiLg0pqVosVMKzBXJg==} + '@angular/forms@21.2.9': + resolution: {integrity: sha512-qXLnzmsJoHMgV/gDU7AZgsKBhUH7k6im6V9YuY5UpHHl+nGKCWxtePAZRB0OH2AsqzLwER3Fv2S6+mtmb7651w==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.2.8 - '@angular/core': 21.2.8 - '@angular/platform-browser': 21.2.8 + '@angular/common': 21.2.9 + '@angular/core': 21.2.9 + '@angular/platform-browser': 21.2.9 rxjs: ^6.5.3 || ^7.4.0 '@angular/language-service@21.2.6': resolution: {integrity: sha512-ui2Zf/h736Kf/jwyXHN2OBQC9fEzGUCz5fJr72sEe4nqa6aTiCL0FfkTarHDLKEYPNr8M+ZX/icgo3j9yztJhQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/material@21.2.6': - resolution: {integrity: sha512-V4hblb5ekgXb5x+UXKRs2yiB0hZUkUJbYwGseMglkCeWQlLM4u6amlsUzP4uOwIWFOkM/ZYl9qz4YGZnvMAyjw==} + '@angular/material@21.2.7': + resolution: {integrity: sha512-YRUE33ZbKwtYjp+b3WrChO5bjKnzg3CjQaJPPIW3OGY9kCxxKp78ub8veh2tXodChvDaCYg5jLxjBJopMogxTw==} peerDependencies: - '@angular/cdk': 21.2.6 + '@angular/cdk': 21.2.7 '@angular/common': ^21.0.0 || ^22.0.0 '@angular/core': ^21.0.0 || ^22.0.0 '@angular/forms': ^21.0.0 || ^22.0.0 '@angular/platform-browser': ^21.0.0 || ^22.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/platform-browser-dynamic@21.2.8': - resolution: {integrity: sha512-9XeplSHsKnLDm14dvwXG00Ox6WbDrhf7ub7MxxcJ6gCgRm/yqJ3Vrz4a+NBpYnelapqiCCGEdHeyx2xt8vG1qA==} + '@angular/platform-browser-dynamic@21.2.9': + resolution: {integrity: sha512-Z+2vefW4GUSuTC4BOKNiyftqecLSjxOKwe1ZNljBsjesLzywIXi+v+tyEm8ODHHlf7bz/0HwXvc9OYZmfjt95A==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.2.8 - '@angular/compiler': 21.2.8 - '@angular/core': 21.2.8 - '@angular/platform-browser': 21.2.8 + '@angular/common': 21.2.9 + '@angular/compiler': 21.2.9 + '@angular/core': 21.2.9 + '@angular/platform-browser': 21.2.9 - '@angular/platform-browser@21.2.8': - resolution: {integrity: sha512-4fwmGf7GCuIsjFqx1gqqWC92YjlN9SmGJO17TPPsOm5zUOnDx+h3Bj9XjdXxlcBtugTb2xHk6Auqyv3lzWGlkw==} + '@angular/platform-browser@21.2.9': + resolution: {integrity: sha512-MjEtFvoFtsjsAeu2yzauqGgwwEHV4ml25c9vGFmw4OmSoNme4yp41f2DegwOkn1TTHL3OF3GE65ng2U2feJU4Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 21.2.8 - '@angular/common': 21.2.8 - '@angular/core': 21.2.8 + '@angular/animations': 21.2.9 + '@angular/common': 21.2.9 + '@angular/core': 21.2.9 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@21.2.8': - resolution: {integrity: sha512-KSlUbFHHKY84G6iKlB2FDMmh+lLmGjmpyT1p/kx8qZm1BuxJGOOU+oNgkCfaPJT1R2/muDXuxQ51uc/la6y28g==} + '@angular/router@21.2.9': + resolution: {integrity: sha512-ExqOEO6IUuNaI75ZcjAbOuzJKpvVze6hRdETyVf7Sny07+XSKv9t8DK9tBHmR7+67wz+zPIUgCXxsQXi8jJu0w==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.2.8 - '@angular/core': 21.2.8 - '@angular/platform-browser': 21.2.8 + '@angular/common': 21.2.9 + '@angular/core': 21.2.9 + '@angular/platform-browser': 21.2.9 rxjs: ^6.5.3 || ^7.4.0 '@arr/every@1.0.1': @@ -2609,49 +2609,42 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -2705,8 +2698,8 @@ packages: keyv: '>=5' rxjs: ^7.8.1 - '@nestjs/cli@11.0.19': - resolution: {integrity: sha512-9htODqTVVNH4lJqyeIotsAgfeaYngDi020cVCd6JhJRKuOT83c/t4JDSky6+xr0lhHyNTNMgZmulxqcMNZFfrw==} + '@nestjs/cli@11.0.21': + resolution: {integrity: sha512-F8mV0Sj/zVEouzR3NxBuJy08YHTUOmC5Xdcx3qIIaJWzrm8Vw86CHkhkaPBJ5ewRMHPDCShPmhsfwhpCcjts3A==} engines: {node: '>= 20.11'} hasBin: true peerDependencies: @@ -2718,8 +2711,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.18': - resolution: {integrity: sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg==} + '@nestjs/common@11.1.19': + resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -2737,8 +2730,8 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - '@nestjs/core@11.1.18': - resolution: {integrity: sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==} + '@nestjs/core@11.1.19': + resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -2755,21 +2748,21 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@11.1.18': - resolution: {integrity: sha512-s6GdHMTa3qx0fJewR74Xa30ysPHfBEqxIwZ7BGSTLoAEQ1vTP24urNl+b6+s49NFLEIOyeNho5fN/9/I17QlOw==} + '@nestjs/platform-express@11.1.19': + resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.18': - resolution: {integrity: sha512-DiFRpMIdFaHqZQFwqLqGHMdNurrKVkRkMHxIrecjooPHJNNIMgrbpYZ+oJW8hpwifUyZUL4r4uXXRy4+yFEMjA==} + '@nestjs/platform-socket.io@11.1.19': + resolution: {integrity: sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 rxjs: ^7.1.0 - '@nestjs/schedule@6.1.1': - resolution: {integrity: sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==} + '@nestjs/schedule@6.1.3': + resolution: {integrity: sha512-RflMFOpR16Dwd1jAUbeB4mfGTCh65fvEdL4mSjQPJChpkRGRjIXjb+6YQcK2faQrVT60c9DmLmoVR7/ONCtuYQ==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 @@ -2817,8 +2810,8 @@ packages: rxjs: ^7.2.0 typeorm: ^0.3.0 || ^1.0.0-dev - '@nestjs/websockets@11.1.18': - resolution: {integrity: sha512-HLO/QGlJoJaMMDphgjbcIwW7/8EA8nnFQjf+qPvA+wWLLfPKoiFPUezc5m9YgN2A7jmzwo6YmEAXeHyqO9tvTw==} + '@nestjs/websockets@11.1.19': + resolution: {integrity: sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -2925,7 +2918,6 @@ packages: resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] - libc: [glibc] '@nx/nx-win32-x64-msvc@22.6.5': resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} @@ -2974,42 +2966,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -3187,70 +3173,60 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} @@ -3338,79 +3314,66 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -3988,49 +3951,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4052,8 +4007,8 @@ packages: cpu: [x64] os: [win32] - '@vercel/oidc@3.1.0': - resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + '@vercel/oidc@3.2.0': + resolution: {integrity: sha512-UycprH3T6n3jH0k44NHMa7pnFHGu/N05MjojYr+Mc6I7obkoLIJujSWwin1pCvdy/eOxrI/l3uDLQsmcrOb4ug==} engines: {node: '>= 20'} '@vitejs/plugin-basic-ssl@2.1.4': @@ -4163,8 +4118,8 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ai@6.0.158: - resolution: {integrity: sha512-gLTp1UXFtMqKUi3XHs33K7UFglbvojkxF/aq337TxnLGOhHIW9+GyP2jwW4hYX87f1es+wId3VQoPRRu9zEStQ==} + ai@6.0.168: + resolution: {integrity: sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -5288,6 +5243,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -5577,6 +5536,10 @@ packages: resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} engines: {node: '>=18.0.0'} + eventsource-parser@3.0.8: + resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} + engines: {node: '>=18.0.0'} + eventsource@3.0.7: resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} engines: {node: '>=18.0.0'} @@ -6734,28 +6697,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -6870,6 +6829,10 @@ packages: resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} engines: {node: 20 || >=22} + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -7402,8 +7365,8 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -9231,11 +9194,11 @@ snapshots: '@aduh95/viz.js@3.4.0': {} - '@ai-sdk/gateway@3.0.95(zod@4.3.6)': + '@ai-sdk/gateway@3.0.104(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) - '@vercel/oidc': 3.1.0 + '@vercel/oidc': 3.2.0 zod: 4.3.6 '@ai-sdk/openai-compatible@2.0.41(zod@4.3.6)': @@ -9244,7 +9207,7 @@ snapshots: '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/openai@3.0.52(zod@4.3.6)': + '@ai-sdk/openai@3.0.53(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) @@ -9254,7 +9217,7 @@ snapshots: dependencies: '@ai-sdk/provider': 3.0.8 '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.8 zod: 4.3.6 '@ai-sdk/provider@3.0.8': @@ -9362,17 +9325,17 @@ snapshots: - chokidar - typescript - '@angular-builders/jest@21.0.3(f9c43a5b0ae0e8f8b7af53557fe3cbed)': + '@angular-builders/jest@21.0.3(07bd7b923bb6f14c0b013a3dc874ca68)': dependencies: '@angular-builders/common': 5.0.3(@types/node@25.5.0)(chokidar@5.0.0)(typescript@5.9.3) '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) - '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) + '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) '@angular-devkit/core': 21.2.5(chokidar@5.0.0) - '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser-dynamic': 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-preset-angular: 16.1.1(2c44891c7f9de772a02b70e8fba8e9f9) + jest-preset-angular: 16.1.1(28bd700369ae87f04923e58e037d4bde) lodash: 4.18.1 transitivePeerDependencies: - '@angular/platform-browser' @@ -9402,14 +9365,14 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3)': + '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) '@angular-devkit/build-webpack': 0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3)) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) - '@angular/build': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) - '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) + '@angular/build': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) + '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@babel/core': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-annotate-as-pure': 7.27.3 @@ -9420,7 +9383,7 @@ snapshots: '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/runtime': 7.28.6 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) + '@ngtools/webpack': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) ansi-colors: 4.1.3 autoprefixer: 10.4.27(postcss@8.5.6) babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)) @@ -9461,8 +9424,8 @@ snapshots: webpack-merge: 6.0.1 webpack-subresource-integrity: 5.1.0(webpack@5.105.2(esbuild@0.27.3)) optionalDependencies: - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) esbuild: 0.27.3 jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) jest-environment-jsdom: 30.3.0 @@ -9682,17 +9645,17 @@ snapshots: eslint: 10.1.0(jiti@2.6.1) typescript: 5.9.3 - '@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))': + '@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': dependencies: - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 - '@angular/build@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': + '@angular/build@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) - '@angular/compiler': 21.2.8 - '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) + '@angular/compiler': 21.2.9 + '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 @@ -9721,8 +9684,8 @@ snapshots: vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) watchpack: 2.5.1 optionalDependencies: - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) less: 4.4.2 lmdb: 3.5.1 postcss: 8.5.6 @@ -9741,12 +9704,12 @@ snapshots: - tsx - yaml - '@angular/cdk@21.2.6(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/cdk@21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) - parse5: 8.0.0 + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + parse5: 8.0.1 rxjs: 7.8.2 tslib: 2.8.1 @@ -9776,15 +9739,15 @@ snapshots: - chokidar - supports-color - '@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2)': + '@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2)': dependencies: - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3)': + '@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3)': dependencies: - '@angular/compiler': 21.2.8 + '@angular/compiler': 21.2.9 '@babel/core': 7.29.0 '@jridgewell/sourcemap-codec': 1.5.5 chokidar: 5.0.0 @@ -9798,60 +9761,60 @@ snapshots: transitivePeerDependencies: - supports-color - '@angular/compiler@21.2.8': + '@angular/compiler@21.2.9': dependencies: tslib: 2.8.1 - '@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)': + '@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 21.2.8 + '@angular/compiler': 21.2.9 zone.js: 0.16.1 - '@angular/forms@21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 '@angular/language-service@21.2.6': {} - '@angular/material@21.2.6(0a54f93f8328dbd3883663025155f2ad)': + '@angular/material@21.2.7(0d5a9f31d3db06fde122ff91a62de812)': dependencies: - '@angular/cdk': 21.2.6(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/forms': 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/cdk': 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser-dynamic@21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))': + '@angular/platform-browser-dynamic@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/compiler': 21.2.8 - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/compiler': 21.2.9 + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) tslib: 2.8.1 - '@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))': + '@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 optionalDependencies: - '@angular/animations': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/animations': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) - '@angular/router@21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) rxjs: 7.8.2 tslib: 2.8.1 @@ -11528,9 +11491,9 @@ snapshots: '@eslint/core': 1.1.1 levn: 0.4.1 - '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))': + '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': dependencies: - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) '@fortawesome/fontawesome-svg-core': 7.2.0 tslib: 2.8.1 @@ -12247,15 +12210,15 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': + '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cache-manager: 7.2.8 keyv: 5.6.0 rxjs: 7.8.2 - '@nestjs/cli@11.0.19(@types/node@25.5.0)': + '@nestjs/cli@11.0.21(@types/node@25.5.0)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) @@ -12281,7 +12244,7 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -12296,17 +12259,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/config@4.0.4(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) dotenv: 17.4.1 dotenv-expand: 12.0.3 lodash: 4.18.1 rxjs: 7.8.2 - '@nestjs/core@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -12316,13 +12279,13 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) - '@nestjs/websockets': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-socket.io@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)': + '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 multer: 2.1.1 @@ -12331,10 +12294,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.18)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-socket.io@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -12343,10 +12306,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.1(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cron: 4.4.0 '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': @@ -12360,58 +12323,58 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(express@5.2.1)': + '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) path-to-regexp: 8.4.2 optionalDependencies: express: 5.2.1 - '@nestjs/testing@11.1.17(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-express@11.1.18)': + '@nestjs/testing@11.1.17(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18) + '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 typeorm: 0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - '@nestjs/websockets@11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.18)(@nestjs/platform-socket.io@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.18)(@nestjs/websockets@11.1.18)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.18(@nestjs/common@11.1.18(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.18)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) - '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3))': + '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3))': dependencies: - '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) + '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) typescript: 5.9.3 webpack: 5.105.2(esbuild@0.27.3) - '@ngx-translate/core@17.0.0(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))': + '@ngx-translate/core@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 - '@ngx-translate/http-loader@17.0.0(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))': + '@ngx-translate/http-loader@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 '@noble/hashes@1.4.0': {} @@ -12435,7 +12398,7 @@ snapshots: agent-base: 7.1.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - lru-cache: 11.3.3 + lru-cache: 11.3.5 socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color @@ -13589,7 +13552,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/oidc@3.1.0': {} + '@vercel/oidc@3.2.0': {} '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))': dependencies: @@ -13714,9 +13677,9 @@ snapshots: agent-base@7.1.4: {} - ai@6.0.158(zod@4.3.6): + ai@6.0.168(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.95(zod@4.3.6) + '@ai-sdk/gateway': 3.0.104(zod@4.3.6) '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.23(zod@4.3.6) '@opentelemetry/api': 1.9.0 @@ -14982,6 +14945,8 @@ snapshots: entities@7.0.1: {} + entities@8.0.0: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -15373,6 +15338,8 @@ snapshots: eventsource-parser@3.0.6: {} + eventsource-parser@3.0.8: {} + eventsource@3.0.7: dependencies: eventsource-parser: 3.0.6 @@ -16442,12 +16409,12 @@ snapshots: optionalDependencies: jest-resolve: 30.3.0 - jest-preset-angular@16.1.1(2c44891c7f9de772a02b70e8fba8e9f9): + jest-preset-angular@16.1.1(28bd700369ae87f04923e58e037d4bde): dependencies: - '@angular/compiler-cli': 21.2.8(@angular/compiler@21.2.8)(typescript@5.9.3) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)) - '@angular/platform-browser-dynamic': 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.8)(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))) + '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) '@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0) bs-logger: 0.2.6 esbuild-wasm: 0.27.4 @@ -16970,6 +16937,8 @@ snapshots: lru-cache@11.3.3: {} + lru-cache@11.3.5: {} + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -17226,17 +17195,17 @@ snapshots: neotraverse@0.6.18: {} - ngx-color-picker@20.1.1(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)): + ngx-color-picker@20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)): dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/forms': 21.2.8(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.8(@angular/animations@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) tslib: 2.8.1 - ngx-toastr@20.0.5(@angular/common@21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2): + ngx-toastr@20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2): dependencies: - '@angular/common': 21.2.8(@angular/core@21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.8(@angular/compiler@21.2.8)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) rxjs: 7.8.2 tslib: 2.8.1 @@ -17564,7 +17533,7 @@ snapshots: parse5-html-rewriting-stream@8.0.0: dependencies: entities: 6.0.1 - parse5: 8.0.0 + parse5: 8.0.1 parse5-sax-parser: 8.0.0 parse5-htmlparser2-tree-adapter@7.1.0: @@ -17578,15 +17547,15 @@ snapshots: parse5-sax-parser@8.0.0: dependencies: - parse5: 8.0.0 + parse5: 8.0.1 parse5@7.3.0: dependencies: entities: 6.0.1 - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.0.0 parseurl@1.3.3: {} @@ -17607,7 +17576,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.3 + lru-cache: 11.3.5 minipass: 7.1.3 path-to-regexp@0.1.13: {} diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index 53c2b155..3965fd8b 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -33,21 +33,21 @@ "prod:typeorm:migrate": "typeorm migration:run --dataSource dist/data-source.js" }, "dependencies": { - "@ai-sdk/openai": "3.0.52", + "@ai-sdk/openai": "3.0.53", "@ai-sdk/openai-compatible": "2.0.41", "@nestjs/cache-manager": "^3.1.0", - "@nestjs/cli": "^11.0.19", - "@nestjs/common": "^11.1.18", + "@nestjs/cli": "^11.0.21", + "@nestjs/common": "^11.1.19", "@nestjs/config": "4.0.4", - "@nestjs/core": "^11.1.18", - "@nestjs/platform-express": "^11.1.18", - "@nestjs/platform-socket.io": "^11.1.18", - "@nestjs/schedule": "^6.1.1", + "@nestjs/core": "^11.1.19", + "@nestjs/platform-express": "^11.1.19", + "@nestjs/platform-socket.io": "^11.1.19", + "@nestjs/schedule": "^6.1.3", "@nestjs/serve-static": "^5.0.5", "@nestjs/typeorm": "^11.0.1", - "@nestjs/websockets": "^11.1.18", + "@nestjs/websockets": "^11.1.19", "@types/uuid": "^11.0.0", - "ai": "6.0.158", + "ai": "6.0.168", "cache-manager": "^7.2.8", "class-validator": "^0.15.1", "cookie-parser": "^1.4.7", diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index 1c4daef1..204d2383 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -42,18 +42,18 @@ }, "dependencies": { "@angular-devkit/build-angular": "21.2.7", - "@angular/animations": "21.2.8", - "@angular/cdk": "21.2.6", + "@angular/animations": "21.2.9", + "@angular/cdk": "21.2.7", "@angular/cli": "21.2.7", - "@angular/common": "21.2.8", - "@angular/compiler": "21.2.8", - "@angular/compiler-cli": "21.2.8", - "@angular/core": "21.2.8", - "@angular/forms": "21.2.8", - "@angular/material": "21.2.6", - "@angular/platform-browser": "21.2.8", - "@angular/platform-browser-dynamic": "21.2.8", - "@angular/router": "21.2.8", + "@angular/common": "21.2.9", + "@angular/compiler": "21.2.9", + "@angular/compiler-cli": "21.2.9", + "@angular/core": "21.2.9", + "@angular/forms": "21.2.9", + "@angular/material": "21.2.7", + "@angular/platform-browser": "21.2.9", + "@angular/platform-browser-dynamic": "21.2.9", + "@angular/router": "21.2.9", "@fortawesome/angular-fontawesome": "^4.0.0", "@fortawesome/fontawesome-svg-core": "^7.2.0", "@fortawesome/free-brands-svg-icons": "^7.2.0", @@ -62,7 +62,7 @@ "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", "@teammapper/mermaid-mindmap-parser": "workspace:^", - "ai": "^6.0.158", + "ai": "^6.0.168", "angular2-hotkeys": "^16.0.1", "d3": "7.9.0", "deep-object-diff": "^1.1.9", From ed69c683bdc4467b8df2b93af89730b6b934d7a1 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 10 May 2026 12:33:37 +0200 Subject: [PATCH 02/14] add table to persist token usage (#1280) --- pnpm-lock.yaml | 137 ++++--- pnpm-workspace.yaml | 11 +- teammapper-backend/README.md | 8 +- teammapper-backend/src/config.service.ts | 4 + .../map/entities/llmUsageCounter.entity.ts | 17 + teammapper-backend/src/map/map.module.ts | 11 +- .../src/map/services/ai.service.spec.ts | 371 ++++++++++-------- .../src/map/services/ai.service.ts | 203 +++++++--- .../llm-usage-counter.service.spec.ts | 77 ++++ .../map/services/llm-usage-counter.service.ts | 84 ++++ .../1778265117672-AddLlmUsageCounter.ts | 20 + .../test/llm-usage-counter.e2e-spec.ts | 84 ++++ 12 files changed, 734 insertions(+), 293 deletions(-) create mode 100644 teammapper-backend/src/map/entities/llmUsageCounter.entity.ts create mode 100644 teammapper-backend/src/map/services/llm-usage-counter.service.spec.ts create mode 100644 teammapper-backend/src/map/services/llm-usage-counter.service.ts create mode 100644 teammapper-backend/src/migrations/1778265117672-AddLlmUsageCounter.ts create mode 100644 teammapper-backend/test/llm-usage-counter.e2e-spec.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 154eab18..745fedf0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,37 +50,37 @@ importers: version: 2.0.41(zod@4.3.6) '@nestjs/cache-manager': specifier: ^3.1.0 - version: 3.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) + version: 3.1.0(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) '@nestjs/cli': specifier: ^11.0.21 version: 11.0.21(@types/node@25.5.0) '@nestjs/common': specifier: ^11.1.19 - version: 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/config': specifier: 4.0.4 - version: 4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + version: 4.0.4(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/platform-socket.io': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.1.3 - version: 6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 6.1.3(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) '@nestjs/serve-static': specifier: ^5.0.5 - version: 5.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1) + version: 5.0.5(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1) '@nestjs/typeorm': specifier: ^11.0.1 - version: 11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))) + version: 11.0.1(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))) '@nestjs/websockets': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/uuid': specifier: ^11.0.0 version: 11.0.0 @@ -168,7 +168,7 @@ importers: version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.1.17 - version: 11.1.17(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.17(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@10.1.0(jiti@2.6.1)) @@ -300,7 +300,7 @@ importers: version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/material': specifier: 21.2.7 - version: 21.2.7(0d5a9f31d3db06fde122ff91a62de812) + version: 21.2.7(23fcsq5atbc2khgeermlooaagm) '@angular/platform-browser': specifier: 21.2.9 version: 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) @@ -385,10 +385,23 @@ importers: zone.js: specifier: ^0.16.1 version: 0.16.1 + optionalDependencies: + '@nx/nx-darwin-arm64': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-darwin-x64': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-linux-x64-gnu': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-win32-x64-msvc': + specifier: 22.6.5 + version: 22.6.5 devDependencies: '@angular-builders/jest': specifier: ^21.0.3 - version: 21.0.3(07bd7b923bb6f14c0b013a3dc874ca68) + version: 21.0.3(k2djlcvkqxy6k6ldfsfrcs6zlu) '@angular-devkit/architect': specifier: 0.2102.5 version: 0.2102.5(chokidar@5.0.0) @@ -475,7 +488,7 @@ importers: version: 30.3.0 jest-preset-angular: specifier: ^16.1.1 - version: 16.1.1(28bd700369ae87f04923e58e037d4bde) + version: 16.1.1(lhdkixmzgqfmespdgcicjoohiq) minimist: specifier: ^1.2.8 version: 1.2.8 @@ -491,19 +504,6 @@ importers: typescript-eslint: specifier: ^8.57.2 version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - optionalDependencies: - '@nx/nx-darwin-arm64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-darwin-x64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-linux-x64-gnu': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-win32-x64-msvc': - specifier: 22.6.5 - version: 22.6.5 teammapper-frontend/packages/mermaid-mindmap-parser: dependencies: @@ -3911,6 +3911,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -4586,9 +4587,6 @@ packages: resolution: {integrity: sha512-bBRQcCIHzI1IVH59fR0bwGrFmi3Btb/JNwM/n401i1DnYgWndpsUBiQRAddLflkZage20A2d25OAWZZk0vBRlA==} engines: {node: '>= 0.3.0'} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - class-validator@0.15.1: resolution: {integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==} @@ -8756,6 +8754,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -9325,7 +9324,7 @@ snapshots: - chokidar - typescript - '@angular-builders/jest@21.0.3(07bd7b923bb6f14c0b013a3dc874ca68)': + '@angular-builders/jest@21.0.3(k2djlcvkqxy6k6ldfsfrcs6zlu)': dependencies: '@angular-builders/common': 5.0.3(@types/node@25.5.0)(chokidar@5.0.0)(typescript@5.9.3) '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) @@ -9335,7 +9334,7 @@ snapshots: '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-preset-angular: 16.1.1(28bd700369ae87f04923e58e037d4bde) + jest-preset-angular: 16.1.1(lhdkixmzgqfmespdgcicjoohiq) lodash: 4.18.1 transitivePeerDependencies: - '@angular/platform-browser' @@ -9784,7 +9783,7 @@ snapshots: '@angular/language-service@21.2.6': {} - '@angular/material@21.2.7(0d5a9f31d3db06fde122ff91a62de812)': + '@angular/material@21.2.7(23fcsq5atbc2khgeermlooaagm)': dependencies: '@angular/cdk': 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) @@ -12210,10 +12209,10 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': + '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cache-manager: 7.2.8 keyv: 5.6.0 rxjs: 7.8.2 @@ -12244,7 +12243,7 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -12254,22 +12253,21 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - class-transformer: 0.5.1 class-validator: 0.15.1 transitivePeerDependencies: - supports-color - '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) dotenv: 17.4.1 dotenv-expand: 12.0.3 lodash: 4.18.1 rxjs: 7.8.2 - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -12279,13 +12277,13 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 multer: 2.1.1 @@ -12294,10 +12292,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -12306,10 +12304,10 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) cron: 4.4.0 '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': @@ -12323,41 +12321,41 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1)': + '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) path-to-regexp: 8.4.2 optionalDependencies: express: 5.2.1 - '@nestjs/testing@11.1.17(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': + '@nestjs/testing@11.1.17(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 typeorm: 0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3))': dependencies: @@ -14286,9 +14284,6 @@ snapshots: dependencies: jsonlint: 1.6.0 - class-transformer@0.5.1: - optional: true - class-validator@0.15.1: dependencies: '@types/validator': 13.15.10 @@ -16409,7 +16404,7 @@ snapshots: optionalDependencies: jest-resolve: 30.3.0 - jest-preset-angular@16.1.1(28bd700369ae87f04923e58e037d4bde): + jest-preset-angular@16.1.1(lhdkixmzgqfmespdgcicjoohiq): dependencies: '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 45d63de5..7f607aad 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,13 @@ packages: - 'teammapper-backend' - 'teammapper-frontend' - - 'teammapper-frontend/packages/*' \ No newline at end of file + - 'teammapper-frontend/packages/*' +allowBuilds: + '@compodoc/compodoc': false + '@nestjs/core': false + '@parcel/watcher': true + core-js: false + esbuild: true + lmdb: false + msgpackr-extract: false + unrs-resolver: false diff --git a/teammapper-backend/README.md b/teammapper-backend/README.md index 7e81539a..05bc1d48 100644 --- a/teammapper-backend/README.md +++ b/teammapper-backend/README.md @@ -100,9 +100,11 @@ Copy `.env.default` to `.env` and configure the variables below. | `AI_LLM_TOKEN` | LLM API token | - | | `AI_LLM_PROVIDER` | LLM provider (`openai` or `openai-compatible`) | `openai` | | `AI_LLM_MODEL` | LLM model name | - | -| `AI_LLM_TPM` | Tokens per minute limit | - | -| `AI_LLM_TPD` | Tokens per day limit | - | -| `AI_LLM_RPM` | Requests per minute limit | - | +| `AI_LLM_TPM` | Tokens per minute limit (per-process, soft) | - | +| `AI_LLM_TPD` | Tokens per day limit (DB-backed, hard ceiling across restarts/instances) | - | +| `AI_LLM_RPM` | Requests per minute limit (per-process) | - | +| `AI_LLM_MAX_OUTPUT_TOKENS` | Per-call cap on generated tokens | `1024` | +| `AI_LLM_TIMEOUT_MS` | Per-call abort timeout in ms | `30000` | > **Migration note:** The previous `stackit` provider value for `AI_LLM_PROVIDER` has been replaced by `openai-compatible`. If you were using `AI_LLM_PROVIDER=stackit`, change it to `AI_LLM_PROVIDER=openai-compatible`. diff --git a/teammapper-backend/src/config.service.ts b/teammapper-backend/src/config.service.ts index 654e9400..85b3cd97 100644 --- a/teammapper-backend/src/config.service.ts +++ b/teammapper-backend/src/config.service.ts @@ -16,6 +16,8 @@ export interface LLMProps { tpm?: string tpd?: string rpm?: string + maxOutputTokens?: string + timeoutMs?: string } class ConfigService { @@ -121,6 +123,8 @@ class ConfigService { tpm: this.getValue('AI_LLM_TPM', false), tpd: this.getValue('AI_LLM_TPD', false), rpm: this.getValue('AI_LLM_RPM', false), + maxOutputTokens: this.getValue('AI_LLM_MAX_OUTPUT_TOKENS', false), + timeoutMs: this.getValue('AI_LLM_TIMEOUT_MS', false), } } diff --git a/teammapper-backend/src/map/entities/llmUsageCounter.entity.ts b/teammapper-backend/src/map/entities/llmUsageCounter.entity.ts new file mode 100644 index 00000000..4c924206 --- /dev/null +++ b/teammapper-backend/src/map/entities/llmUsageCounter.entity.ts @@ -0,0 +1,17 @@ +import { Entity, Column, PrimaryColumn } from 'typeorm' + +/** + * Anonymized aggregate counter for LLM usage. + * Used to enforce daily token caps across restarts and multi-instance deployments. + */ +@Entity('llm_usage_counter') +export class LlmUsageCounter { + @PrimaryColumn({ type: 'date' }) + dateUsage: string + + @Column({ type: 'bigint', default: '0' }) + tokensUsed: string + + @Column({ type: 'bigint', default: '0' }) + requestsCount: string +} diff --git a/teammapper-backend/src/map/map.module.ts b/teammapper-backend/src/map/map.module.ts index d8c41c17..b5e9dd7f 100644 --- a/teammapper-backend/src/map/map.module.ts +++ b/teammapper-backend/src/map/map.module.ts @@ -6,6 +6,7 @@ import MapsController from './controllers/maps.controller' import { MapsGateway } from './controllers/maps.gateway' import { MmpMap } from './entities/mmpMap.entity' import { MmpNode } from './entities/mmpNode.entity' +import { LlmUsageCounter } from './entities/llmUsageCounter.entity' import { MapsService } from './services/maps.service' import { YjsDocManagerService } from './services/yjs-doc-manager.service' import { YjsPersistenceService } from './services/yjs-persistence.service' @@ -14,13 +15,19 @@ import { WsConnectionLimiterService } from './services/ws-connection-limiter.ser import { TasksService } from './services/tasks.service' import MermaidController from './controllers/mermaid.controller' import { AiService } from './services/ai.service' +import { LlmUsageCounterService } from './services/llm-usage-counter.service' import cookieParser from 'cookie-parser' import { PersonIdMiddleware } from '../auth/person-id.middleware' import configService from '../config.service' // When Yjs is enabled, the Yjs providers replace the Socket.io MapsGateway. // Both cannot bind to the same HTTP upgrade path simultaneously. -const baseProviders: Provider[] = [MapsService, TasksService, AiService] +const baseProviders: Provider[] = [ + MapsService, + TasksService, + AiService, + LlmUsageCounterService, +] const yjsProviders: Provider[] = [ YjsDocManagerService, @@ -35,7 +42,7 @@ const mapProviders: Provider[] = configService.isYjsEnabled() @Module({ imports: [ - TypeOrmModule.forFeature([MmpMap, MmpNode]), + TypeOrmModule.forFeature([MmpMap, MmpNode, LlmUsageCounter]), CacheModule.register(), ScheduleModule.forRoot(), ], diff --git a/teammapper-backend/src/map/services/ai.service.spec.ts b/teammapper-backend/src/map/services/ai.service.spec.ts index 20fdf3b6..38856581 100644 --- a/teammapper-backend/src/map/services/ai.service.spec.ts +++ b/teammapper-backend/src/map/services/ai.service.spec.ts @@ -1,45 +1,70 @@ import { jest } from '@jest/globals' -import { AiService } from './ai.service' +import { AiService, SYSTEM_PROMPT_TOKEN_OVERHEAD } from './ai.service' +import { LlmUsageCounterService } from './llm-usage-counter.service' import { RateLimitExceededException } from '../controllers/rate-limit.exception' import { generateText } from 'ai' import * as aiProvider from '../utils/aiProvider' import configService from '../../config.service' import type { LLMProps } from '../../config.service' -// Define proper mock types based on actual function signatures type GenerateTextMock = jest.MockedFunction type CreateProviderMock = jest.MockedFunction type GetLLMConfigMock = jest.MockedFunction -// Define the actual return type we need type MockGenerateTextReturn = Awaited> jest.mock('ai') jest.mock('../utils/aiProvider') jest.mock('../../config.service') +interface FakeUsageState { + tokensUsed: number + requestsCount: number +} + +const buildUsageCounterMock = (state: FakeUsageState) => + ({ + reserve: jest.fn( + async (_dateUsage: string, tokens: number, cap?: number) => { + const proposed = state.tokensUsed + tokens + if (cap !== undefined && proposed > cap) return null + state.tokensUsed = proposed + state.requestsCount += 1 + return { + tokensUsed: state.tokensUsed, + requestsCount: state.requestsCount, + } + } + ), + adjustTokens: jest.fn(async (_dateUsage: string, delta: number) => { + state.tokensUsed = Math.max(0, state.tokensUsed + delta) + }), + release: jest.fn(async (_dateUsage: string, tokens: number) => { + state.tokensUsed = Math.max(0, state.tokensUsed - tokens) + state.requestsCount = Math.max(0, state.requestsCount - 1) + }), + }) as unknown as LlmUsageCounterService + describe('AiService', () => { let aiService: AiService let generateTextMock: GenerateTextMock let createProviderMock: CreateProviderMock let getLLMConfigMock: GetLLMConfigMock + let usageState: FakeUsageState + let usageCounter: LlmUsageCounterService beforeAll(async () => { - // Calling advanceTimers here is very important, as otherwise async ops like await will hang indefinitely - // Ref: https://jestjs.io/docs/jest-object#jestusefaketimersfaketimersconfig jest.useFakeTimers({ advanceTimers: true }) }) beforeEach(() => { jest.clearAllMocks() - // Set up mocks with proper type assertions generateTextMock = generateText as GenerateTextMock createProviderMock = aiProvider.createProvider as CreateProviderMock getLLMConfigMock = configService.getLLMConfig as GetLLMConfigMock - // Default mock implementations with proper types generateTextMock.mockResolvedValue({ text: 'mermaid graph', usage: { @@ -65,7 +90,9 @@ describe('AiService', () => { tpd: '10000', } satisfies LLMProps) - aiService = new AiService() + usageState = { tokensUsed: 0, requestsCount: 0 } + usageCounter = buildUsageCounterMock(usageState) + aiService = new AiService(usageCounter) }) afterEach(() => { @@ -78,41 +105,49 @@ describe('AiService', () => { describe('estimateTokens', () => { it('estimates tokens for short input', () => { - expect(aiService.estimateTokens('hello')).toBe(Math.ceil(5 / 4) + 200) - }) - - it('estimates tokens for long input', () => { - const input = 'a'.repeat(4000) - expect(aiService.estimateTokens(input)).toBe(Math.ceil(4000 / 4) + 200) + expect(aiService.estimateTokens('hello')).toBe( + Math.ceil(5 / 4) + SYSTEM_PROMPT_TOKEN_OVERHEAD + ) }) it('estimates tokens for empty input', () => { - expect(aiService.estimateTokens('')).toBe(200) - }) - - it('estimates tokens for CJK characters', () => { - const input = '\u4f60\u597d\u4e16\u754c' - expect(aiService.estimateTokens(input)).toBe(Math.ceil(4 / 4) + 200) + expect(aiService.estimateTokens('')).toBe(SYSTEM_PROMPT_TOKEN_OVERHEAD) }) }) describe('generateMermaid', () => { - it('calls the generateText functionality', async () => { - const result = await aiService.generateMermaid('create a mindmap', 'en') + it('forwards prompt with language tag and abort signal to generateText', async () => { + await aiService.generateMermaid('create a mindmap', 'en') - expect(result).toBe('mermaid graph') expect(generateTextMock).toHaveBeenCalledWith( expect.objectContaining({ - system: expect.any(String), prompt: 'create a mindmap', + abortSignal: expect.any(AbortSignal), }) ) }) + it('forwards configured maxOutputTokens to generateText', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + maxOutputTokens: '256', + } satisfies LLMProps) + aiService = new AiService(usageCounter) + + await aiService.generateMermaid('hi', 'en') + + expect(generateTextMock).toHaveBeenCalledWith( + expect.objectContaining({ maxOutputTokens: 256 }) + ) + }) + it('returns empty string when provider is not configured', async () => { createProviderMock.mockReturnValueOnce(undefined) - aiService = new AiService() + aiService = new AiService(usageCounter) const result = await aiService.generateMermaid('create a mindmap', 'en') expect(result).toBe('') @@ -130,46 +165,131 @@ describe('AiService', () => { tpd: '10000', } satisfies LLMProps) - aiService = new AiService() + aiService = new AiService(usageCounter) const result = await aiService.generateMermaid('create a mindmap', 'en') expect(result).toBe('') expect(generateTextMock).not.toHaveBeenCalled() }) - it('throws an error if the tokens per day limit is reached', async () => { - // estimateTokens('short') = ceil(5/4) + 200 = 202 + it('rejects atomically when TPD would be exceeded (no row written)', async () => { getLLMConfigMock.mockReturnValue({ url: 'localhost:3000', token: 'test-token', provider: 'openai', model: 'gpt-4', - tpm: undefined, - rpm: undefined, - tpd: '1000', + tpd: '300', } satisfies LLMProps) + aiService = new AiService(usageCounter) - aiService = new AiService() - - // First request uses 800 tokens (tracked after call) + // estimateTokens('short') = 202; first call is fine and bills 100 tokens generateTextMock.mockResolvedValueOnce({ text: 'first response', - usage: { - inputTokens: 300, - outputTokens: 500, - totalTokens: 800, - }, + usage: { inputTokens: 50, outputTokens: 50, totalTokens: 100 }, } as MockGenerateTextReturn) await aiService.generateMermaid('short', 'en') - // Second request: daily total is 800, estimated ~202, total 1002 > 1000 + // Second call estimate (202) + already-billed (100) = 302 > 300 -> reject await expect(aiService.generateMermaid('short', 'en')).rejects.toThrow( RateLimitExceededException ) + + // No reservation was created for the rejected call, so totals reflect + // only the first call's actual (100) and one request — and no rollback. + const releaseMock = usageCounter.release as jest.MockedFunction< + typeof usageCounter.release + > + expect({ + ...usageState, + releaseCalls: releaseMock.mock.calls.length, + }).toEqual({ + tokensUsed: 100, + requestsCount: 1, + releaseCalls: 0, + }) + }) + + it('allows a reservation that lands exactly at the TPD cap', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + tpd: '202', + } satisfies LLMProps) + aiService = new AiService(usageCounter) + + // estimateTokens('short') = 202; equal to the cap, so this must succeed. + await aiService.generateMermaid('short', 'en') + expect(usageCounter.reserve).toHaveBeenCalledWith( + expect.any(String), + 202, + 202 + ) + }) + + it('reconciles billed tokens via adjustTokens with the correct delta', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + } satisfies LLMProps) + aiService = new AiService(usageCounter) + + generateTextMock.mockResolvedValueOnce({ + text: 'response', + usage: { inputTokens: 50, outputTokens: 50, totalTokens: 100 }, + } as MockGenerateTextReturn) + await aiService.generateMermaid('short', 'en') + + // estimated = 202, actual = 100 -> delta = -102 + expect(usageCounter.adjustTokens).toHaveBeenCalledWith( + expect.any(String), + -102 + ) + }) + + it('keeps the conservative reservation when adjustTokens fails after a successful LLM call', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + } satisfies LLMProps) + aiService = new AiService(usageCounter) + ;( + usageCounter.adjustTokens as jest.MockedFunction< + typeof usageCounter.adjustTokens + > + ).mockRejectedValueOnce(new Error('db hiccup')) + + // Reconciliation failure must not propagate, must not release. + await aiService.generateMermaid('short', 'en') + expect(usageCounter.release).not.toHaveBeenCalled() + // Reservation persists at the conservative estimate (202), not actual. + expect(usageState.tokensUsed).toBe(202) + }) + + it('does not mask the original LLM error when release fails during rollback', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + } satisfies LLMProps) + aiService = new AiService(usageCounter) + ;( + usageCounter.release as jest.MockedFunction + ).mockRejectedValueOnce(new Error('db unreachable')) + + generateTextMock.mockRejectedValueOnce(new Error('boom')) + await expect(aiService.generateMermaid('short', 'en')).rejects.toThrow( + 'boom' + ) }) it('throws an error if the tokens per minute limit is reached', async () => { - // estimateTokens('short') = ceil(5/4) + 200 = 202 getLLMConfigMock.mockReturnValue({ url: 'localhost:3000', token: 'test-token', @@ -179,10 +299,8 @@ describe('AiService', () => { rpm: undefined, tpd: undefined, } satisfies LLMProps) + aiService = new AiService(usageCounter) - aiService = new AiService() - - // First request uses 800 tokens generateTextMock.mockResolvedValueOnce({ text: 'first response', usage: { @@ -193,7 +311,6 @@ describe('AiService', () => { } as MockGenerateTextReturn) await aiService.generateMermaid('short', 'en') - // Second request: 800 tracked + 202 estimated = 1002 > 1000 await expect(aiService.generateMermaid('short', 'en')).rejects.toThrow( RateLimitExceededException ) @@ -209,23 +326,12 @@ describe('AiService', () => { rpm: '3', tpd: undefined, } satisfies LLMProps) + aiService = new AiService(usageCounter) - aiService = new AiService() - - // First 3 requests should succeed for (let i = 0; i < 3; i++) { - generateTextMock.mockResolvedValueOnce({ - text: `response ${i}`, - usage: { - inputTokens: 20, - outputTokens: 80, - totalTokens: 100, - }, - } as MockGenerateTextReturn) await aiService.generateMermaid(`request ${i}`, 'en') } - // Fourth request should fail (exceeds 3 requests per minute) await expect( aiService.generateMermaid('fourth request', 'en') ).rejects.toThrow(RateLimitExceededException) @@ -234,67 +340,71 @@ describe('AiService', () => { ).rejects.toThrow('Request limit exceeded.') }) - it('resets token count after one minute', async () => { - // estimateTokens('short') = 202 + it('reserves tokens before generateText so concurrent callers see the precharge', async () => { getLLMConfigMock.mockReturnValue({ url: 'localhost:3000', token: 'test-token', provider: 'openai', model: 'gpt-4', - tpm: '1000', - rpm: undefined, - tpd: undefined, + tpm: '500', } satisfies LLMProps) + aiService = new AiService(usageCounter) + + // estimateTokens('short') = 202. Two concurrent calls would need 404 reserved + // up-front; with TPM=500, only the first should succeed. + let release!: () => void + const block = new Promise((resolve) => { + release = resolve + }) + generateTextMock.mockImplementationOnce((async () => { + await block + return { + text: 'slow', + usage: { inputTokens: 50, outputTokens: 50, totalTokens: 100 }, + } + }) as unknown as typeof generateText) + + const first = aiService.generateMermaid('short', 'en') + // Second call must observe first's pre-charge of 202 already in the + // per-minute window, blocking it instead of racing through the precheck. + await expect( + aiService.generateMermaid('s'.repeat(1200), 'en') + ).rejects.toThrow(RateLimitExceededException) + release() + await first + }) - aiService = new AiService() - - // First request uses 800 tokens - generateTextMock.mockResolvedValueOnce({ - text: 'first response', - usage: { - inputTokens: 300, - outputTokens: 500, - totalTokens: 800, - }, - } as MockGenerateTextReturn) - await aiService.generateMermaid('short', 'en') + it('releases the per-minute reservation when generateText fails', async () => { + getLLMConfigMock.mockReturnValue({ + url: 'localhost:3000', + token: 'test-token', + provider: 'openai', + model: 'gpt-4', + rpm: '1', + } satisfies LLMProps) + aiService = new AiService(usageCounter) - // Second request would exceed limit (800 + 202 > 1000) + generateTextMock.mockRejectedValueOnce(new Error('boom')) await expect(aiService.generateMermaid('short', 'en')).rejects.toThrow( - RateLimitExceededException + 'boom' ) - // Advance time by more than 1 minute - jest.advanceTimersByTime(61000) - - // Now the request should succeed since the minute has passed - generateTextMock.mockResolvedValueOnce({ - text: 'second response', - usage: { - inputTokens: 100, - outputTokens: 300, - totalTokens: 400, - }, - } as MockGenerateTextReturn) - const result = await aiService.generateMermaid('short', 'en') - expect(result).toBe('second response') + // After release, RPM=1 should still allow one more call. + await aiService.generateMermaid('short', 'en') }) - it('resets daily token count when date changes', async () => { - // estimateTokens('short') = 202 + it('resets token count after one minute', async () => { getLLMConfigMock.mockReturnValue({ url: 'localhost:3000', token: 'test-token', provider: 'openai', model: 'gpt-4', - tpm: undefined, + tpm: '1000', rpm: undefined, - tpd: '1000', + tpd: undefined, } satisfies LLMProps) + aiService = new AiService(usageCounter) - aiService = new AiService() - - // Use up most of the daily limit generateTextMock.mockResolvedValueOnce({ text: 'first response', usage: { @@ -305,75 +415,16 @@ describe('AiService', () => { } as MockGenerateTextReturn) await aiService.generateMermaid('short', 'en') - // Second request would exceed daily limit (800 + 202 > 1000) await expect(aiService.generateMermaid('short', 'en')).rejects.toThrow( RateLimitExceededException ) - // Mock date change to next day - const tomorrow = new Date() - tomorrow.setDate(tomorrow.getDate() + 1) - jest.setSystemTime(tomorrow) - - // Now the request should succeed with reset daily counter - generateTextMock.mockResolvedValueOnce({ - text: 'second response', - usage: { - inputTokens: 100, - outputTokens: 400, - totalTokens: 500, - }, - } as MockGenerateTextReturn) - const result = await aiService.generateMermaid('short', 'en') - expect(result).toBe('second response') - }) - - it('handles multiple rate limits simultaneously', async () => { - // estimateTokens('request N') = ceil(9/4) + 200 = 203 - getLLMConfigMock.mockReturnValue({ - url: 'localhost:3000', - token: 'test-token', - provider: 'openai', - model: 'gpt-4', - tpm: '5000', - rpm: '5', - tpd: '5000', - } satisfies LLMProps) - - aiService = new AiService() - - // Make 4 requests, each using 400 tokens (total 1600 tokens, 4 requests) - for (let i = 0; i < 4; i++) { - generateTextMock.mockResolvedValueOnce({ - text: `response ${i}`, - usage: { - inputTokens: 100, - outputTokens: 300, - totalTokens: 400, - }, - } as MockGenerateTextReturn) - await aiService.generateMermaid(`request ${i}`, 'en') - } - - // 5th request should succeed (1600 + 203 = 1803 < 5000) - generateTextMock.mockResolvedValueOnce({ - text: 'fifth response', - usage: { - inputTokens: 50, - outputTokens: 250, - totalTokens: 300, - }, - } as MockGenerateTextReturn) - await aiService.generateMermaid('fifth req', 'en') + jest.advanceTimersByTime(61000) - // 6th request should fail due to RPM limit (6 > 5) - await expect( - aiService.generateMermaid('sixth req', 'en') - ).rejects.toThrow('Request limit exceeded.') + await aiService.generateMermaid('short', 'en') }) it('uses input length for token estimation in rate limiting', async () => { - // A long input (2000 chars) should estimate ~700 tokens (2000/4 + 200) getLLMConfigMock.mockReturnValue({ url: 'localhost:3000', token: 'test-token', @@ -383,11 +434,9 @@ describe('AiService', () => { rpm: undefined, tpd: undefined, } satisfies LLMProps) - - aiService = new AiService() + aiService = new AiService(usageCounter) const longInput = 'a'.repeat(2000) - // estimateTokens = ceil(2000/4) + 200 = 700 > tpm of 600 await expect(aiService.generateMermaid(longInput, 'en')).rejects.toThrow( RateLimitExceededException ) diff --git a/teammapper-backend/src/map/services/ai.service.ts b/teammapper-backend/src/map/services/ai.service.ts index 468bf5f5..c40ff5dd 100644 --- a/teammapper-backend/src/map/services/ai.service.ts +++ b/teammapper-backend/src/map/services/ai.service.ts @@ -1,36 +1,54 @@ import { Injectable, Logger } from '@nestjs/common' -import { generateText } from 'ai' +import { generateText, LanguageModel } from 'ai' import { SYSTEM_PROMPT, userPrompt, SupportedLanguage } from '../utils/prompts' import { createProvider } from '../utils/aiProvider' import configService from '../../config.service' import { RateLimitExceededException } from '../controllers/rate-limit.exception' +import { LlmUsageCounterService } from './llm-usage-counter.service' -const SYSTEM_PROMPT_TOKEN_OVERHEAD = 200 +export const SYSTEM_PROMPT_TOKEN_OVERHEAD = 200 +const DEFAULT_MAX_OUTPUT_TOKENS = 1024 +const DEFAULT_REQUEST_TIMEOUT_MS = 30_000 -interface RequestTokenEntry { +interface PerMinuteEntry { time: number count: number } +interface Reservation { + entry: PerMinuteEntry + dateUsage: string + estimated: number +} + +interface ParsedLimits { + tpm: number | undefined + rpm: number | undefined + tpd: number | undefined + maxOutputTokens: number + timeoutMs: number +} + @Injectable() export class AiService { private readonly logger = new Logger(AiService.name) private readonly llmConfig = configService.getLLMConfig() - // NOTE: Rate limiting is per-process. In multi-instance deployments, - // effective limits are multiplied by the number of instances. - private tokensUsedPerMinute: RequestTokenEntry[] = [] - private totalTokensDaily = { count: 0, date: new Date().toLocaleDateString() } - private readonly parsedLimits: { - tpm: number | undefined - rpm: number | undefined - tpd: number | undefined - } + // NOTE: TPM/RPM limiting is per-process. The daily token cap is DB-backed via + // LlmUsageCounterService and works across restarts and multi-instance deploys. + private tokensUsedPerMinute: PerMinuteEntry[] = [] + private readonly limits: ParsedLimits - constructor() { - this.parsedLimits = { - tpm: this.llmConfig.tpm ? parseInt(this.llmConfig.tpm, 10) : undefined, - rpm: this.llmConfig.rpm ? parseInt(this.llmConfig.rpm, 10) : undefined, - tpd: this.llmConfig.tpd ? parseInt(this.llmConfig.tpd, 10) : undefined, + constructor(private readonly usageCounter: LlmUsageCounterService) { + this.limits = { + tpm: AiService.parseInt(this.llmConfig.tpm), + rpm: AiService.parseInt(this.llmConfig.rpm), + tpd: AiService.parseInt(this.llmConfig.tpd), + maxOutputTokens: + AiService.parseInt(this.llmConfig.maxOutputTokens) ?? + DEFAULT_MAX_OUTPUT_TOKENS, + timeoutMs: + AiService.parseInt(this.llmConfig.timeoutMs) ?? + DEFAULT_REQUEST_TIMEOUT_MS, } } @@ -42,22 +60,27 @@ export class AiService { if (!provider || !this.llmConfig.model) return '' const estimated = this.estimateTokens(mindmapDescription) - await this.waitForRateLimit(estimated) - const { text, usage } = await generateText({ - model: provider(this.llmConfig.model), - system: SYSTEM_PROMPT, - prompt: userPrompt(mindmapDescription, language), - }) - this.tokensUsedPerMinute = [ - ...this.tokensUsedPerMinute, - { time: Date.now(), count: usage.totalTokens ?? 0 }, - ] - this.totalTokensDaily = { - ...this.totalTokensDaily, - count: this.totalTokensDaily.count + (usage.totalTokens ?? 0), + const reservation = await this.reserveBudget(estimated) + let text: string + let actual: number + try { + const result = await this.callLlm( + provider(this.llmConfig.model), + mindmapDescription, + language + ) + text = result.text + actual = result.usage.totalTokens ?? 0 + } catch (err) { + await this.releaseReservation(reservation) + throw err } - this.logger.debug(`Daily used token count: ${this.totalTokensDaily.count}`) - + // Reconciliation is best-effort: a failure here must not roll back a + // successful LLM call (would silently under-bill the budget). + await this.tryCommitReservation(reservation, actual) + this.logger.debug( + `LLM call billed ${actual} tokens (estimated ${estimated})` + ) return text } @@ -65,44 +88,114 @@ export class AiService { return Math.ceil(input.length / 4) + SYSTEM_PROMPT_TOKEN_OVERHEAD } - private async waitForRateLimit(estimatedTokens: number) { - const now = Date.now() - const oneMinuteAgo = now - 60000 - this.tokensUsedPerMinute = this.tokensUsedPerMinute.filter( - (entry) => entry.time > oneMinuteAgo - ) - if (new Date().toLocaleDateString() !== this.totalTokensDaily.date) { - this.totalTokensDaily = { - count: 0, - date: new Date().toLocaleDateString(), - } + private static parseInt(raw: string | undefined): number | undefined { + if (!raw) return undefined + const value = Number.parseInt(raw, 10) + return Number.isFinite(value) ? value : undefined + } + + private async callLlm( + model: LanguageModel, + description: string, + language: SupportedLanguage + ) { + return await generateText({ + model, + system: SYSTEM_PROMPT, + prompt: userPrompt(description, language), + maxOutputTokens: this.limits.maxOutputTokens, + abortSignal: AbortSignal.timeout(this.limits.timeoutMs), + }) + } + + private async reserveBudget(estimated: number): Promise { + this.checkPerMinuteLimits(estimated) + const entry: PerMinuteEntry = { time: Date.now(), count: estimated } + this.tokensUsedPerMinute.push(entry) + const dateUsage = LlmUsageCounterService.currentDateUsage() + try { + await this.reserveDaily(dateUsage, estimated) + } catch (err) { + this.tokensUsedPerMinute = this.tokensUsedPerMinute.filter( + (e) => e !== entry + ) + throw err } + return { entry, dateUsage, estimated } + } + private checkPerMinuteLimits(estimated: number): void { + this.pruneExpiredEntries() const currentTokens = this.tokensUsedPerMinute.reduce( - (sum, entry) => sum + entry.count, + (sum, e) => sum + e.count, 0 ) - const currentRequestCount = this.tokensUsedPerMinute.length - if ( - this.parsedLimits.tpm !== undefined && - currentTokens + estimatedTokens > this.parsedLimits.tpm + this.limits.tpm !== undefined && + currentTokens + estimated > this.limits.tpm ) { throw new RateLimitExceededException('tokens') } - if ( - this.parsedLimits.rpm !== undefined && - currentRequestCount + 1 > this.parsedLimits.rpm + this.limits.rpm !== undefined && + this.tokensUsedPerMinute.length + 1 > this.limits.rpm ) { throw new RateLimitExceededException('requests') } + } - if ( - this.parsedLimits.tpd !== undefined && - this.totalTokensDaily.count + estimatedTokens > this.parsedLimits.tpd - ) { + private pruneExpiredEntries(): void { + const oneMinuteAgo = Date.now() - 60_000 + this.tokensUsedPerMinute = this.tokensUsedPerMinute.filter( + (e) => e.time > oneMinuteAgo + ) + } + + private async reserveDaily( + dateUsage: string, + estimated: number + ): Promise { + const totals = await this.usageCounter.reserve( + dateUsage, + estimated, + this.limits.tpd + ) + if (totals === null) { throw new RateLimitExceededException('tokens') } } + + private async tryCommitReservation( + reservation: Reservation, + actual: number + ): Promise { + reservation.entry.count = actual + try { + await this.usageCounter.adjustTokens( + reservation.dateUsage, + actual - reservation.estimated + ) + } catch (err) { + this.logger.warn( + `Failed to reconcile actual tokens for ${reservation.dateUsage}; keeping conservative reservation. ${(err as Error).message}` + ) + } + } + + private async releaseReservation(reservation: Reservation): Promise { + this.tokensUsedPerMinute = this.tokensUsedPerMinute.filter( + (e) => e !== reservation.entry + ) + try { + await this.usageCounter.release( + reservation.dateUsage, + reservation.estimated + ) + } catch (err) { + // Don't mask the original error from the caller's catch path. + this.logger.error( + `Failed to release reserved tokens for ${reservation.dateUsage}: ${(err as Error).message}` + ) + } + } } diff --git a/teammapper-backend/src/map/services/llm-usage-counter.service.spec.ts b/teammapper-backend/src/map/services/llm-usage-counter.service.spec.ts new file mode 100644 index 00000000..4682ff1f --- /dev/null +++ b/teammapper-backend/src/map/services/llm-usage-counter.service.spec.ts @@ -0,0 +1,77 @@ +import { jest } from '@jest/globals' +import { Repository } from 'typeorm' +import { LlmUsageCounterService } from './llm-usage-counter.service' +import { LlmUsageCounter } from '../entities/llmUsageCounter.entity' + +describe('LlmUsageCounterService', () => { + let service: LlmUsageCounterService + let queryMock: jest.Mock<(sql: string, params: unknown[]) => Promise> + + beforeEach(() => { + queryMock = jest.fn() as unknown as typeof queryMock + const repo = { query: queryMock } as unknown as Repository + service = new LlmUsageCounterService(repo) + }) + + it('produces UTC date keys independent of locale', () => { + const dateUsage = LlmUsageCounterService.currentDateUsage( + new Date('2026-05-08T23:30:00Z') + ) + expect(dateUsage).toBe('2026-05-08') + }) + + it('reserve issues an atomic upsert and returns numeric totals', async () => { + queryMock.mockResolvedValueOnce([{ tokensUsed: '500', requestsCount: '3' }]) + const totals = await service.reserve('2026-05-08', 200) + expect(totals).toEqual({ tokensUsed: 500, requestsCount: 3 }) + }) + + it('reserve returns null when the SQL guard blocks the upsert', async () => { + queryMock.mockResolvedValueOnce([]) + const totals = await service.reserve('2026-05-08', 200, 100) + expect(totals).toBeNull() + }) + + it('reserve passes the dateUsage, tokens, and cap as bound parameters', async () => { + queryMock.mockResolvedValueOnce([{ tokensUsed: '1', requestsCount: '1' }]) + await service.reserve('2026-05-08', 200, 1000) + expect(queryMock).toHaveBeenCalledWith(expect.any(String), [ + '2026-05-08', + 200, + 1000, + ]) + }) + + it('reserve passes null when no cap is provided', async () => { + queryMock.mockResolvedValueOnce([{ tokensUsed: '1', requestsCount: '1' }]) + await service.reserve('2026-05-08', 200) + expect(queryMock).toHaveBeenCalledWith(expect.any(String), [ + '2026-05-08', + 200, + null, + ]) + }) + + it('adjustTokens skips the database when delta is zero', async () => { + await service.adjustTokens('2026-05-08', 0) + expect(queryMock).not.toHaveBeenCalled() + }) + + it('adjustTokens issues an UPDATE for non-zero deltas', async () => { + queryMock.mockResolvedValueOnce([]) + await service.adjustTokens('2026-05-08', -50) + expect(queryMock).toHaveBeenCalledWith(expect.stringContaining('UPDATE'), [ + '2026-05-08', + -50, + ]) + }) + + it('release decrements both tokens and request count', async () => { + queryMock.mockResolvedValueOnce([]) + await service.release('2026-05-08', 200) + expect(queryMock).toHaveBeenCalledWith( + expect.stringContaining('"requestsCount" - 1'), + ['2026-05-08', 200] + ) + }) +}) diff --git a/teammapper-backend/src/map/services/llm-usage-counter.service.ts b/teammapper-backend/src/map/services/llm-usage-counter.service.ts new file mode 100644 index 00000000..ac407c98 --- /dev/null +++ b/teammapper-backend/src/map/services/llm-usage-counter.service.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Repository } from 'typeorm' +import { LlmUsageCounter } from '../entities/llmUsageCounter.entity' + +interface UsageRow { + tokensUsed: string | number + requestsCount: string | number +} + +/** + * Anonymized aggregate counter for LLM usage. Stores only per-date totals + * so the daily token cap is enforced across restarts and multi-instance deployments. + */ +@Injectable() +export class LlmUsageCounterService { + constructor( + @InjectRepository(LlmUsageCounter) + private readonly repo: Repository + ) {} + + /** + * UTC date key (YYYY-MM-DD), locale-independent. + */ + static currentDateUsage(now: Date = new Date()): string { + return now.toISOString().slice(0, 10) + } + + /** + * Atomically reserve `tokens` and one request slot for `dateUsage`, + * returning the new totals after the increment, or `null` if the reservation + * would push the day's total above `cap`. Cap check happens inside the SQL so + * concurrent callers cannot both pass a "would I exceed?" check. + */ + async reserve( + dateUsage: string, + tokens: number, + cap?: number + ): Promise<{ tokensUsed: number; requestsCount: number } | null> { + const rows = (await this.repo.query( + `INSERT INTO llm_usage_counter ("dateUsage", "tokensUsed", "requestsCount") + SELECT $1, $2, 1 + WHERE $3::bigint IS NULL OR $2 <= $3::bigint + ON CONFLICT ("dateUsage") DO UPDATE + SET "tokensUsed" = llm_usage_counter."tokensUsed" + EXCLUDED."tokensUsed", + "requestsCount" = llm_usage_counter."requestsCount" + 1 + WHERE $3::bigint IS NULL + OR llm_usage_counter."tokensUsed" + EXCLUDED."tokensUsed" <= $3::bigint + RETURNING "tokensUsed", "requestsCount"`, + [dateUsage, tokens, cap ?? null] + )) as UsageRow[] + if (rows.length === 0) return null + return { + tokensUsed: Number(rows[0].tokensUsed), + requestsCount: Number(rows[0].requestsCount), + } + } + + /** + * Adjust the token total for `dateUsage` by `delta` (may be negative). + * Used to reconcile reserved tokens against actual billed tokens. + */ + async adjustTokens(dateUsage: string, delta: number): Promise { + if (delta === 0) return + await this.repo.query( + `UPDATE llm_usage_counter SET "tokensUsed" = GREATEST(0, "tokensUsed" + $2) WHERE "dateUsage" = $1`, + [dateUsage, delta] + ) + } + + /** + * Roll back a reservation entirely (token-wise and request-wise), + * e.g. after a failed `generateText` call. + */ + async release(dateUsage: string, tokens: number): Promise { + await this.repo.query( + `UPDATE llm_usage_counter + SET "tokensUsed" = GREATEST(0, "tokensUsed" - $2), + "requestsCount" = GREATEST(0, "requestsCount" - 1) + WHERE "dateUsage" = $1`, + [dateUsage, tokens] + ) + } +} diff --git a/teammapper-backend/src/migrations/1778265117672-AddLlmUsageCounter.ts b/teammapper-backend/src/migrations/1778265117672-AddLlmUsageCounter.ts new file mode 100644 index 00000000..7ddbc80b --- /dev/null +++ b/teammapper-backend/src/migrations/1778265117672-AddLlmUsageCounter.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from 'typeorm' + +export class AddLlmUsageCounter1778265117672 implements MigrationInterface { + name = 'AddLlmUsageCounter1778265117672' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "llm_usage_counter" ( + "dateUsage" date NOT NULL, + "tokensUsed" bigint NOT NULL DEFAULT 0, + "requestsCount" bigint NOT NULL DEFAULT 0, + CONSTRAINT "PK_llm_usage_counter_dateUsage" PRIMARY KEY ("dateUsage") + )` + ) + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "llm_usage_counter"`) + } +} diff --git a/teammapper-backend/test/llm-usage-counter.e2e-spec.ts b/teammapper-backend/test/llm-usage-counter.e2e-spec.ts new file mode 100644 index 00000000..2cee3606 --- /dev/null +++ b/teammapper-backend/test/llm-usage-counter.e2e-spec.ts @@ -0,0 +1,84 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { ConfigModule } from '@nestjs/config' +import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm' +import { Repository } from 'typeorm' +import { LlmUsageCounter } from '../src/map/entities/llmUsageCounter.entity' +import { LlmUsageCounterService } from '../src/map/services/llm-usage-counter.service' +import { createTestConfiguration, destroyWorkerDatabase } from './db' + +describe('LlmUsageCounterService (e2e)', () => { + let moduleRef: TestingModule + let service: LlmUsageCounterService + let repo: Repository + + const readRow = async (date: string) => { + const row = await repo.findOneByOrFail({ dateUsage: date }) + return { + tokensUsed: Number(row.tokensUsed), + requestsCount: Number(row.requestsCount), + } + } + + beforeAll(async () => { + moduleRef = await Test.createTestingModule({ + imports: [ + ConfigModule, + TypeOrmModule.forRoot( + await createTestConfiguration(process.env.JEST_WORKER_ID || '') + ), + TypeOrmModule.forFeature([LlmUsageCounter]), + ], + providers: [LlmUsageCounterService], + }).compile() + + service = moduleRef.get(LlmUsageCounterService) + repo = moduleRef.get>( + getRepositoryToken(LlmUsageCounter) + ) + }) + + afterAll(async () => { + await destroyWorkerDatabase( + repo.manager.connection, + process.env.JEST_WORKER_ID || '' + ) + await moduleRef.close() + }) + + it('reserve creates the row on the INSERT path', async () => { + const totals = await service.reserve('2026-05-09', 200, 1000) + expect(totals).toEqual({ tokensUsed: 200, requestsCount: 1 }) + }) + + it('reserve increments existing rows via ON CONFLICT DO UPDATE', async () => { + const date = '2026-05-10' + await service.reserve(date, 200, 1000) + const second = await service.reserve(date, 300, 1000) + expect(second).toEqual({ tokensUsed: 500, requestsCount: 2 }) + }) + + it('reserve returns null and leaves the row untouched when the cap is exceeded', async () => { + const date = '2026-05-11' + await service.reserve(date, 700, 1000) + const blocked = await service.reserve(date, 400, 1000) + expect({ blocked, state: await readRow(date) }).toEqual({ + blocked: null, + state: { tokensUsed: 700, requestsCount: 1 }, + }) + }) + + it('adjustTokens shifts the persisted total', async () => { + const date = '2026-05-12' + await service.reserve(date, 200, 1000) + await service.adjustTokens(date, -150) + expect(await readRow(date)).toEqual({ tokensUsed: 50, requestsCount: 1 }) + }) + + it('release rolls a reservation back fully', async () => { + const date = '2026-05-13' + await service.reserve(date, 200, 1000) + await service.reserve(date, 300, 1000) + await service.release(date, 300) + expect(await readRow(date)).toEqual({ tokensUsed: 200, requestsCount: 1 }) + }) +}) From 8ee037550f105c314cfbc5d07f3bb4ca7865757d Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 10 May 2026 14:31:09 +0200 Subject: [PATCH 03/14] improve logging security (#1284) --- teammapper-backend/src/config.service.ts | 4 + .../filters/global-exception.filter.spec.ts | 118 ++++++++++++++++++ .../src/filters/global-exception.filter.ts | 27 ++-- .../src/map/controllers/maps.controller.ts | 5 +- .../src/map/controllers/mermaid.controller.ts | 3 +- .../src/map/schemas/gateway.schema.ts | 11 +- .../src/map/schemas/sanitize-issues.spec.ts | 55 ++++++++ .../src/map/schemas/sanitize-issues.ts | 49 ++++++++ 8 files changed, 249 insertions(+), 23 deletions(-) create mode 100644 teammapper-backend/src/filters/global-exception.filter.spec.ts create mode 100644 teammapper-backend/src/map/schemas/sanitize-issues.spec.ts create mode 100644 teammapper-backend/src/map/schemas/sanitize-issues.ts diff --git a/teammapper-backend/src/config.service.ts b/teammapper-backend/src/config.service.ts index 85b3cd97..498cde5a 100644 --- a/teammapper-backend/src/config.service.ts +++ b/teammapper-backend/src/config.service.ts @@ -142,6 +142,10 @@ class ConfigService { migrationsTableName: 'migration', migrations: [join(__dirname, 'migrations', '*.{ts,js}')], + // Defense-in-depth: a `where: { col: undefined }` would otherwise be + // silently dropped and return every row. + invalidWhereValuesBehavior: { undefined: 'throw', null: 'throw' }, + extra: { query_timeout: this.getValue('POSTGRES_QUERY_TIMEOUT') || 100000, statement_timeout: diff --git a/teammapper-backend/src/filters/global-exception.filter.spec.ts b/teammapper-backend/src/filters/global-exception.filter.spec.ts new file mode 100644 index 00000000..5f17abff --- /dev/null +++ b/teammapper-backend/src/filters/global-exception.filter.spec.ts @@ -0,0 +1,118 @@ +import { + ArgumentsHost, + BadRequestException, + HttpException, + HttpStatus, + Logger, + NotFoundException, +} from '@nestjs/common' +import { GlobalExceptionFilter } from './global-exception.filter' + +interface FakeResponse { + status: jest.Mock + json: jest.Mock +} + +const createHttpHost = (response: FakeResponse): ArgumentsHost => + ({ + getType: () => 'http', + switchToHttp: () => ({ getResponse: () => response }), + }) as unknown as ArgumentsHost + +const createResponse = (): FakeResponse => { + const response: FakeResponse = { + status: jest.fn(), + json: jest.fn(), + } + response.status.mockReturnValue(response) + response.json.mockReturnValue(response) + return response +} + +describe('GlobalExceptionFilter', () => { + let filter: GlobalExceptionFilter + let errorSpy: jest.SpyInstance + + beforeEach(() => { + filter = new GlobalExceptionFilter() + errorSpy = jest.spyOn(Logger.prototype, 'error').mockImplementation() + }) + + afterEach(() => { + errorSpy.mockRestore() + }) + + it('forwards a BadRequestException unchanged to the client', () => { + const response = createResponse() + const issues = [{ kind: 'schema', type: 'string', message: 'invalid' }] + const exception = new BadRequestException(issues) + + filter.catch(exception, createHttpHost(response)) + + expect({ + status: response.status.mock.calls[0][0], + body: response.json.mock.calls[0][0], + }).toEqual({ + status: HttpStatus.BAD_REQUEST, + body: exception.getResponse(), + }) + }) + + it('does not log HttpException — they are intentional client-facing flow', () => { + filter.catch( + new BadRequestException('bad'), + createHttpHost(createResponse()) + ) + filter.catch(new NotFoundException(), createHttpHost(createResponse())) + filter.catch( + new HttpException('teapot', HttpStatus.I_AM_A_TEAPOT), + createHttpHost(createResponse()) + ) + + expect(errorSpy).not.toHaveBeenCalled() + }) + + it('forwards a custom HttpException status', () => { + const response = createResponse() + filter.catch( + new HttpException('teapot', HttpStatus.I_AM_A_TEAPOT), + createHttpHost(response) + ) + + expect(response.status).toHaveBeenCalledWith(HttpStatus.I_AM_A_TEAPOT) + }) + + it('returns generic 500 body for non-HttpException', () => { + const response = createResponse() + filter.catch(new Error('boom'), createHttpHost(response)) + + expect({ + status: response.status.mock.calls[0][0], + bodyMessage: response.json.mock.calls[0][0].message, + }).toEqual({ + status: 500, + bodyMessage: 'Internal server error', + }) + }) + + it('does not include the raw exception object in the log payload', () => { + filter.catch( + new Error('boom: secret-token-leak'), + createHttpHost(createResponse()) + ) + + expect(errorSpy.mock.calls[0][0]).not.toHaveProperty('error') + }) + + it('logs only allowlisted fields for non-HttpException', () => { + const exception = new Error('boom') + filter.catch(exception, createHttpHost(createResponse())) + + expect(Object.keys(errorSpy.mock.calls[0][0]).sort()).toEqual([ + 'context', + 'message', + 'stack', + 'type', + ]) + }) +}) diff --git a/teammapper-backend/src/filters/global-exception.filter.ts b/teammapper-backend/src/filters/global-exception.filter.ts index e7169cdf..1095bcf8 100644 --- a/teammapper-backend/src/filters/global-exception.filter.ts +++ b/teammapper-backend/src/filters/global-exception.filter.ts @@ -2,11 +2,11 @@ import { ExceptionFilter, Catch, ArgumentsHost, + HttpException, Logger, - NotFoundException, } from '@nestjs/common' -// This is for any unhandled gateway and "internal" NestJS related errors - like if the gateway can't reach clients or things like that. +// This is for any unhandled gateway and "internal" NestJS related errors, like if the gateway can't reach clients or things like that. // It will try to always keep clients and their websockets alive and gracefully send errors over the wire, without revealing internal error reasons. @Catch() export class GlobalExceptionFilter implements ExceptionFilter { @@ -15,26 +15,23 @@ export class GlobalExceptionFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.getType() - // Skip logging for NotFoundException in HTTP context - // This is handled before anything else (and explicitly outside of ctx switch) to prevent _any_ error from logging - if (ctx === 'http' && exception instanceof NotFoundException) { + // HttpException is intentional client-facing flow (validation, auth, not-found...). + // Forward its status/body unchanged and don't log `util.inspect` the + // exception expands `.response` (raw user input, secrets) and any + // `QueryFailedError.parameters` into server logs. + if (ctx === 'http' && exception instanceof HttpException) { const response = host.switchToHttp().getResponse() - return response.status(404).json({ - statusCode: 404, - message: 'Not Found', - timestamp: new Date().toISOString(), - }) + return response + .status(exception.getStatus()) + .json(exception.getResponse()) } - const errorDetails = { - error: exception, + this.logger.error({ type: exception?.constructor?.name || typeof exception, message: exception instanceof Error ? exception.message : 'Unknown error', stack: exception instanceof Error ? exception.stack : undefined, context: ctx, - } - - this.logger.error(errorDetails) + }) try { switch (ctx) { diff --git a/teammapper-backend/src/map/controllers/maps.controller.ts b/teammapper-backend/src/map/controllers/maps.controller.ts index 8dd0103a..4e746005 100644 --- a/teammapper-backend/src/map/controllers/maps.controller.ts +++ b/teammapper-backend/src/map/controllers/maps.controller.ts @@ -25,6 +25,7 @@ import { Request, } from '../types' import { MapCreateSchema, MapDeleteSchema } from '../schemas/maps.schema' +import { sanitizeIssues } from '../schemas/sanitize-issues' import MalformedUUIDError from '../services/uuid.error' import { EntityNotFoundError } from 'typeorm' @@ -83,7 +84,7 @@ export default class MapsController { ): Promise { const result = v.safeParse(MapDeleteSchema, body) if (!result.success) { - throw new BadRequestException(result.issues) + throw new BadRequestException(sanitizeIssues(result.issues)) } const mmpMap = await this.mapsService.findMap(mapId) if (mmpMap && mmpMap.adminId === result.output.adminId) { @@ -102,7 +103,7 @@ export default class MapsController { ): Promise { const result = v.safeParse(MapCreateSchema, body) if (!result.success) { - throw new BadRequestException(result.issues) + throw new BadRequestException(sanitizeIssues(result.issues)) } const pid = req?.pid diff --git a/teammapper-backend/src/map/controllers/mermaid.controller.ts b/teammapper-backend/src/map/controllers/mermaid.controller.ts index ef7d0a9e..ebb98d24 100644 --- a/teammapper-backend/src/map/controllers/mermaid.controller.ts +++ b/teammapper-backend/src/map/controllers/mermaid.controller.ts @@ -9,6 +9,7 @@ import * as v from 'valibot' import { AiService } from '../services/ai.service' import { RateLimitExceptionFilter } from './rate-limit-exception.filter' import { MermaidCreateSchema } from '../schemas/mermaid.schema' +import { sanitizeIssues } from '../schemas/sanitize-issues' @UseFilters(RateLimitExceptionFilter) @Controller('api/mermaid') @@ -19,7 +20,7 @@ export default class AiController { async createMermaid(@Body() body: unknown) { const result = v.safeParse(MermaidCreateSchema, body) if (!result.success) { - throw new BadRequestException(result.issues) + throw new BadRequestException(sanitizeIssues(result.issues)) } return this.aiService.generateMermaid( result.output.mindmapDescription, diff --git a/teammapper-backend/src/map/schemas/gateway.schema.ts b/teammapper-backend/src/map/schemas/gateway.schema.ts index 1294ec13..4b32a41e 100644 --- a/teammapper-backend/src/map/schemas/gateway.schema.ts +++ b/teammapper-backend/src/map/schemas/gateway.schema.ts @@ -1,8 +1,9 @@ import * as v from 'valibot' import { NodeSchema } from './node.schema' +import { sanitizeIssues } from './sanitize-issues' import type { Socket } from 'socket.io' -// --- Base schemas --- +// Base schemas export const JoinSchema = v.object({ mapId: v.pipe(v.string(), v.nonEmpty()), @@ -22,7 +23,7 @@ export const NodeSelectionSchema = v.object({ selected: v.boolean(), }) -// --- EditGuard-protected schemas --- +// EditGuard-protected schemas export const MapOptionsSchema = v.partial( v.object({ @@ -103,7 +104,7 @@ export const DeleteRequestSchema = v.object({ mapId: v.pipe(v.string(), v.nonEmpty()), }) -// --- Inferred types --- +// Inferred types export type IMmpClientJoinRequest = v.InferOutput export type IMmpClientEditingRequest = v.InferOutput< @@ -130,7 +131,7 @@ export type IMmpClientSnapshotChanges = v.InferOutput< > export type IMmpClientMapDiff = v.InferOutput -// --- Validation helper --- +// Validation helper export const validateWsPayload = ( client: Socket, @@ -141,7 +142,7 @@ export const validateWsPayload = ( if (!result.success) { client.emit('exception', { message: 'Invalid payload', - issues: result.issues, + issues: sanitizeIssues(result.issues), }) return null } diff --git a/teammapper-backend/src/map/schemas/sanitize-issues.spec.ts b/teammapper-backend/src/map/schemas/sanitize-issues.spec.ts new file mode 100644 index 00000000..eda177cb --- /dev/null +++ b/teammapper-backend/src/map/schemas/sanitize-issues.spec.ts @@ -0,0 +1,55 @@ +import * as v from 'valibot' +import { sanitizeIssues } from './sanitize-issues' + +const schema = v.object({ + modificationSecret: v.string(), + mapId: v.pipe(v.string(), v.uuid()), +}) + +const failureIssues = (input: unknown) => { + const result = v.safeParse(schema, input) + if (result.success) throw new Error('expected validation failure') + return result.issues +} + +describe('sanitizeIssues', () => { + it('produces only the allowlisted issue and path-item fields', () => { + const sanitized = sanitizeIssues( + failureIssues({ modificationSecret: 'secret', mapId: 'not-a-uuid' }) + ) + + expect(sanitized).toEqual([ + { + kind: 'validation', + type: 'uuid', + expected: null, + path: [{ type: 'object', origin: 'value', key: 'mapId' }], + }, + ]) + }) + + it('does not leak any submitted secret value into the serialized output', () => { + const sanitized = sanitizeIssues( + failureIssues({ + modificationSecret: 'super-secret-token', + mapId: 'leak-me-too', + }) + ) + + expect(JSON.stringify(sanitized)).toEqual( + expect.not.stringMatching(/super-secret-token|leak-me-too/) + ) + }) + + it('recurses into nested sub-issues without leaking input', () => { + const wrapped = v.object({ field: v.union([v.string(), v.number()]) }) + const result = v.safeParse(wrapped, { + field: { leakyKeyXyz: 'leakyValueXyz' }, + }) + if (result.success) throw new Error('expected validation failure') + + expect(JSON.stringify(sanitizeIssues(result.issues))).toEqual( + expect.not.stringMatching(/leakyKeyXyz|leakyValueXyz/) + ) + }) +}) diff --git a/teammapper-backend/src/map/schemas/sanitize-issues.ts b/teammapper-backend/src/map/schemas/sanitize-issues.ts new file mode 100644 index 00000000..eb9a1ac1 --- /dev/null +++ b/teammapper-backend/src/map/schemas/sanitize-issues.ts @@ -0,0 +1,49 @@ +import type { BaseIssue, IssuePathItem } from 'valibot' + +type AnyIssue = BaseIssue + +export interface SanitizedIssuePathItem { + type: IssuePathItem['type'] + origin: IssuePathItem['origin'] + key: IssuePathItem['key'] +} + +export interface SanitizedIssue { + kind: AnyIssue['kind'] + type: AnyIssue['type'] + expected: AnyIssue['expected'] + path?: SanitizedIssuePathItem[] + issues?: SanitizedIssue[] +} + +/** + * Strip raw user input from valibot issues so secrets bound on sibling fields + * (e.g. `adminId`, `modificationSecret`) cannot land in error responses or logs. + * Removes: + * - `input` and `received` on each issue (both can contain literal user values) + * - `message` (valibot bakes the received value into default messages, e.g. + * `Invalid UUID: Received ""`) + * - `requirement` (often a RegExp / unstructured constraint, not stable for clients) + * - `input` and `value` on each `path` item (echoes the surrounding object) + * Applied recursively to nested `issues`. Clients can render localized messages + * from `kind`, `type`, `expected`, and `path`. + */ +export const sanitizeIssues = (issues: readonly AnyIssue[]): SanitizedIssue[] => + issues.map(sanitizeIssue) + +const sanitizeIssue = (issue: AnyIssue): SanitizedIssue => { + const sanitized: SanitizedIssue = { + kind: issue.kind, + type: issue.type, + expected: issue.expected, + } + if (issue.path) sanitized.path = issue.path.map(sanitizePathItem) + if (issue.issues) sanitized.issues = issue.issues.map(sanitizeIssue) + return sanitized +} + +const sanitizePathItem = (item: IssuePathItem): SanitizedIssuePathItem => ({ + type: item.type, + origin: item.origin, + key: item.key, +}) From e29dd79a0cb681ff32f0fcaafbdc694921627e2d Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Mon, 11 May 2026 13:01:29 +0200 Subject: [PATCH 04/14] build(deps): patch pnpm audit vulnerabilities (#1286) Resolves 4 high and 8 moderate severity findings reported by `pnpm audit`: Direct dependency bumps (patch-level): - teammapper-frontend uuid 13.0.0 -> 13.0.1 (GHSA-w5hq-g745-h8pq) - teammapper-backend uuid 11.1.0 -> 11.1.1 (GHSA-w5hq-g745-h8pq) Transitive overrides (patch-level): - hono: <4.12.14 -> <4.12.18 (>=4.12.18) covers GHSA-9vqf-7f2p-gf9v, GHSA-69xw-7hcm-h432, GHSA-qp7p-654g-cw7p, GHSA-p77w-8qqv-26rm, GHSA-hm8q-7f3q-5f36 - fast-uri >=3.1.2 (GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc) - postcss >=8.5.10 (GHSA-qx2v-qp2m-jg93) - ip-address >=10.1.1 (GHSA-v2v4-37r5-5v8g) - @babel/plugin-transform-modules-systemjs >=7.29.4 (GHSA-fv7c-fp4j-7gwp) - uuid (transitive via @compodoc/compodoc) >=11.1.1 (GHSA-w5hq-g745-h8pq) `pnpm audit` now reports "No known vulnerabilities found". Frontend tests (163) pass; lint passes across all workspaces. Backend test failures observed locally are pre-existing infrastructure issues (missing POSTGRES_DATABASE env vars) and unrelated to these changes. --- package.json | 7 +- pnpm-lock.yaml | 236 ++++++++++++++++++------------- teammapper-backend/package.json | 2 +- teammapper-frontend/package.json | 2 +- 4 files changed, 148 insertions(+), 99 deletions(-) diff --git a/package.json b/package.json index 20763eb5..65056399 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,12 @@ "picomatch@>=4.0.0 <4.0.4": ">=4.0.4", "lodash@<4.18.0": ">=4.18.0", "follow-redirects@<1.16.0": ">=1.16.0", - "hono@<4.12.14": ">=4.12.14" + "hono@<4.12.18": ">=4.12.18", + "fast-uri@<3.1.2": ">=3.1.2", + "postcss@<8.5.10": ">=8.5.10", + "ip-address@<10.1.1": ">=10.1.1", + "@babel/plugin-transform-modules-systemjs@<7.29.4": ">=7.29.4", + "uuid@>=11.0.0 <11.1.1": ">=11.1.1" } } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 745fedf0..8fb3b5fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,12 @@ overrides: picomatch@>=4.0.0 <4.0.4: '>=4.0.4' lodash@<4.18.0: '>=4.18.0' follow-redirects@<1.16.0: '>=1.16.0' - hono@<4.12.14: '>=4.12.14' + hono@<4.12.18: '>=4.12.18' + fast-uri@<3.1.2: '>=3.1.2' + postcss@<8.5.10: '>=8.5.10' + ip-address@<10.1.1: '>=10.1.1' + '@babel/plugin-transform-modules-systemjs@<7.29.4': '>=7.29.4' + uuid@>=11.0.0 <11.1.1: '>=11.1.1' importers: @@ -133,8 +138,8 @@ importers: specifier: ^0.3.28 version: 0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) uuid: - specifier: 11.1.0 - version: 11.1.0 + specifier: 11.1.1 + version: 11.1.1 valibot: specifier: ^1.3.1 version: 1.3.1(typescript@5.9.3) @@ -300,7 +305,7 @@ importers: version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/material': specifier: 21.2.7 - version: 21.2.7(23fcsq5atbc2khgeermlooaagm) + version: 21.2.7(0d5a9f31d3db06fde122ff91a62de812) '@angular/platform-browser': specifier: 21.2.9 version: 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) @@ -374,8 +379,8 @@ importers: specifier: ^2.8.1 version: 2.8.1 uuid: - specifier: 13.0.0 - version: 13.0.0 + specifier: 13.0.1 + version: 13.0.1 y-websocket: specifier: ^3.0.0 version: 3.0.0(yjs@13.6.30) @@ -385,23 +390,10 @@ importers: zone.js: specifier: ^0.16.1 version: 0.16.1 - optionalDependencies: - '@nx/nx-darwin-arm64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-darwin-x64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-linux-x64-gnu': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-win32-x64-msvc': - specifier: 22.6.5 - version: 22.6.5 devDependencies: '@angular-builders/jest': specifier: ^21.0.3 - version: 21.0.3(k2djlcvkqxy6k6ldfsfrcs6zlu) + version: 21.0.3(07bd7b923bb6f14c0b013a3dc874ca68) '@angular-devkit/architect': specifier: 0.2102.5 version: 0.2102.5(chokidar@5.0.0) @@ -431,7 +423,7 @@ importers: version: 21.2.6 '@compodoc/compodoc': specifier: ^1.2.1 - version: 1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + version: 1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) '@eslint/js': specifier: ^10.0.1 version: 10.0.1(eslint@10.1.0(jiti@2.6.1)) @@ -488,7 +480,7 @@ importers: version: 30.3.0 jest-preset-angular: specifier: ^16.1.1 - version: 16.1.1(lhdkixmzgqfmespdgcicjoohiq) + version: 16.1.1(28bd700369ae87f04923e58e037d4bde) minimist: specifier: ^1.2.8 version: 1.2.8 @@ -504,6 +496,19 @@ importers: typescript-eslint: specifier: ^8.57.2 version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + optionalDependencies: + '@nx/nx-darwin-arm64': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-darwin-x64': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-linux-x64-gnu': + specifier: 22.6.5 + version: 22.6.5 + '@nx/nx-win32-x64-msvc': + specifier: 22.6.5 + version: 22.6.5 teammapper-frontend/packages/mermaid-mindmap-parser: dependencies: @@ -830,7 +835,7 @@ packages: karma: ^6.4.0 less: ^4.2.0 ng-packagr: ^21.0.0 - postcss: ^8.4.0 + postcss: '>=8.5.10' tailwindcss: ^2.0.0 || ^3.0.0 || ^4.0.0 tslib: ^2.3.0 typescript: '>=5.9 <6.0' @@ -1379,8 +1384,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.29.0': - resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -2062,7 +2067,7 @@ packages: resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.14' + hono: '>=4.12.18' '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -2609,42 +2614,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -2918,6 +2930,7 @@ packages: resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] + libc: [glibc] '@nx/nx-win32-x64-msvc@22.6.5': resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} @@ -2966,36 +2979,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -3173,60 +3192,70 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} @@ -3314,66 +3343,79 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -3952,41 +3994,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4305,7 +4355,7 @@ packages: engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} @@ -5601,8 +5651,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.2: + resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==} fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -5888,8 +5938,8 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hono@4.12.14: - resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} + hono@4.12.18: + resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} engines: {node: '>=16.9.0'} hookified@1.15.1: @@ -6009,7 +6059,7 @@ packages: resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -6076,8 +6126,8 @@ packages: iobuffer@5.4.0: resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} - ip-address@10.1.0: - resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} engines: {node: '>= 12'} ipaddr.js@1.9.1: @@ -6695,24 +6745,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -7514,7 +7568,7 @@ packages: engines: {node: '>= 18.12.0'} peerDependencies: '@rspack/core': 0.x || 1.x - postcss: ^7.0.0 || ^8.0.1 + postcss: '>=8.5.10' webpack: ^5.0.0 peerDependenciesMeta: '@rspack/core': @@ -7529,31 +7583,31 @@ packages: resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' postcss-modules-local-by-default@4.2.0: resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' postcss-modules-scope@3.2.1: resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' postcss-modules-values@4.0.0: resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: - postcss: ^8.1.0 + postcss: '>=8.5.10' postcss-safe-parser@7.0.1: resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} engines: {node: '>=18.0'} peerDependencies: - postcss: ^8.4.31 + postcss: '>=8.5.10' postcss-selector-parser@7.1.1: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} @@ -7566,10 +7620,6 @@ packages: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -8744,12 +8794,12 @@ packages: utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} hasBin: true - uuid@13.0.0: - resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} + uuid@13.0.1: + resolution: {integrity: sha512-9ezox2roIft6ExBVTVqibSd5dc5/47Sw/uY6b4SjQUT2TzQ0tltNquWA46y4xPQmdZYqvnio22SgWd41M86+jw==} hasBin: true uuid@8.3.2: @@ -8794,7 +8844,7 @@ packages: vis-data@8.0.3: resolution: {integrity: sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==} peerDependencies: - uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 + uuid: '>=11.1.1' vis-util: '>=6.0.0' vis-network@10.0.2: @@ -8803,7 +8853,7 @@ packages: '@egjs/hammerjs': ^2.0.0 component-emitter: ^1.3.0 || ^2.0.0 keycharm: ^0.2.0 || ^0.3.0 || ^0.4.0 - uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^13.0.0 + uuid: '>=11.1.1' vis-data: '>=8.0.0' vis-util: '>=6.0.0' @@ -9324,7 +9374,7 @@ snapshots: - chokidar - typescript - '@angular-builders/jest@21.0.3(k2djlcvkqxy6k6ldfsfrcs6zlu)': + '@angular-builders/jest@21.0.3(07bd7b923bb6f14c0b013a3dc874ca68)': dependencies: '@angular-builders/common': 5.0.3(@types/node@25.5.0)(chokidar@5.0.0)(typescript@5.9.3) '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) @@ -9334,7 +9384,7 @@ snapshots: '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-preset-angular: 16.1.1(lhdkixmzgqfmespdgcicjoohiq) + jest-preset-angular: 16.1.1(28bd700369ae87f04923e58e037d4bde) lodash: 4.18.1 transitivePeerDependencies: - '@angular/platform-browser' @@ -9370,7 +9420,7 @@ snapshots: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) '@angular-devkit/build-webpack': 0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3)) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) - '@angular/build': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) + '@angular/build': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.10)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -9384,7 +9434,7 @@ snapshots: '@discoveryjs/json-ext': 0.6.3 '@ngtools/webpack': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) ansi-colors: 4.1.3 - autoprefixer: 10.4.27(postcss@8.5.6) + autoprefixer: 10.4.27(postcss@8.5.10) babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)) browserslist: 4.28.2 copy-webpack-plugin: 14.0.0(webpack@5.105.2(esbuild@0.27.3)) @@ -9403,8 +9453,8 @@ snapshots: ora: 9.3.0 picomatch: 4.0.4 piscina: 5.1.4 - postcss: 8.5.6 - postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) + postcss: 8.5.10 + postcss-loader: 8.2.0(postcss@8.5.10)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 sass: 1.97.3 @@ -9649,7 +9699,7 @@ snapshots: '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) tslib: 2.8.1 - '@angular/build@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.6)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': + '@angular/build@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.10)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) @@ -9687,7 +9737,7 @@ snapshots: '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) less: 4.4.2 lmdb: 3.5.1 - postcss: 8.5.6 + postcss: 8.5.10 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9783,7 +9833,7 @@ snapshots: '@angular/language-service@21.2.6': {} - '@angular/material@21.2.7(23fcsq5atbc2khgeermlooaagm)': + '@angular/material@21.2.7(0d5a9f31d3db06fde122ff91a62de812)': dependencies: '@angular/cdk': 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) @@ -10599,7 +10649,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.28.6)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) @@ -10609,7 +10659,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) @@ -10982,7 +11032,7 @@ snapshots: '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.6) '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.6) '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.28.6) '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.6) '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.6) '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.6) @@ -11058,7 +11108,7 @@ snapshots: '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) @@ -11146,7 +11196,7 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@compodoc/compodoc@1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1))': + '@compodoc/compodoc@1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1))': dependencies: '@angular-devkit/schematics': 21.1.0(chokidar@5.0.0) '@babel/core': 7.28.6 @@ -11189,8 +11239,8 @@ snapshots: svg-pan-zoom: 3.6.2 tablesort: 5.7.0 ts-morph: 27.0.2 - uuid: 11.1.0 - vis-network: 10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@11.1.0)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + uuid: 13.0.1 + vis-network: 10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@13.0.1)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) transitivePeerDependencies: - '@egjs/hammerjs' - component-emitter @@ -11517,9 +11567,9 @@ snapshots: '@harperfast/extended-iterable@1.0.3': optional: true - '@hono/node-server@1.19.14(hono@4.12.14)': + '@hono/node-server@1.19.14(hono@4.12.18)': dependencies: - hono: 4.12.14 + hono: 4.12.18 '@humanfs/core@0.19.1': {} @@ -12078,7 +12128,7 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.14) + '@hono/node-server': 1.19.14(hono@4.12.18) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 @@ -12088,7 +12138,7 @@ snapshots: eventsource-parser: 3.0.6 express: 5.2.1 express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.12.14 + hono: 4.12.18 jose: 6.2.2 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -13384,7 +13434,7 @@ snapshots: '@types/uuid@11.0.0': dependencies: - uuid: 11.1.0 + uuid: 13.0.1 '@types/validator@13.15.10': {} @@ -13710,7 +13760,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.2 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -13880,13 +13930,13 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.27(postcss@8.5.6): + autoprefixer@10.4.27(postcss@8.5.10): dependencies: browserslist: 4.28.2 caniuse-lite: 1.0.30001787 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.6 + postcss: 8.5.10 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -15379,7 +15429,7 @@ snapshots: express-rate-limit@8.3.2(express@5.2.1): dependencies: express: 5.2.1 - ip-address: 10.1.0 + ip-address: 10.2.0 express@4.22.1: dependencies: @@ -15478,7 +15528,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.0: {} + fast-uri@3.1.2: {} fastq@1.20.1: dependencies: @@ -15801,7 +15851,7 @@ snapshots: dependencies: function-bind: 1.1.2 - hono@4.12.14: {} + hono@4.12.18: {} hookified@1.15.1: {} @@ -15995,7 +16045,7 @@ snapshots: iobuffer@5.4.0: {} - ip-address@10.1.0: {} + ip-address@10.2.0: {} ipaddr.js@1.9.1: {} @@ -16404,7 +16454,7 @@ snapshots: optionalDependencies: jest-resolve: 30.3.0 - jest-preset-angular@16.1.1(lhdkixmzgqfmespdgcicjoohiq): + jest-preset-angular@16.1.1(28bd700369ae87f04923e58e037d4bde): dependencies: '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) @@ -17673,11 +17723,11 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)): + postcss-loader@8.2.0(postcss@8.5.10)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: cosmiconfig: 9.0.1(typescript@5.9.3) jiti: 2.6.1 - postcss: 8.5.6 + postcss: 8.5.10 semver: 7.7.4 optionalDependencies: webpack: 5.105.2(esbuild@0.27.3) @@ -17724,12 +17774,6 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postgres-array@2.0.0: {} postgres-bytea@1.0.1: {} @@ -18442,7 +18486,7 @@ snapshots: socks@2.8.7: dependencies: - ip-address: 10.1.0 + ip-address: 10.2.0 smart-buffer: 4.2.0 source-map-js@1.2.1: {} @@ -18995,7 +19039,7 @@ snapshots: sha.js: 2.4.12 sql-highlight: 6.1.0 tslib: 2.8.1 - uuid: 11.1.0 + uuid: 11.1.1 yargs: 17.7.2 optionalDependencies: pg: 8.20.0 @@ -19106,9 +19150,9 @@ snapshots: base64-arraybuffer: 1.0.2 optional: true - uuid@11.1.0: {} + uuid@11.1.1: {} - uuid@13.0.0: {} + uuid@13.0.1: {} uuid@8.3.2: {} @@ -19137,18 +19181,18 @@ snapshots: version-range@4.15.0: {} - vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): + vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): dependencies: - uuid: 13.0.0 + uuid: 13.0.1 vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1) - vis-network@10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@11.1.0)(vis-data@8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): + vis-network@10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@13.0.1)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): dependencies: '@egjs/hammerjs': 2.0.17 component-emitter: 1.3.1 keycharm: 0.2.0 - uuid: 11.1.0 - vis-data: 8.0.3(uuid@13.0.0)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + uuid: 13.0.1 + vis-data: 8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1) vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1): diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index 3965fd8b..ae785b69 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -63,7 +63,7 @@ "sanitize-html": "^2.17.3", "socket.io": "4.8.3", "typeorm": "^0.3.28", - "uuid": "11.1.0", + "uuid": "11.1.1", "valibot": "^1.3.1", "ws": "^8.20.0", "y-protocols": "^1.0.7", diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index 204d2383..1f753bbf 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -75,7 +75,7 @@ "rxjs": "~7.8.2", "socket.io-client": "~4.8.3", "tslib": "^2.8.1", - "uuid": "13.0.0", + "uuid": "13.0.1", "y-websocket": "^3.0.0", "yjs": "^13.6.30", "zone.js": "^0.16.1" From 2b1c93810f85ed919eb4c09f6bb5534703875354 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 16:17:00 +0200 Subject: [PATCH 05/14] build(deps): bump sanitize-html from 2.17.3 to 2.17.4 (#1291) Bumps [sanitize-html](https://github.com/apostrophecms/apostrophe/tree/HEAD/packages/sanitize-html) from 2.17.3 to 2.17.4. - [Changelog](https://github.com/apostrophecms/apostrophe/blob/main/packages/sanitize-html/CHANGELOG.md) - [Commits](https://github.com/apostrophecms/apostrophe/commits/HEAD/packages/sanitize-html) --- updated-dependencies: - dependency-name: sanitize-html dependency-version: 2.17.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 134 +++++++++++++------------------- teammapper-backend/package.json | 2 +- 2 files changed, 56 insertions(+), 80 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fb3b5fc..0a22cc7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,8 +129,8 @@ importers: specifier: ^7.8.2 version: 7.8.2 sanitize-html: - specifier: ^2.17.3 - version: 2.17.3 + specifier: ^2.17.4 + version: 2.17.4 socket.io: specifier: 4.8.3 version: 4.8.3 @@ -2614,49 +2614,42 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -2930,7 +2923,6 @@ packages: resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] - libc: [glibc] '@nx/nx-win32-x64-msvc@22.6.5': resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} @@ -2979,42 +2971,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -3192,70 +3178,60 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} @@ -3343,79 +3319,66 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -3994,49 +3957,41 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] - libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] - libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] - libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -6662,6 +6617,9 @@ packages: launch-editor@2.13.2: resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} + launder@1.7.1: + resolution: {integrity: sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==} + less-loader@12.3.1: resolution: {integrity: sha512-JZZmG7gMzoDP3VGeEG8Sh6FW5wygB5jYL7Wp29FFihuRTsIBacqO3LbRPr2yStYD11riVf13selLm/CPFRDBRQ==} engines: {node: '>= 18.12.0'} @@ -6745,28 +6703,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -7115,6 +7069,11 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -7620,6 +7579,10 @@ packages: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -7946,8 +7909,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-html@2.17.3: - resolution: {integrity: sha512-Kn4srCAo2+wZyvCNKCSyB2g8RQ8IkX/gQs2uqoSRNu5t9I2qvUyAVvRDiFUVAiX3N3PNuwStY0eNr+ooBHVWEg==} + sanitize-html@2.17.4: + resolution: {integrity: sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==} sass-loader@16.0.7: resolution: {integrity: sha512-w6q+fRHourZ+e+xA1kcsF27iGM6jdB8teexYCfdUw0sYgcDNeZESnDNT9sUmmPm3ooziwUJXGwZJSTF3kOdBfA==} @@ -14088,9 +14051,9 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.1.0 picocolors: 1.1.1 - postcss: 8.5.10 + postcss: 8.5.15 postcss-media-query-parser: 0.2.3 - postcss-safe-parser: 7.0.1(postcss@8.5.10) + postcss-safe-parser: 7.0.1(postcss@8.5.15) big.js@5.2.2: {} @@ -14566,12 +14529,12 @@ snapshots: css-loader@7.1.3(webpack@5.105.2(esbuild@0.27.3)): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.10) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.10) - postcss-modules-scope: 3.2.1(postcss@8.5.10) - postcss-modules-values: 4.0.0(postcss@8.5.10) + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) + postcss-modules-scope: 3.2.1(postcss@8.5.15) + postcss-modules-values: 4.0.0(postcss@8.5.15) postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: @@ -15991,9 +15954,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.10): + icss-utils@5.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 ieee754@1.2.1: {} @@ -16787,6 +16750,10 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.8.3 + launder@1.7.1: + dependencies: + dayjs: 1.11.20 + less-loader@12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)): dependencies: less: 4.4.2 @@ -17220,6 +17187,8 @@ snapshots: nanoid@3.3.11: {} + nanoid@3.3.12: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -17736,30 +17705,30 @@ snapshots: postcss-media-query-parser@0.2.3: {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.10): + postcss-modules-extract-imports@3.1.0(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 - postcss-modules-local-by-default@4.2.0(postcss@8.5.10): + postcss-modules-local-by-default@4.2.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.10): + postcss-modules-scope@3.2.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.10): + postcss-modules-values@4.0.0(postcss@8.5.15): dependencies: - icss-utils: 5.1.0(postcss@8.5.10) - postcss: 8.5.10 + icss-utils: 5.1.0(postcss@8.5.15) + postcss: 8.5.15 - postcss-safe-parser@7.0.1(postcss@8.5.10): + postcss-safe-parser@7.0.1(postcss@8.5.15): dependencies: - postcss: 8.5.10 + postcss: 8.5.15 postcss-selector-parser@7.1.1: dependencies: @@ -17774,6 +17743,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postgres-array@2.0.0: {} postgres-bytea@1.0.1: {} @@ -18008,7 +17983,7 @@ snapshots: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.10 + postcss: 8.5.15 source-map: 0.6.1 resolve@1.22.11: @@ -18179,14 +18154,15 @@ snapshots: safer-buffer@2.1.2: {} - sanitize-html@2.17.3: + sanitize-html@2.17.4: dependencies: deepmerge: 4.3.1 escape-string-regexp: 4.0.0 htmlparser2: 10.1.0 is-plain-object: 5.0.0 + launder: 1.7.1 parse-srcset: 1.0.2 - postcss: 8.5.10 + postcss: 8.5.15 sass-loader@16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)): dependencies: @@ -19205,7 +19181,7 @@ snapshots: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.10 + postcss: 8.5.15 rollup: 4.60.1 tinyglobby: 0.2.16 optionalDependencies: diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index ae785b69..457c2142 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -60,7 +60,7 @@ "reflect-metadata": "^0.2.2", "rimraf": "^6.1.3", "rxjs": "^7.8.2", - "sanitize-html": "^2.17.3", + "sanitize-html": "^2.17.4", "socket.io": "4.8.3", "typeorm": "^0.3.28", "uuid": "11.1.1", From 7e7872969e2b56420d65abedd7d7728802951bca Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sat, 23 May 2026 21:14:28 +0200 Subject: [PATCH 06/14] Refactor docker file (#1295) --- .github/workflows/ci.yml | 29 ++++++++--------- .github/workflows/playwright.yml | 4 +-- Dockerfile | 53 +++++++++++++++++++++++--------- entrypoint.prod.sh | 5 +-- package.json | 2 +- teammapper-backend/package.json | 6 +++- teammapper-frontend/package.json | 2 +- 7 files changed, 65 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6f248c1..d71b610e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,15 +36,16 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - name: Build and export to Docker uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . + target: production tags: | ghcr.io/b310-digital/teammapper:latest @@ -56,10 +57,10 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run lint @@ -72,10 +73,10 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-frontend run lint @@ -88,10 +89,10 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build @@ -105,10 +106,10 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run tsc @@ -134,10 +135,10 @@ jobs: - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run test @@ -162,10 +163,10 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 7f78a129..b9e21592 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -29,9 +29,9 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: - node-version: '25' + node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@9 --activate + - run: corepack prepare pnpm@10 --activate - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build packages diff --git a/Dockerfile b/Dockerfile index 6fb45656..575625d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,21 +1,27 @@ -FROM node:25-alpine3.22 AS base +ARG NODE_VERSION=24 +ARG ALPINE_VERSION=3.21 +ARG PNPM_VERSION=10.33.4 -RUN apk add --no-cache postgresql-client make g++ python3 py3-pip curl && npm install -g pnpm - -# Ensuring that all npm packages and commands are executed with a non-root user -USER node +FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS base ENV APP_PATH=/home/node/app -ENV APP_BACKEND_PATH=${APP_PATH}/teammapper-backend -ENV APP_FRONTEND_PATH=${APP_PATH}/teammapper-frontend - -RUN mkdir -p $APP_PATH WORKDIR $APP_PATH -FROM base AS production +RUN corepack enable \ + && chown node:node $APP_PATH + USER node -COPY --chown=node:node package.json pnpm-workspace.yaml pnpm-lock.yaml $APP_PATH/ +ARG PNPM_VERSION +RUN corepack prepare pnpm@${PNPM_VERSION} --activate + + +FROM base AS builder + +ENV APP_BACKEND_PATH=${APP_PATH}/teammapper-backend +ENV APP_FRONTEND_PATH=${APP_PATH}/teammapper-frontend + +COPY --chown=node:node package.json pnpm-workspace.yaml pnpm-lock.yaml ./ COPY --chown=node:node teammapper-backend/package.json $APP_BACKEND_PATH/ COPY --chown=node:node teammapper-frontend/package.json $APP_FRONTEND_PATH/ COPY --chown=node:node teammapper-frontend/packages $APP_FRONTEND_PATH/packages @@ -25,12 +31,29 @@ COPY --chown=node:node teammapper-backend $APP_BACKEND_PATH/ RUN pnpm --filter teammapper-backend run build COPY --chown=node:node teammapper-frontend $APP_FRONTEND_PATH/ -RUN pnpm --filter @teammapper/mermaid-mindmap-parser run build && GENERATE_SOURCEMAP=false pnpm --filter teammapper-frontend run build:prod +RUN pnpm --filter @teammapper/mermaid-mindmap-parser run build \ + && GENERATE_SOURCEMAP=false pnpm --filter teammapper-frontend run build:prod \ + && mv $APP_FRONTEND_PATH/dist $APP_BACKEND_PATH/client -RUN mv $APP_FRONTEND_PATH/dist $APP_BACKEND_PATH/client +RUN pnpm --filter teammapper-backend deploy --prod --legacy /home/node/deploy -COPY --chown=node:node entrypoint.prod.sh $APP_PATH/ -CMD ["./entrypoint.prod.sh"] FROM base AS development + + +FROM base AS production + +USER root +RUN apk add --no-cache tini postgresql-client USER node + +ENV NODE_ENV=production +ENV APP_BACKEND_PATH=${APP_PATH}/teammapper-backend +WORKDIR $APP_BACKEND_PATH + +COPY --from=builder --chown=node:node /home/node/deploy/ ./ +COPY --chown=node:node teammapper-backend/config ./config +COPY --chown=node:node --chmod=755 entrypoint.prod.sh ./ + +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["/home/node/app/teammapper-backend/entrypoint.prod.sh"] diff --git a/entrypoint.prod.sh b/entrypoint.prod.sh index 32267479..9220ef4c 100755 --- a/entrypoint.prod.sh +++ b/entrypoint.prod.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e echo "Looking for the database ..." while ! pg_isready -q -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER @@ -9,5 +10,5 @@ done echo "Found database." echo "Starting the application..." -pnpm run migrate:prod -pnpm run start:prod \ No newline at end of file +pnpm run prod:typeorm:migrate +exec pnpm run start:prod diff --git a/package.json b/package.json index 65056399..c53440cf 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "license": "MIT", "engines": { "npm": "~10.9.0", - "node": "~25.0" + "node": "^24.0" }, "scripts": { "dev": "concurrently \"pnpm --filter teammapper-backend start:dev\" \"pnpm --filter teammapper-frontend start\"", diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index 457c2142..8b502db1 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -5,9 +5,13 @@ "author": "b310 digital gmbh", "private": true, "license": "MIT", + "files": [ + "dist", + "client" + ], "engines": { "npm": "~10.9.0", - "node": "~25.0.0" + "node": "^24.0" }, "scripts": { "prebuild": "rimraf dist", diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index 1f753bbf..6dc71550 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -15,7 +15,7 @@ "license": "MIT", "engines": { "npm": "~10.9.0", - "node": "~25.0.0" + "node": "^24.0" }, "keywords": [ "mindmap", From d264be7f6c91ba802ea60f78b7c633e684538b7b Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 24 May 2026 07:49:41 +0200 Subject: [PATCH 07/14] refactor pnpm security and dependabot policies (#1296) --- .github/dependabot.yml | 59 +- .github/workflows/ci.yml | 46 +- .github/workflows/playwright.yml | 3 +- docker-compose.yml | 4 +- docs/pnpm-security.md | 36 + package.json | 20 - pnpm-lock.yaml | 4189 +++++++++-------- pnpm-workspace.yaml | 17 + teammapper-backend/package.json | 5 - .../node-after-drag-webkit-linux.png | Bin 36493 -> 36665 bytes .../node-before-drag-webkit-linux.png | Bin 33692 -> 33825 bytes teammapper-frontend/package.json | 14 +- 12 files changed, 2388 insertions(+), 2005 deletions(-) create mode 100644 docs/pnpm-security.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d8262b0f..7b269fc1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,15 +1,27 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# Dependabot config — see docs/pnpm-security.md for the full policy. +# Reference: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values + - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Europe/Berlin" open-pull-requests-limit: 15 + labels: + - "dependencies" + - "npm" + commit-message: + prefix: "chore(deps)" + prefix-development: "chore(deps-dev)" + include: "scope" + # 7-day cooldown mirrors pnpm-workspace.yaml minimumReleaseAge: 10080. + # Security updates ignore cooldown (Dependabot rule). + cooldown: + default-days: 7 groups: production-dependencies: dependency-type: "production" @@ -17,11 +29,40 @@ updates: development-dependencies: dependency-type: "development" update-types: ["minor", "patch"] - - package-ecosystem: "docker" # See documentation for possible values - directory: "/" # Location of package manifests + + - package-ecosystem: "docker" + directory: "/" schedule: interval: "weekly" - - package-ecosystem: "github-actions" # See documentation for possible values - directory: "/" # Location of package manifests + day: "monday" + time: "08:00" + timezone: "Europe/Berlin" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore(docker)" + include: "scope" + cooldown: + default-days: 7 + # Node base image is bumped manually on the Node LTS cycle. + ignore: + - dependency-name: "node" + + - package-ecosystem: "github-actions" + directory: "/" schedule: interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Europe/Berlin" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "chore(actions)" + include: "scope" + cooldown: + default-days: 7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d71b610e..dd465c1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - name: Build and export to Docker uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 @@ -60,7 +60,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run lint @@ -76,7 +76,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-frontend run lint @@ -92,7 +92,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build @@ -109,7 +109,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run tsc @@ -138,7 +138,7 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter teammapper-backend run test @@ -166,8 +166,38 @@ jobs: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - run: pnpm install --frozen-lockfile - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build - - run: pnpm --filter teammapper-frontend run test \ No newline at end of file + - run: pnpm --filter teammapper-frontend run test + + teammapper-audit: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version: '24' + + - run: corepack enable + - run: corepack prepare pnpm@10.33.4 --activate + + - run: pnpm install --frozen-lockfile + - name: pnpm audit (fail on high/critical production deps) + run: pnpm audit --audit-level=high --prod + + dependency-review: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Dependency Review (PR diff vs base) + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: high + allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Zlib, CC-BY-4.0 \ No newline at end of file diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index b9e21592..4ddacb9c 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,7 +1,6 @@ name: Playwright Tests permissions: contents: read - actions: write on: pull_request: branches: [main] @@ -31,7 +30,7 @@ jobs: with: node-version: '24' - run: corepack enable - - run: corepack prepare pnpm@10 --activate + - run: corepack prepare pnpm@10.33.4 --activate - name: Install dependencies run: pnpm install --frozen-lockfile - name: Build packages diff --git a/docker-compose.yml b/docker-compose.yml index 4057f195..1b96afb8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,13 +64,13 @@ services: - postgres_data:/var/lib/postgresql/data/pgdata playwright: - image: mcr.microsoft.com/playwright:v1.58.2-noble + image: mcr.microsoft.com/playwright:v1.60.0-noble container_name: playwright depends_on: - app expose: - "9323" - command: ["npx", "playwright", "run-server", "--port=9323"] + command: ["npx", "playwright", "run-server", "--port=9323", "--host=0.0.0.0"] chrome: image: chromedp/headless-shell:latest diff --git a/docs/pnpm-security.md b/docs/pnpm-security.md new file mode 100644 index 00000000..db500cd7 --- /dev/null +++ b/docs/pnpm-security.md @@ -0,0 +1,36 @@ +# pnpm Security Policy + +This repo applies a subset of the [bodadotsh npm security best practices](https://github.com/bodadotsh/npm-security-best-practices) that fit a private-app monorepo distributed as a Docker image. + +## What's enforced + +| Practice | Where | Why | +|----------|-------|-----| +| Exact-version saves | `.npmrc` (`save-exact=true`) | New `pnpm add` calls write exact versions, keeping the manifest aligned with what the lockfile already pins. | +| Lifecycle scripts gated by allowlist | `pnpm-workspace.yaml` (`allowBuilds:`) | pnpm 10 disables install scripts by default for transitive deps. `allowBuilds` is the explicit allowlist for the few packages (currently `@parcel/watcher` and `esbuild`) whose native builds we trust. | +| 7-day quarantine on new versions | `pnpm-workspace.yaml` (`minimumReleaseAge: 10080`) | New package versions are held back from installation for 7 days, giving the ecosystem time to revoke compromised releases. If a specific package needs an exemption, add it to `minimumReleaseAgeExclude:`. | +| Frozen-lockfile installs | All CI workflows, the Dockerfile, and the `teammapper-frontend` `build:packages` script | Keeps the lockfile authoritative across every install path. | +| pnpm patch version pinned in CI | `corepack prepare pnpm@10.33.4 --activate` in all CI jobs | Matches the Dockerfile's `PNPM_VERSION` so CI and production builds use the same pnpm. | +| `pnpm audit` in CI | `teammapper-audit` job in `ci.yml` | Fails CI on high or critical advisories in production dependencies. | +| Transitive-dep CVE pins | `overrides:` in `pnpm-workspace.yaml` | Forces minimum versions of known-vulnerable transitive deps. Audit periodically with `pnpm why ` and retire entries once the tree resolves above the vulnerable range. | +| Action pinning | All `uses:` lines in `.github/workflows/` carry a 40-char SHA | Protects against tag-rewrite supply-chain attacks. | +| Least-privilege workflow permissions | Workflow-level `permissions: contents: read`, with per-job overrides only where a job needs more | CI runs with `secrets.GITHUB_TOKEN` scoped to the minimum each job requires. | +| Dependabot 7-day cooldown | `cooldown:` block in `.github/dependabot.yml` (each ecosystem) | Holds version-update PRs for 7 days after a release, mirroring `minimumReleaseAge: 10080`. Security updates are exempt from cooldown so CVE fixes still flow promptly. | +| Node base image bumped manually | `ignore: dependency-name: "node"` in `.github/dependabot.yml` | Dependabot does not open PRs for the Node base image; bumps follow the Node LTS release cycle and are applied by hand. | +| Dependency review on every PR | `dependency-review` job in `ci.yml` (`actions/dependency-review-action`) | Fails the PR if it introduces a new dep with a high/critical advisory or a license outside the allow-list (MIT, Apache-2.0, BSD-2/3-Clause, ISC, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Zlib, CC-BY-4.0). Complements `pnpm audit`, which only checks the installed tree. | + +## Running checks locally + +```bash +pnpm install --frozen-lockfile +pnpm audit --audit-level=high --prod # match the CI gate +pnpm run lint && pnpm run tsc && pnpm run test +``` + +## Adding a new override + +1. Confirm the CVE via `pnpm audit` or the advisory link. +2. Identify the affected version range, e.g. `lodash@<4.18.0`. +3. Add the entry to `overrides:` in `pnpm-workspace.yaml` with the patched version, e.g. `'lodash@<4.18.0': '>=4.18.0'`. +4. Run `pnpm install` to refresh the lockfile, then commit both files. +5. Once `pnpm why ` shows the tree resolves above the vulnerable range, retire the override. diff --git a/package.json b/package.json index c53440cf..067cfd09 100644 --- a/package.json +++ b/package.json @@ -38,25 +38,5 @@ "prettier": "^3.8.1", "secretlint": "^11.4.1", "typescript": "~5.9.3" - }, - "pnpm": { - "overrides": { - "multer@<2.1.1": "2.1.1", - "serialize-javascript@<7.0.3": "7.0.4", - "ajv@>=7.0.0-alpha.0 <8.18.0": "8.18.0", - "underscore@<=1.13.7": "1.13.8", - "undici@>=7.0.0 <7.24.0": "7.24.0", - "file-type@>=13.0.0 <=21.3.1": "21.3.2", - "socket.io-parser@>=4.0.0 <4.2.6": "4.2.6", - "picomatch@>=4.0.0 <4.0.4": ">=4.0.4", - "lodash@<4.18.0": ">=4.18.0", - "follow-redirects@<1.16.0": ">=1.16.0", - "hono@<4.12.18": ">=4.12.18", - "fast-uri@<3.1.2": ">=3.1.2", - "postcss@<8.5.10": ">=8.5.10", - "ip-address@<10.1.1": ">=10.1.1", - "@babel/plugin-transform-modules-systemjs@<7.29.4": ">=7.29.4", - "uuid@>=11.0.0 <11.1.1": ">=11.1.1" - } } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0a22cc7e..bf3b31d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,11 +6,8 @@ settings: overrides: multer@<2.1.1: 2.1.1 - serialize-javascript@<7.0.3: 7.0.4 ajv@>=7.0.0-alpha.0 <8.18.0: 8.18.0 underscore@<=1.13.7: 1.13.8 - undici@>=7.0.0 <7.24.0: 7.24.0 - file-type@>=13.0.0 <=21.3.1: 21.3.2 socket.io-parser@>=4.0.0 <4.2.6: 4.2.6 picomatch@>=4.0.0 <4.0.4: '>=4.0.4' lodash@<4.18.0: '>=4.18.0' @@ -18,7 +15,6 @@ overrides: hono@<4.12.18: '>=4.12.18' fast-uri@<3.1.2: '>=3.1.2' postcss@<8.5.10: '>=8.5.10' - ip-address@<10.1.1: '>=10.1.1' '@babel/plugin-transform-modules-systemjs@<7.29.4': '>=7.29.4' uuid@>=11.0.0 <11.1.1: '>=11.1.1' @@ -28,19 +24,19 @@ importers: devDependencies: '@secretlint/secretlint-rule-preset-recommend': specifier: ^11.4.1 - version: 11.4.1 + version: 11.7.1 concurrently: specifier: 9.2.1 version: 9.2.1 eslint: specifier: ^10.1.0 - version: 10.1.0(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) prettier: specifier: ^3.8.1 - version: 3.8.1 + version: 3.8.3 secretlint: specifier: ^11.4.1 - version: 11.4.1 + version: 11.7.1 typescript: specifier: ~5.9.3 version: 5.9.3 @@ -55,37 +51,37 @@ importers: version: 2.0.41(zod@4.3.6) '@nestjs/cache-manager': specifier: ^3.1.0 - version: 3.1.0(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) + version: 3.1.2(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) '@nestjs/cli': specifier: ^11.0.21 - version: 11.0.21(@types/node@25.5.0) + version: 11.0.21(@types/node@25.8.0)(prettier@3.8.3) '@nestjs/common': specifier: ^11.1.19 - version: 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/config': specifier: 4.0.4 - version: 4.0.4(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + version: 4.0.4(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/platform-socket.io': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.1.3 - version: 6.1.3(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + version: 6.1.3(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) '@nestjs/serve-static': specifier: ^5.0.5 - version: 5.0.5(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1) + version: 5.0.5(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(express@5.2.1) '@nestjs/typeorm': specifier: ^11.0.1 - version: 11.0.1(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))) + version: 11.0.1(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3))) '@nestjs/websockets': specifier: ^11.1.19 - version: 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/uuid': specifier: ^11.0.0 version: 11.0.0 @@ -116,9 +112,6 @@ importers: pg: specifier: ^8.20.0 version: 8.20.0 - pq: - specifier: ^0.0.3 - version: 0.0.3 reflect-metadata: specifier: ^0.2.2 version: 0.2.2 @@ -136,47 +129,41 @@ importers: version: 4.8.3 typeorm: specifier: ^0.3.28 - version: 0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + version: 0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) uuid: specifier: 11.1.1 version: 11.1.1 valibot: specifier: ^1.3.1 - version: 1.3.1(typescript@5.9.3) + version: 1.4.0(typescript@5.9.3) ws: specifier: ^8.20.0 - version: 8.20.0 + version: 8.20.1 y-protocols: specifier: ^1.0.7 version: 1.0.7(yjs@13.6.30) - y-websocket: - specifier: ^3.0.0 - version: 3.0.0(yjs@13.6.30) yjs: specifier: ^13.6.30 version: 13.6.30 devDependencies: - '@eslint/compat': - specifier: ^2.0.3 - version: 2.0.3(eslint@10.1.0(jiti@2.6.1)) '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.1.0(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@golevelup/ts-jest': specifier: ^3.0.0 version: 3.0.0 '@jest/globals': specifier: ^30.3.0 - version: 30.3.0 + version: 30.4.1 '@nestjs/schematics': specifier: ^11.0.9 - version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) + version: 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^11.1.17 - version: 11.1.17(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21) '@stylistic/eslint-plugin': specifier: ^5.10.0 - version: 5.10.0(eslint@10.1.0(jiti@2.6.1)) + version: 5.10.0(eslint@10.4.0(jiti@2.7.0)) '@types/cache-manager': specifier: 5.0.0 version: 5.0.0 @@ -200,7 +187,7 @@ importers: version: 9.0.10 '@types/node': specifier: ^25.5.0 - version: 25.5.0 + version: 25.8.0 '@types/sanitize-html': specifier: ^2.16.1 version: 2.16.1 @@ -212,43 +199,37 @@ importers: version: 8.18.1 '@typescript-eslint/eslint-plugin': specifier: ^8.57.2 - version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.57.2 - version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) eslint: specifier: ^10.1.0 - version: 10.1.0(jiti@2.6.1) + version: 10.4.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.1.0(jiti@2.6.1)) + version: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-import-resolver-typescript: specifier: 4.4.4 - version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@10.1.0(jiti@2.6.1)) + version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.1.0(jiti@2.6.1)) + version: 2.32.0(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) eslint-plugin-jest: specifier: 29.15.1 - version: 29.15.1(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3) - eslint-plugin-nestjs: - specifier: 1.2.3 - version: 1.2.3 + version: 29.15.1(@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(typescript@5.9.3) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) - eslint-plugin-typeorm: - specifier: 0.0.19 - version: 0.0.19 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) globals: specifier: ^17.4.0 - version: 17.4.0 + version: 17.6.0 jest: specifier: 30.3.0 - version: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + version: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) prettier: specifier: ^3.8.1 - version: 3.8.1 + version: 3.8.3 socket.io-client: specifier: ^4.8.3 version: 4.8.3 @@ -257,13 +238,13 @@ importers: version: 7.2.2 ts-jest: specifier: 29.4.6 - version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.27.4)(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.28.0)(jest-util@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.4 - version: 9.5.4(typescript@5.9.3)(webpack@5.106.0) + version: 9.5.7(typescript@5.9.3)(webpack@5.106.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@25.5.0)(typescript@5.9.3) + version: 10.9.2(@types/node@25.8.0)(typescript@5.9.3) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -272,25 +253,25 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.57.2 - version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) teammapper-frontend: dependencies: '@angular-devkit/build-angular': specifier: 21.2.7 - version: 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) + version: 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.8.0)(chokidar@5.0.0)(jest-environment-jsdom@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(jiti@2.7.0)(lightningcss@1.32.0)(typescript@5.9.3) '@angular/animations': specifier: 21.2.9 - version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/cdk': specifier: 21.2.7 - version: 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + version: 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/cli': specifier: 21.2.7 - version: 21.2.7(@types/node@25.5.0)(chokidar@5.0.0) + version: 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) '@angular/common': specifier: 21.2.9 - version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + version: 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@angular/compiler': specifier: 21.2.9 version: 21.2.9 @@ -299,25 +280,25 @@ importers: version: 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@angular/core': specifier: 21.2.9 - version: 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + version: 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) '@angular/forms': specifier: 21.2.9 - version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@angular/material': specifier: 21.2.7 - version: 21.2.7(0d5a9f31d3db06fde122ff91a62de812) + version: 21.2.7(9b1d9887e76b4bf579d2fa4e777cf88c) '@angular/platform-browser': specifier: 21.2.9 - version: 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@angular/platform-browser-dynamic': specifier: 21.2.9 - version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))) '@angular/router': specifier: 21.2.9 - version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + version: 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) '@fortawesome/angular-fontawesome': specifier: ^4.0.0 - version: 4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@fortawesome/fontawesome-svg-core': specifier: ^7.2.0 version: 7.2.0 @@ -332,16 +313,13 @@ importers: version: 0.14.15 '@ngx-translate/core': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@ngx-translate/http-loader': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + version: 17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@teammapper/mermaid-mindmap-parser': specifier: workspace:^ version: link:packages/mermaid-mindmap-parser - ai: - specifier: ^6.0.168 - version: 6.0.168(zod@4.3.6) angular2-hotkeys: specifier: ^16.0.1 version: 16.0.1 @@ -362,10 +340,10 @@ importers: version: 1.10.0 ngx-color-picker: specifier: ^20.1.1 - version: 20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)) + version: 20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)) ngx-toastr: specifier: ^20.0.5 - version: 20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + version: 20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) qr-code-styling: specifier: 1.9.2 version: 1.9.2 @@ -389,11 +367,11 @@ importers: version: 13.6.30 zone.js: specifier: ^0.16.1 - version: 0.16.1 + version: 0.16.2 devDependencies: '@angular-builders/jest': specifier: ^21.0.3 - version: 21.0.3(07bd7b923bb6f14c0b013a3dc874ca68) + version: 21.0.3(ee59e09cf9085142f51ff0bc4edd671f) '@angular-devkit/architect': specifier: 0.2102.5 version: 0.2102.5(chokidar@5.0.0) @@ -405,34 +383,34 @@ importers: version: 21.2.5(chokidar@5.0.0) '@angular-eslint/builder': specifier: 21.3.1 - version: 21.3.1(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 21.3.1(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@angular-eslint/eslint-plugin': specifier: 21.3.1 - version: 21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@angular-eslint/eslint-plugin-template': specifier: 21.3.1 - version: 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@angular-eslint/schematics': specifier: 21.3.1 - version: 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@angular-eslint/template-parser': specifier: 21.3.1 - version: 21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@angular/language-service': specifier: 21.2.6 version: 21.2.6 '@compodoc/compodoc': specifier: ^1.2.1 - version: 1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + version: 1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.4.0)(typescript@5.9.3)(vis-data@8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.1.0(jiti@2.6.1)) + version: 10.0.1(eslint@10.4.0(jiti@2.7.0)) '@playwright/test': - specifier: ^1.58.2 - version: 1.58.2 + specifier: ^1.60.0 + version: 1.60.0 '@schematics/angular': specifier: ^21.2.5 - version: 21.2.5(chokidar@5.0.0) + version: 21.2.11(chokidar@5.0.0) '@types/d3': specifier: 7.4.3 version: 7.4.3 @@ -444,71 +422,49 @@ importers: version: 1.6.15 '@types/node': specifier: ^25.5.0 - version: 25.5.0 + version: 25.8.0 '@typescript-eslint/eslint-plugin': specifier: ^8.57.2 - version: 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.57.2 - version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + version: 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) angular-eslint: specifier: ^21.3.1 - version: 21.3.1(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript-eslint@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3) + version: 21.4.0(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript-eslint@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(typescript@5.9.3) eslint: specifier: ^10.1.0 - version: 10.1.0(jiti@2.6.1) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@10.1.0(jiti@2.6.1)) - eslint-plugin-jest: - specifier: ^29.15.1 - version: 29.15.1(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3) + version: 10.4.0(jiti@2.7.0) eslint-plugin-prettier: specifier: ^5.5.5 - version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1) + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3) globals: specifier: ^17.4.0 - version: 17.4.0 + version: 17.6.0 jest: specifier: ^30.3.0 - version: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + version: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) jest-canvas-mock: specifier: ^2.5.2 version: 2.5.2 jest-environment-jsdom: specifier: ^30.3.0 - version: 30.3.0 + version: 30.4.1 jest-preset-angular: specifier: ^16.1.1 - version: 16.1.1(28bd700369ae87f04923e58e037d4bde) - minimist: - specifier: ^1.2.8 - version: 1.2.8 + version: 16.1.5(38ed6dc12e43f709a9a6350e673a80e1) prettier: specifier: ^3.8.1 - version: 3.8.1 + version: 3.8.3 ts-node: specifier: ~10.9.2 - version: 10.9.2(@types/node@25.5.0)(typescript@5.9.3) + version: 10.9.2(@types/node@25.8.0)(typescript@5.9.3) typescript: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: specifier: ^8.57.2 - version: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - optionalDependencies: - '@nx/nx-darwin-arm64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-darwin-x64': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-linux-x64-gnu': - specifier: 22.6.5 - version: 22.6.5 - '@nx/nx-win32-x64-msvc': - specifier: 22.6.5 - version: 22.6.5 + version: 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) teammapper-frontend/packages/mermaid-mindmap-parser: dependencies: @@ -524,7 +480,7 @@ importers: version: 5.9.3 vite: specifier: ^8.0.8 - version: 8.0.8(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1) + version: 8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(jiti@2.7.0)(less@4.4.2)(sass@1.97.3)(terser@5.47.1) packages: @@ -700,8 +656,8 @@ packages: webpack: ^5.30.0 webpack-dev-server: ^5.0.2 - '@angular-devkit/core@19.2.17': - resolution: {integrity: sha512-Ah008x2RJkd0F+NLKqIpA34/vUGwjlprRCkvddjDopAWRzYn6xCkz1Tqwuhn0nR1Dy47wTLKYD999TYl5ONOAQ==} + '@angular-devkit/core@19.2.24': + resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^4.0.0 @@ -709,17 +665,17 @@ packages: chokidar: optional: true - '@angular-devkit/core@19.2.24': - resolution: {integrity: sha512-Kd49warf6U/EyWe5BszF/eebN3zQ3bk7tgfEljAw8q/rX95UUtriJubWvp6pgzHfzBA4jwq8f+QiNZB8eBEXPA==} - engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/core@21.1.0': + resolution: {integrity: sha512-dPfVy0CictDjWffRv4pGTPOFjdlJL3ZkGUqxzaosUjMbJW+Ai9cNn1VNr7zxYZ4kem3BxLBh1thzDsCPrkXlZA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: - chokidar: ^4.0.0 + chokidar: ^5.0.0 peerDependenciesMeta: chokidar: optional: true - '@angular-devkit/core@21.1.0': - resolution: {integrity: sha512-dPfVy0CictDjWffRv4pGTPOFjdlJL3ZkGUqxzaosUjMbJW+Ai9cNn1VNr7zxYZ4kem3BxLBh1thzDsCPrkXlZA==} + '@angular-devkit/core@21.2.11': + resolution: {integrity: sha512-kfMNh5X2hOdyr0uNFaaHUJR3OVr4oH2+UhI+FsTu7gqogdgYlHAVHhHAFulfDgtAEOiqpeSQF9RhQnCJl+/LXA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} peerDependencies: chokidar: ^5.0.0 @@ -750,10 +706,6 @@ packages: engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} hasBin: true - '@angular-devkit/schematics@19.2.17': - resolution: {integrity: sha512-ADfbaBsrG8mBF6Mfs+crKA/2ykB8AJI50Cv9tKmZfwcUcyAdmTr+vVvhsBCfvUAEokigSsgqgpYxfkJVxhJYeg==} - engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@angular-devkit/schematics@19.2.24': resolution: {integrity: sha512-lnw+ZM1Io+cJAkReC0NPDjqObL8NtKzKIkdgEEKC8CUmkhurYhedbicN8Y8NYHgG1uLd2GozW3+/QqPRZaN+Lw==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -762,6 +714,10 @@ packages: resolution: {integrity: sha512-sVgTntCZCOV7mOpHzj6V14KOAoy4B9Ur9yHNRFZVgL2yD77TYRrJ0qwq+l7Im9fSjMCar6csjboqCvyAEpfV1g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@21.2.11': + resolution: {integrity: sha512-69CWZ5/ftLdpUPAwwdAxTNosiGXUyvwdnOfmHsd9NvCT0OSTeq0eQ0UfnGcHASrXIVmnyWiNfBWM1DLqsgBXmw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@angular-devkit/schematics@21.2.5': resolution: {integrity: sha512-gEg84eipTX6lcpNTDVUXBBwp0vs3rXM319Qom+sCLOKBGyqE0mvb1RM1WwfNcyOqeSMQC/vLUwRKqnP0wg1UDg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} @@ -777,9 +733,19 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' + '@angular-eslint/builder@21.4.0': + resolution: {integrity: sha512-3kgGmrVaCYbLtDjC8g4BmMBbdz4thsOB8/NYly8JtXM8EuDZEk5Pz6VTRpJR02ARprwayraTTmhyvq6OGBlQ9w==} + peerDependencies: + '@angular/cli': '>= 21.0.0 < 22.0.0' + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + '@angular-eslint/bundled-angular-compiler@21.3.1': resolution: {integrity: sha512-jjbnJPUXQeQBJ8RM+ahlbt4GH2emVN8JvG3AhFbPci1FrqXi9cOOfkbwLmvpoyTli4LF8gy7g4ctFqnlRgqryw==} + '@angular-eslint/bundled-angular-compiler@21.4.0': + resolution: {integrity: sha512-/3H4BPbQ1BHJkkrUsfusZtmHc+qiFWBBZ9UDPWah4xZMjflexOK9U4GYeH7nMjcuyqFnIlMMeJJNwNLGt/hmdg==} + '@angular-eslint/eslint-plugin-template@21.3.1': resolution: {integrity: sha512-ndPWJodkcEOu2PVUxlUwyz4D2u3r9KO7veWmStVNOLeNrICJA+nQvrz2BWCu0l48rO0K5ezsy0JFcQDVwE/5mw==} peerDependencies: @@ -789,6 +755,15 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' + '@angular-eslint/eslint-plugin-template@21.4.0': + resolution: {integrity: sha512-sJEHx2WYnvOgPpzP1eHnUdRS06zgKmRxbiIR0JiCcaSen5iv1HlsMieXy//FS9TtNW+abHOy4UtDuGuSPflPFA==} + peerDependencies: + '@angular-eslint/template-parser': 21.4.0 + '@typescript-eslint/types': ^7.11.0 || ^8.0.0 + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + '@angular-eslint/eslint-plugin@21.3.1': resolution: {integrity: sha512-08NNTxwawRLTWPLl8dg1BnXMwimx93y4wMEwx2aWQpJbIt4pmNvwJzd+NgoD/Ag2VdLS/gOMadhJH5fgaYKsPQ==} peerDependencies: @@ -796,17 +771,35 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' + '@angular-eslint/eslint-plugin@21.4.0': + resolution: {integrity: sha512-mow2DMj+xBvGl5t7jzC34R8YfbHbaGNyCNFzpovtl9qc0JbuqLyg6htmt8xb05f8ZjATOr4nz0ESt6HV4c51hw==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + '@angular-eslint/schematics@21.3.1': resolution: {integrity: sha512-1U2u4ZsZvwT30aXRLsIJf6tULIiioo9BtASNsldpYecU3/m/1+F61lCYG79qt7YWbif9KABPYZlFTJUFGN8HWA==} peerDependencies: '@angular/cli': '>= 21.0.0 < 22.0.0' + '@angular-eslint/schematics@21.4.0': + resolution: {integrity: sha512-crD6Hfxs7x5bN9FCqTZI7uVSiGvprfCS3MCPOpyIQl87bRr/9aNhnicJ3ROUHv+2A713BgPHIgiCII/bxzrfPw==} + peerDependencies: + '@angular/cli': '>= 21.0.0 < 22.0.0' + '@angular-eslint/template-parser@21.3.1': resolution: {integrity: sha512-moERVCTekQKOvR8RMuEOtWSO3VS1qrzA3keI1dPto/JVB8Nqp9w3R5ZpEoXHzh4zgEryosxmPgdi6UczJe2ouQ==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' + '@angular-eslint/template-parser@21.4.0': + resolution: {integrity: sha512-BaUSLSyS+43fzDoJkTMkGqNdCXq3fGnUZsfXTmrlZPJf5AYFbgAlAPGZXDJyoNWw43fux+DafdlrlKcYUSgSIw==} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + '@angular-eslint/utils@21.3.1': resolution: {integrity: sha512-Q3SGA1/36phZhmsp1mYrKzp/jcmqofRr861MYn46FaWIKSYXBYRzl+H3FIJKBu5CE36Bggu6hbNpwGPuUp+MCg==} peerDependencies: @@ -814,6 +807,13 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '*' + '@angular-eslint/utils@21.4.0': + resolution: {integrity: sha512-7pi+Ga7QmdH5Ig/diau6fR5L4yubgKr9TOjdCg7OeuE/zo0O3osTCNT6JOodzS/iQM1kSCJFDoIBKFeUOttiNw==} + peerDependencies: + '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '*' + '@angular/animations@21.2.9': resolution: {integrity: sha512-wOWbrneivpTYx3xhiPygoNFNC8ZZ1shpgwBe1hYvfky1fkiz1c92XeHIW1V4rPqYw6d3U55aUX5InyTsVe2Xng==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -983,8 +983,8 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} '@babel/core@7.28.6': @@ -1007,8 +1007,8 @@ packages: resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1090,8 +1090,8 @@ packages: resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1654,20 +1654,11 @@ packages: resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} engines: {node: '>=0.8.0'} - '@emnapi/core@1.9.1': - resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - - '@emnapi/runtime@1.9.1': - resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} - - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -1678,8 +1669,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.4': - resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -1690,8 +1681,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.4': - resolution: {integrity: sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==} + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -1702,8 +1693,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.4': - resolution: {integrity: sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==} + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -1714,8 +1705,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.4': - resolution: {integrity: sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==} + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -1726,8 +1717,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.4': - resolution: {integrity: sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==} + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -1738,8 +1729,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.4': - resolution: {integrity: sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==} + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -1750,8 +1741,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.4': - resolution: {integrity: sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==} + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -1762,8 +1753,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.4': - resolution: {integrity: sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==} + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -1774,8 +1765,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.4': - resolution: {integrity: sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==} + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -1786,8 +1777,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.4': - resolution: {integrity: sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==} + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -1798,8 +1789,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.4': - resolution: {integrity: sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==} + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -1810,8 +1801,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.4': - resolution: {integrity: sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==} + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -1822,8 +1813,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.4': - resolution: {integrity: sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==} + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -1834,8 +1825,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.4': - resolution: {integrity: sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==} + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -1846,8 +1837,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.4': - resolution: {integrity: sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==} + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -1858,8 +1849,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.4': - resolution: {integrity: sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==} + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -1870,8 +1861,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.4': - resolution: {integrity: sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==} + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -1882,8 +1873,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.4': - resolution: {integrity: sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==} + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -1894,8 +1885,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.4': - resolution: {integrity: sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==} + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -1906,8 +1897,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.4': - resolution: {integrity: sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==} + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -1918,8 +1909,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.4': - resolution: {integrity: sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==} + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -1930,8 +1921,8 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.4': - resolution: {integrity: sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==} + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -1942,8 +1933,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.4': - resolution: {integrity: sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==} + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -1954,8 +1945,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.4': - resolution: {integrity: sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==} + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -1966,8 +1957,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.4': - resolution: {integrity: sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==} + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -1978,8 +1969,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.4': - resolution: {integrity: sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==} + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1994,25 +1985,16 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@2.0.3': - resolution: {integrity: sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==} - engines: {node: ^20.19.0 || ^22.13.0 || >=24} - peerDependencies: - eslint: ^8.40 || 9 || 10 - peerDependenciesMeta: - eslint: - optional: true - - '@eslint/config-array@0.23.3': - resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.5.3': - resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@1.1.1': - resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/js@10.0.1': @@ -2024,12 +2006,12 @@ packages: eslint: optional: true - '@eslint/object-schema@3.0.3': - resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/plugin-kit@0.6.1': - resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@fortawesome/angular-fontawesome@4.0.0': @@ -2069,12 +2051,16 @@ packages: peerDependencies: hono: '>=4.12.18' - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -2240,8 +2226,8 @@ packages: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} '@jest/console@30.3.0': @@ -2261,8 +2247,12 @@ packages: resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/environment-jsdom-abstract@30.3.0': - resolution: {integrity: sha512-0hNFs5N6We3DMCwobzI0ydhkY10sT1tZSC0AAiy+0g2Dt/qEWgrcV5BrMxPczhe41cxW4qm6X+jqZaUdpZIajA==} + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment-jsdom-abstract@30.4.1': + resolution: {integrity: sha512-dSlKrqug3siYNHVnjwIldShY12wAH3spwRltO/+8VOjg0X+xEq7vOs3DbBs4LRKsu7OH+NUb9kuZUNBF9Ho3TA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -2275,18 +2265,34 @@ packages: resolution: {integrity: sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment@30.4.1': + resolution: {integrity: sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect-utils@30.3.0': resolution: {integrity: sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect-utils@30.4.1': + resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect@30.3.0': resolution: {integrity: sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/expect@30.4.1': + resolution: {integrity: sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/fake-timers@30.3.0': resolution: {integrity: sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/fake-timers@30.4.1': + resolution: {integrity: sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/get-type@30.1.0': resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2295,10 +2301,18 @@ packages: resolution: {integrity: sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/globals@30.4.1': + resolution: {integrity: sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/pattern@30.0.1': resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/pattern@30.4.0': + resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/reporters@30.3.0': resolution: {integrity: sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2312,10 +2326,18 @@ packages: resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/snapshot-utils@30.3.0': resolution: {integrity: sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/snapshot-utils@30.4.1': + resolution: {integrity: sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/source-map@30.0.1': resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -2332,10 +2354,18 @@ packages: resolution: {integrity: sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/transform@30.4.1': + resolution: {integrity: sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/types@30.3.0': resolution: {integrity: sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/types@30.4.1': + resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==} + 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==} @@ -2394,50 +2424,50 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/fs-core@4.57.1': - resolution: {integrity: sha512-YrEi/ZPmgc+GfdO0esBF04qv8boK9Dg9WpRQw/+vM8Qt3nnVIJWIa8HwZ/LXVZ0DB11XUROM8El/7yYTJX+WtA==} + '@jsonjoy.com/fs-core@4.57.2': + resolution: {integrity: sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-fsa@4.57.1': - resolution: {integrity: sha512-ooEPvSW/HQDivPDPZMibHGKZf/QS4WRir1czGZmXmp3MsQqLECZEpN0JobrD8iV9BzsuwdIv+PxtWX9WpPLsIA==} + '@jsonjoy.com/fs-fsa@4.57.2': + resolution: {integrity: sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.57.1': - resolution: {integrity: sha512-XHkFKQ5GSH3uxm8c3ZYXVrexGdscpWKIcMWKFQpMpMJc8gA3AwOMBJXJlgpdJqmrhPyQXxaY9nbkNeYpacC0Og==} + '@jsonjoy.com/fs-node-builtins@4.57.2': + resolution: {integrity: sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.57.1': - resolution: {integrity: sha512-pqGHyWWzNck4jRfaGV39hkqpY5QjRUQ/nRbNT7FYbBa0xf4bDG+TE1Gt2KWZrSkrkZZDE3qZUjYMbjwSliX6pg==} + '@jsonjoy.com/fs-node-to-fsa@4.57.2': + resolution: {integrity: sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-utils@4.57.1': - resolution: {integrity: sha512-vp+7ZzIB8v43G+GLXTS4oDUSQmhAsRz532QmmWBbdYA20s465JvwhkSFvX9cVTqRRAQg+vZ7zWDaIEh0lFe2gw==} + '@jsonjoy.com/fs-node-utils@4.57.2': + resolution: {integrity: sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node@4.57.1': - resolution: {integrity: sha512-3YaKhP8gXEKN+2O49GLNfNb5l2gbnCFHyAaybbA2JkkbQP3dpdef7WcUaHAulg/c5Dg4VncHsA3NWAUSZMR5KQ==} + '@jsonjoy.com/fs-node@4.57.2': + resolution: {integrity: sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-print@4.57.1': - resolution: {integrity: sha512-Ynct7ZJmfk6qoXDOKfpovNA36ITUx8rChLmRQtW08J73VOiuNsU8PB6d/Xs7fxJC2ohWR3a5AqyjmLojfrw5yw==} + '@jsonjoy.com/fs-print@4.57.2': + resolution: {integrity: sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.57.1': - resolution: {integrity: sha512-/oG8xBNFMbDXTq9J7vepSA1kerS5vpgd3p5QZSPd+nX59uwodGJftI51gDYyHRpP57P3WCQf7LHtBYPqwUg2Bg==} + '@jsonjoy.com/fs-snapshot@4.57.2': + resolution: {integrity: sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' @@ -2614,42 +2644,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -2682,20 +2719,14 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.1.3': - resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@nestjs/cache-manager@3.1.0': - resolution: {integrity: sha512-pEIqYZrBcE8UdkJmZRduurvoUfdU+3kRPeO1R2muiMbZnRuqlki5klFFNllO9LyYWzrx98bd1j0PSPKSJk1Wbw==} + '@nestjs/cache-manager@3.1.2': + resolution: {integrity: sha512-Eglt8lUzC3Q3OZ2hFt4vLZ190M94YSJXUiKo67K/zlUgZQGtvxL0AYeKbG96x8+1gJTF7QhFpYw/RkQ28416Mw==} peerDependencies: '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 @@ -2716,8 +2747,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.19': - resolution: {integrity: sha512-qeiTt2tv+e5QyDKqG8HlVZb2wx64FEaSGFJouqTSRs+kG44iTfl3xlz1XqVped+rihx4hmjWgL5gkhtdK3E6+Q==} + '@nestjs/common@11.1.21': + resolution: {integrity: sha512-YV1HYDGsm2rnR0vrLKidtrG6jYX5yqiIjeur1j8++dKGqhhsJ6cjMs0RfQRSTUH7IjgDemA59/znQ8nRrE0D9g==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -2735,8 +2766,8 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - '@nestjs/core@11.1.19': - resolution: {integrity: sha512-6nJkWa2efrYi+XlU686J9y5L7OvxpLVjT0T/sxRKE7Jvpffiihelup4WSvLvRhdHDjj/5SuoWEwqReXAaaeHmw==} + '@nestjs/core@11.1.21': + resolution: {integrity: sha512-fqo0BHgny3MOuAL8GSfG3ZUKFVVBaBQD/0iyibnwTONT5vPexjQxJzu+945iloVvBDmrnAaRWxC1gqCDEs/AXQ==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -2753,14 +2784,14 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@11.1.19': - resolution: {integrity: sha512-Vpdv8jyCQdThfoTx+UTn+DRYr6H6X02YUqcpZ3qP6G3ZUwtVp7eS+hoQPGd4UuCnlnFG8Wqr2J9bGEzQdi1rIg==} + '@nestjs/platform-express@11.1.21': + resolution: {integrity: sha512-lA3ViycOnz4Df3EstIKpuAVFhqxQixTnjAVk0M+LRyNBlGM6VSCaNJaAIrb9Pcry39T4hTHpNVbRqGLSvhL8gA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.19': - resolution: {integrity: sha512-gu1nPIEaP5Qjjg/Cl8wXyvwGpdZGzgbtK4KcH65YRAA+GTKUkIHb4BNpLJ27Ymq/wqLJKNEbCjajfzD0BEjMGA==} + '@nestjs/platform-socket.io@11.1.21': + resolution: {integrity: sha512-Tq5JgaVS+auD3DXuRBy8UMU3mf69HJO8Ep+BuRS9GYMXGd/5sdMHqIQvXlXkGih9tQXdeeG9WoqURe/+IjPKng==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 @@ -2772,10 +2803,14 @@ packages: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 - '@nestjs/schematics@11.0.9': - resolution: {integrity: sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==} + '@nestjs/schematics@11.1.0': + resolution: {integrity: sha512-lVxGZ46tcdItFMoXr6vyKWlnOsm1SZm/GUqAEDvy2RL4Q4O+3bkziAhrO7Y8JLssFUUvNFEGqAizI52WAxhjDw==} peerDependencies: + prettier: ^3.0.0 typescript: '>=4.8.2' + peerDependenciesMeta: + prettier: + optional: true '@nestjs/serve-static@5.0.5': resolution: {integrity: sha512-AhYx3N9aMwR2cb0w5Nlb5nHNYiAcF74ea/D/xna+PxlXwjmwGN/PpC/5fuMtOwmPBMgOTxNPOnB8C9LDZBSgyw==} @@ -2793,8 +2828,8 @@ packages: fastify: optional: true - '@nestjs/testing@11.1.17': - resolution: {integrity: sha512-lNffw+z+2USewmw4W0tsK+Rq94A2N4PiHbcqoRUu5y8fnqxQeIWGHhjo5BFCqj7eivqJBhT7WdRydxVq4rAHzg==} + '@nestjs/testing@11.1.21': + resolution: {integrity: sha512-RhzaUFxr6/bpXWjKIzr7p2eHKMFMLwPgsxJNFcCf2CkkT3UEjW+KRGb7E2JY+fh+ck3zAdvQJrzATDnSsVlFZw==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -2815,8 +2850,8 @@ packages: rxjs: ^7.2.0 typeorm: ^0.3.0 || ^1.0.0-dev - '@nestjs/websockets@11.1.19': - resolution: {integrity: sha512-2qo8jtIwwwgkqAI1BtnJ02EaFLrRkKA39eYXS8IhZCHilhBHCWdjnJ5cLcFq4oF+s+KZ7LcLGD/3stxJy8ijzg==} + '@nestjs/websockets@11.1.21': + resolution: {integrity: sha512-2L+jFf6Nbjv7WacngvSoYGYTalqNuXlOkFl9Q5e92XiPK4gR6c4Zw6zFcb7btTY5zf7pITvOoAJIGwf3+gciRA==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -2909,26 +2944,6 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true - '@nx/nx-darwin-arm64@22.6.5': - resolution: {integrity: sha512-qT77Omkg5xQuL2+pDbneX2tI+XW5ZeayMylu7UUgK8OhTrAkJLKjpuYRH4xT5XBipxbDtlxmO3aLS3Ib1pKzJQ==} - cpu: [arm64] - os: [darwin] - - '@nx/nx-darwin-x64@22.6.5': - resolution: {integrity: sha512-9jICxb7vfJ56y/7Yuh3b/n1QJqWxO9xnXKYEs6SO8xPoW/KomVckILGc1C6RQSs6/3ixVJC7k1Dh1wm5tKPFrg==} - cpu: [x64] - os: [darwin] - - '@nx/nx-linux-x64-gnu@22.6.5': - resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} - cpu: [x64] - os: [linux] - - '@nx/nx-win32-x64-msvc@22.6.5': - resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} - cpu: [x64] - os: [win32] - '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -2936,8 +2951,8 @@ packages: '@oxc-project/types@0.113.0': resolution: {integrity: sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA==} - '@oxc-project/types@0.124.0': - resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@oxc-project/types@0.130.0': + resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -2971,36 +2986,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.6': resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.6': resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.6': resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.6': resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.6': resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.6': resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} @@ -3024,35 +3045,38 @@ packages: resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} - '@peculiar/asn1-cms@2.6.1': - resolution: {integrity: sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==} + '@peculiar/asn1-cms@2.7.0': + resolution: {integrity: sha512-hew63shtzzvBcSHbhm+cyAmKe6AIfinT9hzEqSPjDC6opTTMKmTkQ0gHuN2KsWlvqiKw1S/fS94fhag/FJkioQ==} - '@peculiar/asn1-csr@2.6.1': - resolution: {integrity: sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==} + '@peculiar/asn1-csr@2.7.0': + resolution: {integrity: sha512-VVsAyGqErT9D1SY4aEqozThXMVI+ssVRiv2DDeYuvpBKLIgZ3hYs3Ay3u/VSoKq6ESFi9cf6rf3IOOzfwh7oMA==} - '@peculiar/asn1-ecc@2.6.1': - resolution: {integrity: sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==} + '@peculiar/asn1-ecc@2.7.0': + resolution: {integrity: sha512-n7KEs/Q/wrB415cxy4fHOBhegp4NdJ15fkJPwcB/3/8iNBQC2L/N7SChJPKDJPZGYH0jD4Tg4/0vnHmwghnbKw==} - '@peculiar/asn1-pfx@2.6.1': - resolution: {integrity: sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==} + '@peculiar/asn1-pfx@2.7.0': + resolution: {integrity: sha512-V/nrlQVmhg7lYAsM7E13UDL5erAwFv6kCIVFqNaMIHSVi7dngcT839JkRTkQBqznMG98l2XjxYk74ZztAohZzA==} - '@peculiar/asn1-pkcs8@2.6.1': - resolution: {integrity: sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==} + '@peculiar/asn1-pkcs8@2.7.0': + resolution: {integrity: sha512-9GTl1nE8Mx1kTZ+7QyYatDyKsm34QcWRBFkY1iPvWC3X4Dona5s/tlLiQsx5WzVdZqiMBZNYT0buyw4/vbhnjw==} - '@peculiar/asn1-pkcs9@2.6.1': - resolution: {integrity: sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==} + '@peculiar/asn1-pkcs9@2.7.0': + resolution: {integrity: sha512-Bh7m+OuIaSEllPQcSd9OSp93F4ROWH7sbITWV8MI+8dwsjE5111/87VxiWVvYFKyww3vp39geLv9ENqhwWHcew==} - '@peculiar/asn1-rsa@2.6.1': - resolution: {integrity: sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==} + '@peculiar/asn1-rsa@2.7.0': + resolution: {integrity: sha512-/qvENQrXyTZURjMqSeofHul0JJt2sNSzSwk36pl2olkHbaioMQgrASDZAlHXl0xUlnVbHj0uGgOrBMTb5x2aJQ==} - '@peculiar/asn1-schema@2.6.0': - resolution: {integrity: sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==} + '@peculiar/asn1-schema@2.7.0': + resolution: {integrity: sha512-W8ZfWzLmQnrcky+eh3tni4IozMdqBDiHWU0N+vve/UGjMaUs8c0L7A2oEdkBXS8rTpWDpK/aoI3DG/L/hxmxPg==} - '@peculiar/asn1-x509-attr@2.6.1': - resolution: {integrity: sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==} + '@peculiar/asn1-x509-attr@2.7.0': + resolution: {integrity: sha512-NS8e7SOgXipkzUPLF/sce7ukpMpWjhxYsH0n6Y+bHYo4TTxOb95Zv7hqwSuL212mj5YxovjdOKQOgH1As3E94w==} - '@peculiar/asn1-x509@2.6.1': - resolution: {integrity: sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==} + '@peculiar/asn1-x509@2.7.0': + resolution: {integrity: sha512-mUn9RRrkGDnG4ALfunDmzyRW5dg+sWCj/pfnCCqEHYbkGxEpvUt6iVJv8Yw1cyp6SWZ26ZE5oSmI5SqEaen15g==} + + '@peculiar/utils@2.0.3': + resolution: {integrity: sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==} '@peculiar/x509@1.14.3': resolution: {integrity: sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==} @@ -3066,8 +3090,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.58.2': - resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} engines: {node: '>=18'} hasBin: true @@ -3080,56 +3104,17 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@redis/bloom@5.11.0': - resolution: {integrity: sha512-KYiVilAhAFN3057afUb/tfYJpsEyTkQB+tQcn5gVVA7DgcNOAj8lLxe4j8ov8BF6I9C1Fe/kwlbuAICcTMX8Lw==} - engines: {node: '>= 18'} - peerDependencies: - '@redis/client': ^5.11.0 - - '@redis/client@5.11.0': - resolution: {integrity: sha512-GHoprlNQD51Xq2Ztd94HHV94MdFZQ3CVrpA04Fz8MVoHM0B7SlbmPEVIjwTbcv58z8QyjnrOuikS0rWF03k5dQ==} - engines: {node: '>= 18'} - peerDependencies: - '@node-rs/xxhash': ^1.1.0 - peerDependenciesMeta: - '@node-rs/xxhash': - optional: true - - '@redis/json@5.11.0': - resolution: {integrity: sha512-1iAy9kAtcD0quB21RbPTbUqqy+T2Uu2JxucwE+B4A+VaDbIRvpZR6DMqV8Iqaws2YxJYB3GC5JVNzPYio2ErUg==} - engines: {node: '>= 18'} - peerDependencies: - '@redis/client': ^5.11.0 - - '@redis/search@5.11.0': - resolution: {integrity: sha512-g1l7f3Rnyk/xI99oGHIgWHSKFl45Re5YTIcO8j/JE8olz389yUFyz2+A6nqVy/Zi031VgPDWscbbgOk8hlhZ3g==} - engines: {node: '>= 18'} - peerDependencies: - '@redis/client': ^5.11.0 - - '@redis/time-series@5.11.0': - resolution: {integrity: sha512-TWFeOcU4xkj0DkndnOyhtxvX1KWD+78UHT3XX3x3XRBUGWeQrKo3jqzDsZwxbggUgf9yLJr/akFHXru66X5UQA==} - engines: {node: '>= 18'} - peerDependencies: - '@redis/client': ^5.11.0 - - '@rolldown/binding-android-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - '@rolldown/binding-android-arm64@1.0.0-rc.4': resolution: {integrity: sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + '@rolldown/binding-android-arm64@1.0.1': + resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] - os: [darwin] + os: [android] '@rolldown/binding-darwin-arm64@1.0.0-rc.4': resolution: {integrity: sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q==} @@ -3137,10 +3122,10 @@ packages: cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.15': - resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + '@rolldown/binding-darwin-arm64@1.0.1': + resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] + cpu: [arm64] os: [darwin] '@rolldown/binding-darwin-x64@1.0.0-rc.4': @@ -3149,11 +3134,11 @@ packages: cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': - resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + '@rolldown/binding-darwin-x64@1.0.1': + resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] - os: [freebsd] + os: [darwin] '@rolldown/binding-freebsd-x64@1.0.0-rc.4': resolution: {integrity: sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw==} @@ -3161,11 +3146,11 @@ packages: cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': - resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + '@rolldown/binding-freebsd-x64@1.0.1': + resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] + cpu: [x64] + os: [freebsd] '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': resolution: {integrity: sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w==} @@ -3173,10 +3158,10 @@ packages: cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] + cpu: [arm] os: [linux] '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': @@ -3184,60 +3169,70 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + '@rolldown/binding-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': resolution: {integrity: sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] + libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + '@rolldown/binding-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] + cpu: [arm64] os: [linux] + libc: [musl] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.1': + resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] + cpu: [ppc64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': - resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + '@rolldown/binding-linux-s390x-gnu@1.0.1': + resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] + cpu: [s390x] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': resolution: {integrity: sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': - resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + '@rolldown/binding-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': resolution: {integrity: sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] + libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': - resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + '@rolldown/binding-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [linux] + libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': resolution: {integrity: sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ==} @@ -3245,21 +3240,21 @@ packages: cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': - resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] + '@rolldown/binding-openharmony-arm64@1.0.1': + resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] '@rolldown/binding-wasm32-wasi@1.0.0-rc.4': resolution: {integrity: sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + '@rolldown/binding-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] + cpu: [wasm32] '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': resolution: {integrity: sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ==} @@ -3267,10 +3262,10 @@ packages: cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': - resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + '@rolldown/binding-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] + cpu: [arm64] os: [win32] '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': @@ -3279,184 +3274,203 @@ packages: cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.15': - resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + '@rolldown/binding-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] '@rolldown/pluginutils@1.0.0-rc.4': resolution: {integrity: sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ==} - '@rollup/rollup-android-arm-eabi@4.60.1': - resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.60.1': - resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + '@rollup/rollup-android-arm64@4.60.4': + resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.60.1': - resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.60.1': - resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + '@rollup/rollup-darwin-x64@4.60.4': + resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.60.1': - resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.60.1': - resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': - resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} cpu: [arm] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.60.1': - resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} cpu: [arm] os: [linux] + libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.60.1': - resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} cpu: [arm64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.60.1': - resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} cpu: [arm64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.60.1': - resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} cpu: [loong64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.60.1': - resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} cpu: [loong64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.60.1': - resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} cpu: [ppc64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.60.1': - resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} cpu: [ppc64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.60.1': - resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} cpu: [riscv64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.60.1': - resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} cpu: [riscv64] os: [linux] + libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.60.1': - resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} cpu: [s390x] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.60.1': - resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} cpu: [x64] os: [linux] + libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.60.1': - resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} cpu: [x64] os: [linux] + libc: [musl] - '@rollup/rollup-openbsd-x64@4.60.1': - resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.60.1': - resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.60.1': - resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.60.1': - resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.60.1': - resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.60.1': - resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} cpu: [x64] os: [win32] '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@schematics/angular@21.2.5': - resolution: {integrity: sha512-orOiXcG86t34ejqbkm7ZHEkGfwTU/ySYFgY7BOQdaYFCoNQXxtU87fZoHckJ2xYpVitoKTvbf1bxDDphXb3ycw==} + '@schematics/angular@21.2.11': + resolution: {integrity: sha512-EqH12Fr3vaWFpsilFDFXkxwMIidEDZr5cGl0w2hDRG7DjXE2oRB/VXix8xmpuHkzJ40Jgew6hIc+bfbwQhFK1A==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} '@schematics/angular@21.2.7': resolution: {integrity: sha512-aqEj3RyBtmH+41HZvrbfrpCo0e+0NzwyQyNSC/wLDShVqoidBtPbEdHU1FZ4+ni41da7rI3F12gUuAHws27kMA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - '@secretlint/config-creator@11.4.1': - resolution: {integrity: sha512-gWFJvjIrSd1LW0Su8eaVeo4mKEoxNoTVkVm1a9krJWeIDXG6iJ0/Hwp7d6TqDxCmwjEZQ80d/PH2TzU144bpVA==} + '@secretlint/config-creator@11.7.1': + resolution: {integrity: sha512-ET4EXYzSjl1EgwZRJmU/2NjQVIW4u+2nIZu4hs6Og5ys2MbSEheHNoBXOifQOZDgObleP8S9aU3BV2bUqPEQxw==} engines: {node: '>=20.0.0'} - '@secretlint/config-loader@11.4.1': - resolution: {integrity: sha512-x91BCLX8Q2qdQvAIt2uUJx/W3mX82p5mW7tldG8di6vYGWxg5MjXSfcSpPhSRdjJJysl770RnOn1+OEJttAjxA==} + '@secretlint/config-loader@11.7.1': + resolution: {integrity: sha512-XExWOV82/nbuo9N+m6G9by4n2mnts3Z1k2i499pIJ0+VaASbA06u1fQFErMGYrobeUpSTC5i077Ee7G2lJhFiw==} engines: {node: '>=20.0.0'} - '@secretlint/core@11.4.1': - resolution: {integrity: sha512-pGFyG0tF1Yp51NnbJrCACqYZSYZ0CZniGn0UYT/s9BNjXQ2wkd6qi9dlPeg6tedaICTQD1IByhyM02emvolO0Q==} + '@secretlint/core@11.7.1': + resolution: {integrity: sha512-D1a/6U3JT2hhs+OpocAf1j+2t79ExrFqNm8j7pilhCQqpJ9QIEexGs1ty3v9wDACm48v0PFhCNCyn4hyctGYig==} engines: {node: '>=20.0.0'} - '@secretlint/formatter@11.4.1': - resolution: {integrity: sha512-0XWJkWEWZnva3l3cmkRQy+aH6JNqXJ9KqciQQhYYqmvN37gz0vIuXAmJ1V+NrgZPe3b6hpwWAKiJtKXBR57n7w==} + '@secretlint/formatter@11.7.1': + resolution: {integrity: sha512-x/9yd8DzUOgJlURhXH9Z0aIOmCn1XNvHxQPhlufPKFg9MWeDTysGGqUwEn1TBIlOvkeB38y2nJkrCkK/u2y7dg==} engines: {node: '>=20.0.0'} - '@secretlint/node@11.4.1': - resolution: {integrity: sha512-Vz7sAt+6Zse6dnT8MsCD8RBqpvzepP3uaft0iN6BHQBia8akzyKhW16VrHQOykB4pfRoFHH8ZJfxoRw52HdS3A==} + '@secretlint/node@11.7.1': + resolution: {integrity: sha512-spzkxcE+MjgO6cGir2AoLHyuCmOBufJLq/rHwUDmi/AOCPITGbCRvow+cBC5O9uWQcGOxDq/DgIsbgIlnooTVA==} engines: {node: '>=20.0.0'} - '@secretlint/profiler@11.4.1': - resolution: {integrity: sha512-mMzPUnZ2+arX8PYCwKU1ouCHzVUIZiNWPtzyyguL0Oc7dokyk8u7QA4IwAl8DMm04GN3jntL2E+1CBVN7z3crg==} + '@secretlint/profiler@11.7.1': + resolution: {integrity: sha512-Fa1fCVT4izv8PRXL8t4KB6FwBEL2OYqOYtNsyssWd7giNoNcohCoLGnDhJNLcTxTyTqkDzLlGV0M4CYsCkcfZQ==} - '@secretlint/resolver@11.4.1': - resolution: {integrity: sha512-SIakIOk99/XvSl8FyZHkGw3TDBdOPEtC1PDFLbLBmx2SXFKyIiDFY+i8sYBDyK08EKEML7QHUDuGeDzFoEHkIA==} + '@secretlint/resolver@11.7.1': + resolution: {integrity: sha512-TShFKvFDqJalqM4r/NHrpdbd512odIOLZit01r65Szs5vdRq/W38B1QF0hwh0ZJq9wLOOpkTuVWkff0S4LFwZQ==} - '@secretlint/secretlint-rule-preset-recommend@11.4.1': - resolution: {integrity: sha512-htejVuXSTVOlhZhJ9XqdKXBtWTuL+EbG2LdQQk0pfQFJfZLpeNUTjI8fE9ZCuFejuUJiGv1jUi1/rqT64M0Dvg==} + '@secretlint/secretlint-rule-preset-recommend@11.7.1': + resolution: {integrity: sha512-LsiEhDWoXi9GNx1Zp5eYRNajeUvBrZ82h3k/vzFqo0WoSdmpyDnkkzBTLXqWqj1VNyPCcTwlxOqnEmlz6lXc7A==} engines: {node: '>=20.0.0'} - '@secretlint/source-creator@11.4.1': - resolution: {integrity: sha512-Q6k6sQmYy7whNl0qfqetfinYKC2TYjG501qw96iMfkdBwrGeqlajFWsGLcaLHQlAVldRMHCEmlyw2m4I7uwJ/Q==} + '@secretlint/source-creator@11.7.1': + resolution: {integrity: sha512-y7SS9VXbUJgVn0qT1KvaDsB0asLa+vsg55W6WLX4DStgkB1IrnUdwAtelZizDjeToLtjRhRRroCs8y6FFIpf3g==} engines: {node: '>=20.0.0'} - '@secretlint/types@11.4.1': - resolution: {integrity: sha512-30jPadVKENeyZKNT7MCO0jTKAYQcWV6TIIccqHx874rBlzU/mBiTnGZdLSflFGECttscgqtozPKawM0fjBwDEg==} + '@secretlint/types@11.7.1': + resolution: {integrity: sha512-bIkBwIdQScZX5xm3DsLp3oL5U/KKUYWgmst39dZsDi55X9tKuPd7/uVpO1PqNrDW1I0QD5t6es/2LonG5RjxXg==} engines: {node: '>=20.0.0'} '@sigstore/bundle@4.0.0': @@ -3483,8 +3497,8 @@ packages: resolution: {integrity: sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==} engines: {node: ^20.17.0 || >=22.9.0} - '@sinclair/typebox@0.34.48': - resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} '@sindresorhus/merge-streams@2.3.0': resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} @@ -3493,8 +3507,8 @@ packages: '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - '@sinonjs/fake-timers@15.1.1': - resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==} + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -3511,20 +3525,20 @@ packages: peerDependencies: eslint: ^9.0.0 || ^10.0.0 - '@textlint/ast-node-types@15.5.2': - resolution: {integrity: sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==} + '@textlint/ast-node-types@15.7.0': + resolution: {integrity: sha512-wZILFXsRf2gGK9k0Fr69GUdfuZV9CrtByga8Qkw0CLyKBBfZXdNQlJF2XdZ2Ju9ggrTbAWehGo0RjCsAHSBWtA==} - '@textlint/linter-formatter@15.5.2': - resolution: {integrity: sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==} + '@textlint/linter-formatter@15.7.0': + resolution: {integrity: sha512-wrpDID1bfBt5XIUXtgw8NmGIuRFansWAtX1FLHv/zLRt3sgxCFnyon88SbhPOPITEgIlfFcsESElCSLzWySubQ==} - '@textlint/module-interop@15.5.2': - resolution: {integrity: sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==} + '@textlint/module-interop@15.7.0': + resolution: {integrity: sha512-+VdoeGFH66OasijhoO7D3OSrqTfJNAIH2OoQHEc5StlO0dqDO2JfZStCbuBPP/ZbpJvuYdoERBP+nKqTAT24vA==} - '@textlint/resolver@15.5.2': - resolution: {integrity: sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==} + '@textlint/resolver@15.7.0': + resolution: {integrity: sha512-f94t/8ZR97uhOu2KvBujMGGtfdoJQZLjDNN7+7PNLaTjCtGn+XrqKjSP9lzXgBhUbKSygRN8AlrCMx9S70FHKw==} - '@textlint/types@15.5.2': - resolution: {integrity: sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ==} + '@textlint/types@15.7.0': + resolution: {integrity: sha512-soItNoFZ8Ua4WCgWwOaTEEyTkX7bjArwVxhN1F2UySeqPsP8QeRiKNUjAIfWQFHddTY6UYR1sHaEh+O0sQPv0Q==} '@thednp/event-listener@2.0.15': resolution: {integrity: sha512-X1B+Yuvb30a/0rGdXnTLMfqvzCQ4D/1Kns7B78ON3BoFNFazJyFQ4ypuErY1jQLZSFDN4c3zTAIrdrtuIa/fFg==} @@ -3568,8 +3582,8 @@ packages: resolution: {integrity: sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==} engines: {node: ^20.17.0 || >=22.9.0} - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -3719,6 +3733,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/express-serve-static-core@4.19.8': resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} @@ -3782,8 +3799,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@25.5.0': - resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} + '@types/node@25.8.0': + resolution: {integrity: sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -3791,8 +3808,8 @@ packages: '@types/pako@2.0.4': resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} - '@types/qs@6.15.0': - resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} '@types/raf@3.4.3': resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==} @@ -3855,68 +3872,67 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.57.2': - resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} + '@typescript-eslint/eslint-plugin@8.59.3': + resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.57.2 + '@typescript-eslint/parser': ^8.59.3 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.57.2': - resolution: {integrity: sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==} + '@typescript-eslint/parser@8.59.3': + resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.57.2': - resolution: {integrity: sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==} + '@typescript-eslint/project-service@8.59.3': + resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.57.2': - resolution: {integrity: sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==} + '@typescript-eslint/scope-manager@8.59.3': + resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.57.2': - resolution: {integrity: sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==} + '@typescript-eslint/tsconfig-utils@8.59.3': + resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.57.2': - resolution: {integrity: sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==} + '@typescript-eslint/type-utils@8.59.3': + resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.57.2': - resolution: {integrity: sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==} + '@typescript-eslint/types@8.59.3': + resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.57.2': - resolution: {integrity: sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==} + '@typescript-eslint/typescript-estree@8.59.3': + resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.57.2': - resolution: {integrity: sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==} + '@typescript-eslint/utils@8.59.3': + resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.57.2': - resolution: {integrity: sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==} + '@typescript-eslint/visitor-keys@8.59.3': + resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -3957,41 +3973,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -4156,12 +4180,15 @@ packages: peerDependencies: ajv: 8.18.0 - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} ajv@8.18.0: resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + algoliasearch@5.48.1: resolution: {integrity: sha512-Rf7xmeuIo7nb6S4mp4abW2faW8DauZyE2faBIKFaUfP3wnpOvNSbiI5AwVhqBNj0jPgBWEvhyCu0sLjN2q77Rg==} engines: {node: '>= 14.0.0'} @@ -4170,8 +4197,8 @@ packages: resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} engines: {node: '>=0.4.2'} - angular-eslint@21.3.1: - resolution: {integrity: sha512-VGQWTyuPAEO/AnZuqHxGBJMYSiZ0tbrHx/OgPCRTKHfbrFU4x+zivS84h9UWoDpDtius1RyD+ZReFjTAEWptiA==} + angular-eslint@21.4.0: + resolution: {integrity: sha512-LH7bWmtJvsubzwPoztnl1pWgI5X0VrfGTUITGSYcwn2J+SXuN/avzrKrxJmhUiIrNvLtfV+18GG6xZS1IGZdKg==} peerDependencies: '@angular/cli': '>= 21.0.0 < 22.0.0' eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -4222,6 +4249,10 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + ansis@4.3.0: + resolution: {integrity: sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -4287,8 +4318,8 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - asn1js@3.0.7: - resolution: {integrity: sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==} + asn1js@3.0.10: + resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} engines: {node: '>=12.0.0'} astral-regex@2.0.0: @@ -4299,9 +4330,6 @@ packages: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -4390,8 +4418,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.18: - resolution: {integrity: sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==} + baseline-browser-mapping@2.10.30: + resolution: {integrity: sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==} engines: {node: '>=6.0.0'} hasBin: true @@ -4423,8 +4451,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} body-parser@2.2.2: @@ -4444,14 +4472,14 @@ packages: boundary@2.0.0: resolution: {integrity: sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==} - brace-expansion@1.1.13: - resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - brace-expansion@2.0.3: - resolution: {integrity: sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} - brace-expansion@5.0.5: - resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} engines: {node: 18 || 20 || >=22} braces@3.0.3: @@ -4509,8 +4537,8 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -4532,8 +4560,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001787: - resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} canvg@3.0.11: resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} @@ -4639,10 +4667,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -4694,8 +4718,8 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - comment-json@4.4.1: - resolution: {integrity: sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==} + comment-json@5.0.0: + resolution: {integrity: sha512-uiqLcOiVDJtBP8WGkZHEP+FZIhTzP1dxvn59EfoYUi9gqupjrBWVQkO2atDrbnKPwLeotFYDsuNb26uBMqB+hw==} engines: {node: '>= 6'} component-emitter@1.3.1: @@ -4745,6 +4769,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -5185,8 +5213,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.335: - resolution: {integrity: sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==} + electron-to-chromium@1.5.358: + resolution: {integrity: sha512-EO7tKm3QxRqTs1lSuPXzl6yRAwznehp0AH9OoMOIC+4mQzTFday8FJCO5KU6J/TFSQXEOahNq4vTKpz1jmCVOA==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -5226,12 +5254,12 @@ packages: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - engine.io@6.6.6: - resolution: {integrity: sha512-U2SN0w3OpjFRVlrc17E6TMDmH58Xl9rai1MblNjAdwWp07Kk+llmzX0hjDpQdrDGzwmvOtgM5yI+meYX6iZ2xA==} + engine.io@6.6.7: + resolution: {integrity: sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==} engines: {node: '>=10.2.0'} - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + enhanced-resolve@5.21.3: + resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -5268,8 +5296,8 @@ packages: error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -5280,8 +5308,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@2.0.0: - resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} @@ -5307,8 +5335,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild-wasm@0.27.4: - resolution: {integrity: sha512-3xhVMcJ8Odvb1QjlWnjBGSYVYESsi3/oJYwLyVvbHOb2CiV4mFtD6x8Lk6JFnRxwEE3fUeVuJLbIxyVQWa867g==} + esbuild-wasm@0.28.0: + resolution: {integrity: sha512-5TRVKExcEmeMkccIZMzUq+Az6X2RoMAJyfl6SMMO1dMVhmvt0I2mx7gAb6zYi42n4d1ETcatFXazGKzA+aW7fg==} engines: {node: '>=18'} hasBin: true @@ -5317,8 +5345,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.4: - resolution: {integrity: sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==} + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} hasBin: true @@ -5357,8 +5385,8 @@ packages: unrs-resolver: optional: true - eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} eslint-import-resolver-typescript@4.4.4: resolution: {integrity: sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==} @@ -5420,10 +5448,6 @@ packages: typescript: optional: true - eslint-plugin-nestjs@1.2.3: - resolution: {integrity: sha512-CYS2l+oO9sZ8QN1B0/Xgz+2CERfiWCiHDmDslX30yrJrNlBNKFypeCac/7g/NE+LDuox5MH13uvd4qd52Tlt5w==} - engines: {npm: '>=3'} - eslint-plugin-prettier@5.5.5: resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -5438,10 +5462,6 @@ packages: eslint-config-prettier: optional: true - eslint-plugin-typeorm@0.0.19: - resolution: {integrity: sha512-NQX2QXSz9nByJ5XGZzFSn+vk5hHLpaks0ZypY8jeqqHrWfOtz/sUkW5AyaKTlNafB3XkpWlGnGW2afwHlTUoHA==} - engines: {node: '>=0.10.0'} - eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -5462,8 +5482,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.1.0: - resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} + eslint@10.4.0: + resolution: {integrity: sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -5535,10 +5555,6 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - eventsource-parser@3.0.6: - resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} - engines: {node: '>=18.0.0'} - eventsource-parser@3.0.8: resolution: {integrity: sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==} engines: {node: '>=18.0.0'} @@ -5563,17 +5579,21 @@ packages: resolution: {integrity: sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + expect@30.4.1: + resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@8.3.2: - resolution: {integrity: sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} engines: {node: '>= 0.10.0'} express@5.2.1: @@ -5628,8 +5648,8 @@ packages: picomatch: optional: true - fflate@0.8.2: - resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -5728,8 +5748,8 @@ packages: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} - fs-extra@11.3.4: - resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + fs-extra@11.3.5: + resolution: {integrity: sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==} engines: {node: '>=14.14'} fs-minipass@3.0.3: @@ -5774,8 +5794,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.5.0: - resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -5802,8 +5822,8 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.13.7: - resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -5835,8 +5855,8 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - globals@17.4.0: - resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} engines: {node: '>=18'} globalthis@1.0.4: @@ -5889,12 +5909,12 @@ packages: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} - hono@4.12.18: - resolution: {integrity: sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==} + hono@4.12.19: + resolution: {integrity: sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ==} engines: {node: '>=16.9.0'} hookified@1.15.1: @@ -5904,8 +5924,8 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} - hosted-git-info@9.0.2: - resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} engines: {node: ^20.17.0 || >=22.9.0} hpack.js@2.1.6: @@ -6089,8 +6109,8 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} - ipaddr.js@2.3.0: - resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + ipaddr.js@2.4.0: + resolution: {integrity: sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==} engines: {node: '>= 10'} is-array-buffer@3.0.5: @@ -6123,8 +6143,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -6198,8 +6218,8 @@ packages: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} - is-network-error@1.3.1: - resolution: {integrity: sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} engines: {node: '>=16'} is-number-object@1.1.1: @@ -6378,6 +6398,10 @@ packages: resolution: {integrity: sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-docblock@30.2.0: resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6386,8 +6410,8 @@ packages: resolution: {integrity: sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-environment-jsdom@30.3.0: - resolution: {integrity: sha512-RLEOJy6ip1lpw0yqJ8tB3i88FC7VBz7i00Zvl2qF71IdxjS98gC9/0SPWYIBVXHm5hgCYK0PAlSlnHGGy9RoMg==} + jest-environment-jsdom@30.4.1: + resolution: {integrity: sha512-o3nfaN4zej7qgk2X0j8Jhq/S9nAVKs2xK3QeQxeHVvpkEPxaA1yxDGydR+iVI7zPy7Cp62Aq2h3Ja46QvfWHGA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 @@ -6403,6 +6427,10 @@ packages: resolution: {integrity: sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-haste-map@30.4.1: + resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-leak-detector@30.3.0: resolution: {integrity: sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6411,14 +6439,26 @@ packages: resolution: {integrity: sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.3.0: resolution: {integrity: sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.4.1: + resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.3.0: resolution: {integrity: sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.4.1: + resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-pnp-resolver@1.2.3: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} @@ -6428,8 +6468,8 @@ packages: jest-resolve: optional: true - jest-preset-angular@16.1.1: - resolution: {integrity: sha512-yrvV/86IO2mj3H33xWVEPwmLW9CJIOM7YcMBcwDWa9OXEAW9XM+47no8R06oine9+wH+QWXfFzFR3TG6nX7QRA==} + jest-preset-angular@16.1.5: + resolution: {integrity: sha512-4YNjA8O02TAQisr3JozsyFGQ4Dkc3FQyGebjpRZfXhiMo32arYW1bXZ5KXCjSe4OoXZrFV5T5nrV7SXr4NmBuA==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0} peerDependencies: '@angular/compiler-cli': '>=19.0.0 <22.0.0' @@ -6444,6 +6484,10 @@ packages: resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-regex-util@30.4.0: + resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-resolve-dependencies@30.3.0: resolution: {integrity: sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6464,10 +6508,18 @@ packages: resolution: {integrity: sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-snapshot@30.4.1: + resolution: {integrity: sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-util@30.3.0: resolution: {integrity: sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-util@30.4.1: + resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-validate@30.3.0: resolution: {integrity: sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6484,6 +6536,10 @@ packages: resolution: {integrity: sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-worker@30.4.1: + resolution: {integrity: sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest@30.3.0: resolution: {integrity: sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -6504,12 +6560,12 @@ packages: engines: {node: '>=0.4'} hasBin: true - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.2: - resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6573,8 +6629,8 @@ packages: jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} jsonlint@1.6.0: resolution: {integrity: sha512-x6YLBe6NjdpmIeiklwQOxsZuYj/SOWkT33GlTpaG1UdFGjdWjPcxJ1CWZAX3wA7tarz8E2YHF6KiW5HTapPlXw==} @@ -6601,8 +6657,8 @@ packages: karma-source-map-support@1.4.0: resolution: {integrity: sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==} - keycharm@0.2.0: - resolution: {integrity: sha512-i/XBRTiLqRConPKioy2oq45vbv04e8x59b0mnsIRQM+7Ec/8BC7UcL5pnC4FMeGb8KwG7q4wOMw7CtNZf5tiIg==} + keycharm@0.4.0: + resolution: {integrity: sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==} keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -6654,8 +6710,8 @@ packages: engines: {node: '>=16'} hasBin: true - libphonenumber-js@1.12.40: - resolution: {integrity: sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==} + libphonenumber-js@1.13.2: + resolution: {integrity: sha512-S3kmBrptp3yRTm83NUcHy9g1vbwiWMzI8WvY22+koBJ6zkRteLnedBL2VX0MIAGwx2yiyxX4J85pceZyQ6ffgg==} license-webpack-plugin@4.0.2: resolution: {integrity: sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==} @@ -6703,24 +6759,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -6753,8 +6813,8 @@ packages: resolution: {integrity: sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==} engines: {node: '>=13.2.0'} - loader-runner@4.3.1: - resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + loader-runner@4.3.2: + resolution: {integrity: sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==} engines: {node: '>=6.11.5'} loader-utils@2.0.4: @@ -6831,12 +6891,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.3: - resolution: {integrity: sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==} - engines: {node: 20 || >=22} - - lru-cache@11.3.5: - resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -6905,8 +6961,8 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - memfs@4.57.1: - resolution: {integrity: sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==} + memfs@4.57.2: + resolution: {integrity: sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==} peerDependencies: tslib: '2' @@ -6975,10 +7031,6 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.2.4: - resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} - engines: {node: 18 || 20 || >=22} - minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -7049,8 +7101,8 @@ packages: resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} hasBin: true - msgpackr@1.11.9: - resolution: {integrity: sha512-FkoAAyyA6HM8wL882EcEyFZ9s7hVADSwG9xrVx3dxxNQAtgADTrJoEWivID82Iv1zWDsv/OtbrrcZAzGzOMdNw==} + msgpackr@1.11.12: + resolution: {integrity: sha512-RBdJ1Un7yGlXWajrkxcSa93nvQ0w4zBf60c0yYv7YtBelP8H2FA7XsfBbMHtXKXUMUxH7zV3Zuozh+kUQWhHvg==} multer@2.1.1: resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} @@ -7064,11 +7116,6 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -7132,20 +7179,24 @@ packages: node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + node-gyp-build-optional-packages@5.2.2: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true - node-gyp@12.2.0: - resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} + node-gyp@12.3.0: + resolution: {integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.37: - resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} nomnom@1.5.2: resolution: {integrity: sha512-fiVbT7BqxiQqjlR9U3FDGOSERFCKoXVCdxV2FwZuNN7/cmJ42iQx35nUFOAFDcyvemu9Adp+IlsCGlKQYLmBKw==} @@ -7222,6 +7273,10 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + object.fromentries@2.0.8: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} @@ -7498,13 +7553,13 @@ packages: resolution: {integrity: sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==} engines: {node: '>=16.0.0'} - playwright-core@1.58.2: - resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} engines: {node: '>=18'} hasBin: true - playwright@1.58.2: - resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} engines: {node: '>=18'} hasBin: true @@ -7575,12 +7630,8 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.10: - resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -7603,9 +7654,6 @@ packages: resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} engines: {node: '>=20'} - pq@0.0.3: - resolution: {integrity: sha512-Km5+CIz9f/bIfk5XDnKEeXpkufmvdfqKLz+rDQOSA/oCALg9oYdWn2puEZoF4453/5/k+u+t7lJU9Aqw9vQUDQ==} - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -7614,8 +7662,8 @@ packages: resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} hasBin: true @@ -7623,6 +7671,10 @@ packages: resolution: {integrity: sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} @@ -7673,14 +7725,6 @@ packages: qrcode-generator@1.5.2: resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==} - qs@6.14.2: - resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} - engines: {node: '>=0.6'} - - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} - engines: {node: '>=0.6'} - qs@6.15.1: resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} engines: {node: '>=0.6'} @@ -7709,6 +7753,9 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + read-pkg@9.0.1: resolution: {integrity: sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==} engines: {node: '>=18'} @@ -7732,10 +7779,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - redis@5.11.0: - resolution: {integrity: sha512-YwXjATVDT+AuxcyfOwZn046aml9jMlQPvU1VXIlLDVAExe0u93aTfPYSeRgG4p9Q/Jlkj+LXJ1XEoFV+j2JKcQ==} - engines: {node: '>= 18'} - reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -7779,10 +7822,6 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - requireindex@1.2.0: - resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==} - engines: {node: '>=0.10.5'} - requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} @@ -7805,13 +7844,13 @@ packages: resolution: {integrity: sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==} engines: {node: '>=12'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} engines: {node: '>= 0.4'} hasBin: true @@ -7850,18 +7889,18 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.0-rc.15: - resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + rolldown@1.0.0-rc.4: + resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-rc.4: - resolution: {integrity: sha512-V2tPDUrY3WSevrvU2E41ijZlpF+5PbZu4giH+VpNraaadsJGHa4fR6IFwsocVwEXDoAdIv5qgPPxgrvKAOIPtA==} + rolldown@1.0.1: + resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.60.1: - resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + rollup@4.60.4: + resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -7888,8 +7927,8 @@ packages: rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -7954,8 +7993,8 @@ packages: resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} engines: {node: '>= 10.13.0'} - secretlint@11.4.1: - resolution: {integrity: sha512-G3ESOL10gASIYOh9z60JdCCCnocy6TpjtFnYvzKkipX5Oh6fH/hHhQcI/Cu36+8jUvPBeREg9pl5T0e6YPf3iw==} + secretlint@11.7.1: + resolution: {integrity: sha512-igo+3xtwOisz3Ge0V1t6SzCnOLXSHjRIODZpqppUmfkb0EE0EbsEMdoUuunaacxffq3NTYSzpZcjbfrPYuO+qA==} engines: {node: '>=20.0.0'} hasBin: true @@ -7979,6 +8018,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -8116,8 +8160,8 @@ packages: resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} engines: {node: '>= 14'} - socks@2.8.7: - resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} source-map-js@1.2.1: @@ -8216,8 +8260,8 @@ packages: resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} engines: {node: '>=18'} - stdin-discarder@0.3.1: - resolution: {integrity: sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==} + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} engines: {node: '>=18'} stop-iteration-iterator@1.1.0: @@ -8247,8 +8291,8 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.2.0: - resolution: {integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==} + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} engines: {node: '>=20'} string.prototype.trim@1.2.10: @@ -8346,35 +8390,62 @@ packages: resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} engines: {node: '>=10.0.0'} - tablesort@5.7.0: - resolution: {integrity: sha512-irnN1HPD08466v6DHKR1+gqZ2be2+QZBDIGTM1DFGoWywY+d38bFtfsuUqBbMGkqaMyYE1uPxE7p0AM5cmbRSA==} + tablesort@5.7.1: + resolution: {integrity: sha512-wm+xSponmlN1spiuwGlrvNGoG8fEso+y86OOjS+Pl0Qsje/cOVSfUvELcXILWc8/sgEdY/NGMPo0qhFGyYKnwQ==} engines: {node: '>= 22', npm: '>= 10'} - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tar@7.5.13: - resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + tar@7.5.15: + resolution: {integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==} engines: {node: '>=18'} terminal-link@4.0.0: resolution: {integrity: sha512-lk+vH+MccxNqgVqSnkMVKx4VLJfnLjDBGzH16JVZjKE2DoxP57s6/vt6JmXV5I3jBcfGrxNrYtC+mPtU7WJztA==} engines: {node: '>=18'} - terser-webpack-plugin@5.4.0: - resolution: {integrity: sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==} + terser-webpack-plugin@5.6.0: + resolution: {integrity: sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==} engines: {node: '>= 10.13.0'} peerDependencies: + '@minify-html/node': '*' '@swc/core': '*' + '@swc/css': '*' + '@swc/html': '*' + clean-css: '*' + cssnano: '*' + csso: '*' esbuild: '*' + html-minifier-terser: '*' + lightningcss: '*' + postcss: '*' uglify-js: '*' webpack: ^5.1.0 peerDependenciesMeta: + '@minify-html/node': + optional: true '@swc/core': optional: true + '@swc/css': + optional: true + '@swc/html': + optional: true + clean-css: + optional: true + cssnano: + optional: true + csso: + optional: true esbuild: optional: true + html-minifier-terser: + optional: true + lightningcss: + optional: true + postcss: + optional: true uglify-js: optional: true @@ -8383,8 +8454,8 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.46.1: - resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + terser@5.47.1: + resolution: {integrity: sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==} engines: {node: '>=10'} hasBin: true @@ -8507,8 +8578,8 @@ packages: jest-util: optional: true - ts-loader@9.5.4: - resolution: {integrity: sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==} + ts-loader@9.5.7: + resolution: {integrity: sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==} engines: {node: '>=12.0.0'} peerDependencies: typescript: '*' @@ -8576,9 +8647,9 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - type-is@2.0.1: - resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} - engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} @@ -8602,8 +8673,8 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm@0.3.28: - resolution: {integrity: sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==} + typeorm@0.3.29: + resolution: {integrity: sha512-wwPEX/df4l72gCmOsrs0otJZYLGA9lLQkUZCkukbsymEycV4zXv2KM7wU7v2r8L01TaCgY9ApSSqHQWBOUhEoQ==} engines: {node: '>=16.13.0'} hasBin: true peerDependencies: @@ -8657,12 +8728,12 @@ packages: typeorm-aurora-data-api-driver: optional: true - typescript-eslint@8.57.2: - resolution: {integrity: sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==} + typescript-eslint@8.59.3: + resolution: {integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.0.0' + typescript: '>=4.8.4 <6.1.0' typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} @@ -8689,17 +8760,21 @@ packages: underscore@1.13.8: resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - undici@7.24.0: - resolution: {integrity: sha512-jxytwMHhsbdpBXxLAcuu0fzlQeXCNnWdDyRHpvWsUl8vd98UwYdl9YTyn8/HcpcJPC3pwUveefsa3zTxyD/ERg==} - engines: {node: '>=20.18.1'} + undici@6.25.0: + resolution: {integrity: sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg==} + engines: {node: '>=18.17'} undici@7.24.4: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} engines: {node: '>=20.18.1'} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + unicode-canonical-property-names-ecmascript@2.0.1: resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} @@ -8777,8 +8852,8 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} - valibot@1.3.1: - resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} + valibot@1.4.0: + resolution: {integrity: sha512-iC/x7fVcSyOwlm/VSt7RlHnzNGLGvR9GnxdifUeWoCJo0q4ZZvrVkIHC6faTlkxG47I2Y4UrFquPuVHCrOnrLg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -8792,8 +8867,8 @@ packages: resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} engines: {node: ^20.17.0 || >=22.9.0} - validator@13.15.26: - resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} + validator@13.15.35: + resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} engines: {node: '>= 0.10'} vary@1.1.2: @@ -8804,14 +8879,14 @@ packages: resolution: {integrity: sha512-Ck0EJbAGxHwprkzFO966t4/5QkRuzh+/I1RxhLgUKKwEn+Cd8NwM60mE3AqBZg5gYODoXW0EFsQvbZjRlvdqbg==} engines: {node: '>=4'} - vis-data@8.0.3: - resolution: {integrity: sha512-jhnb6rJNqkKR1Qmlay0VuDXY9ZlvAnYN1udsrP4U+krgZEq7C0yNSKdZqmnCe13mdnf9AdVcdDGFOzy2mpPoqw==} + vis-data@8.0.4: + resolution: {integrity: sha512-TsN0sMHqIRpdfg6TNPtfdINpkgxtnQP6JNWCaiSwvou5seXqKiP5eERkaBg+Y56wyJ4FZTeOEs/dEmWEPrpltQ==} peerDependencies: uuid: '>=11.1.1' vis-util: '>=6.0.0' - vis-network@10.0.2: - resolution: {integrity: sha512-qPl8GLYBeHEFqiTqp4VBbYQIJ2EA8KLr7TstA2E8nJxfEHaKCU81hQLz7hhq11NUpHbMaRzBjW5uZpVKJ45/wA==} + vis-network@10.1.0: + resolution: {integrity: sha512-D7b5p/C6SwWv1BlH9EDdtP0Tje/PJzSBWKef9qy2DyTC14QB7KBcnAZxIyW2m7mFYyfoeR+k5GF747zDcIhaKA==} peerDependencies: '@egjs/hammerjs': ^2.0.0 component-emitter: ^1.3.0 || ^2.0.0 @@ -8867,13 +8942,13 @@ packages: yaml: optional: true - vite@8.0.8: - resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + vite@8.0.13: + resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.0 + '@vitejs/devtools': ^0.1.18 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -8964,8 +9039,8 @@ packages: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} - webpack-sources@3.3.4: - resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + webpack-sources@3.4.1: + resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==} engines: {node: '>=10.13.0'} webpack-subresource-integrity@5.1.0: @@ -9094,8 +9169,8 @@ packages: utf-8-validate: optional: true - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + ws@8.20.1: + resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -9199,8 +9274,8 @@ packages: zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} - zone.js@0.16.1: - resolution: {integrity: sha512-dpvY17vxYIW3+bNrP0ClUlaiY0CiIRK3tnoLaGoQsQcY9/I/NpzIWQ7tQNhbV7LacQMpCII6wVzuL3tuWOyfuA==} + zone.js@0.16.2: + resolution: {integrity: sha512-Eky7p2Z1Ig3NnbfodSPoARCjKBSTFMnE/ACsP1L/XJEfY4SdOFce19BsUCWVwL6K5ABZFy5J3bjcMWffX+YM3Q==} snapshots: @@ -9325,10 +9400,10 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular-builders/common@5.0.3(@types/node@25.5.0)(chokidar@5.0.0)(typescript@5.9.3)': + '@angular-builders/common@5.0.3(@types/node@25.8.0)(chokidar@5.0.0)(typescript@5.9.3)': dependencies: '@angular-devkit/core': 21.2.5(chokidar@5.0.0) - ts-node: 10.9.2(@types/node@25.5.0)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.8.0)(typescript@5.9.3) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@swc/core' @@ -9337,17 +9412,17 @@ snapshots: - chokidar - typescript - '@angular-builders/jest@21.0.3(07bd7b923bb6f14c0b013a3dc874ca68)': + '@angular-builders/jest@21.0.3(ee59e09cf9085142f51ff0bc4edd671f)': dependencies: - '@angular-builders/common': 5.0.3(@types/node@25.5.0)(chokidar@5.0.0)(typescript@5.9.3) + '@angular-builders/common': 5.0.3(@types/node@25.8.0)(chokidar@5.0.0)(typescript@5.9.3) '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) - '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3) + '@angular-devkit/build-angular': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.8.0)(chokidar@5.0.0)(jest-environment-jsdom@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(jiti@2.7.0)(lightningcss@1.32.0)(typescript@5.9.3) '@angular-devkit/core': 21.2.5(chokidar@5.0.0) '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) - jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-preset-angular: 16.1.1(28bd700369ae87f04923e58e037d4bde) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))) + jest: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) + jest-preset-angular: 16.1.5(38ed6dc12e43f709a9a6350e673a80e1) lodash: 4.18.1 transitivePeerDependencies: - '@angular/platform-browser' @@ -9377,13 +9452,13 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jest-environment-jsdom@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(jiti@2.6.1)(lightningcss@1.32.0)(typescript@5.9.3)': + '@angular-devkit/build-angular@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.8.0)(chokidar@5.0.0)(jest-environment-jsdom@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(jiti@2.7.0)(lightningcss@1.32.0)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) - '@angular-devkit/build-webpack': 0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3)) + '@angular-devkit/build-webpack': 0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)))(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) - '@angular/build': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.10)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) + '@angular/build': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.8.0)(chokidar@5.0.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3) '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -9395,62 +9470,69 @@ snapshots: '@babel/preset-env': 7.29.0(@babel/core@7.29.0) '@babel/runtime': 7.28.6 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) + '@ngtools/webpack': 21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) ansi-colors: 4.1.3 - autoprefixer: 10.4.27(postcss@8.5.10) - babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)) + autoprefixer: 10.4.27(postcss@8.5.14) + babel-loader: 10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) browserslist: 4.28.2 - copy-webpack-plugin: 14.0.0(webpack@5.105.2(esbuild@0.27.3)) - css-loader: 7.1.3(webpack@5.105.2(esbuild@0.27.3)) + copy-webpack-plugin: 14.0.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) + css-loader: 7.1.3(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) esbuild-wasm: 0.27.3 http-proxy-middleware: 3.0.5 istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 karma-source-map-support: 1.4.0 less: 4.4.2 - less-loader: 12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)) - license-webpack-plugin: 4.0.2(webpack@5.105.2(esbuild@0.27.3)) + less-loader: 12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) + license-webpack-plugin: 4.0.2(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.10.0(webpack@5.105.2(esbuild@0.27.3)) + mini-css-extract-plugin: 2.10.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) open: 11.0.0 ora: 9.3.0 picomatch: 4.0.4 piscina: 5.1.4 - postcss: 8.5.10 - postcss-loader: 8.2.0(postcss@8.5.10)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)) + postcss: 8.5.14 + postcss-loader: 8.2.0(postcss@8.5.14)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 sass: 1.97.3 - sass-loader: 16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)) + sass-loader: 16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) semver: 7.7.4 - source-map-loader: 5.0.0(webpack@5.105.2(esbuild@0.27.3)) + source-map-loader: 5.0.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) source-map-support: 0.5.21 terser: 5.46.0 tinyglobby: 0.2.15 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.9.3 - webpack: 5.105.2(esbuild@0.27.3) - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) - webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) + webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.105.2(esbuild@0.27.3)) + webpack-subresource-integrity: 5.1.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) optionalDependencies: - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) esbuild: 0.27.3 - jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-environment-jsdom: 30.3.0 + jest: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) + jest-environment-jsdom: 30.4.1 transitivePeerDependencies: - '@angular/compiler' - '@emnapi/core' - '@emnapi/runtime' + - '@minify-html/node' - '@rspack/core' - '@swc/core' + - '@swc/css' + - '@swc/html' - '@types/node' - bufferutil - chokidar + - clean-css + - cssnano + - csso - debug + - html-minifier-terser - html-webpack-plugin - jiti - lightningcss @@ -9466,16 +9548,16 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)))(webpack@5.105.2(esbuild@0.27.3))': + '@angular-devkit/build-webpack@0.2102.7(chokidar@5.0.0)(webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)))(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14))': dependencies: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) rxjs: 7.8.2 - webpack: 5.105.2(esbuild@0.27.3) - webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) + webpack-dev-server: 5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) transitivePeerDependencies: - chokidar - '@angular-devkit/core@19.2.17(chokidar@4.0.3)': + '@angular-devkit/core@19.2.24(chokidar@4.0.3)': dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) @@ -9486,18 +9568,18 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/core@19.2.24(chokidar@4.0.3)': + '@angular-devkit/core@21.1.0(chokidar@5.0.0)': dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) jsonc-parser: 3.3.1 picomatch: 4.0.4 - rxjs: 7.8.1 - source-map: 0.7.4 + rxjs: 7.8.2 + source-map: 0.7.6 optionalDependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 - '@angular-devkit/core@21.1.0(chokidar@5.0.0)': + '@angular-devkit/core@21.2.11(chokidar@5.0.0)': dependencies: ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) @@ -9530,11 +9612,11 @@ snapshots: optionalDependencies: chokidar: 5.0.0 - '@angular-devkit/schematics-cli@19.2.24(@types/node@25.5.0)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.24(@types/node@25.8.0)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@25.5.0) + '@inquirer/prompts': 7.3.2(@types/node@25.8.0) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -9542,16 +9624,6 @@ snapshots: - '@types/node' - chokidar - '@angular-devkit/schematics@19.2.17(chokidar@4.0.3)': - dependencies: - '@angular-devkit/core': 19.2.17(chokidar@4.0.3) - jsonc-parser: 3.3.1 - magic-string: 0.30.17 - ora: 5.4.1 - rxjs: 7.8.1 - transitivePeerDependencies: - - chokidar - '@angular-devkit/schematics@19.2.24(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) @@ -9572,6 +9644,16 @@ snapshots: transitivePeerDependencies: - chokidar + '@angular-devkit/schematics@21.2.11(chokidar@5.0.0)': + dependencies: + '@angular-devkit/core': 21.2.11(chokidar@5.0.0) + jsonc-parser: 3.3.1 + magic-string: 0.30.21 + ora: 9.3.0 + rxjs: 7.8.2 + transitivePeerDependencies: + - chokidar + '@angular-devkit/schematics@21.2.5(chokidar@5.0.0)': dependencies: '@angular-devkit/core': 21.2.5(chokidar@5.0.0) @@ -9592,46 +9674,79 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-eslint/builder@21.3.1(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/builder@21.3.1(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) '@angular-devkit/core': 21.2.5(chokidar@5.0.0) - '@angular/cli': 21.2.7(@types/node@25.5.0)(chokidar@5.0.0) - eslint: 10.1.0(jiti@2.6.1) + '@angular/cli': 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) + eslint: 10.4.0(jiti@2.7.0) + typescript: 5.9.3 + transitivePeerDependencies: + - chokidar + + '@angular-eslint/builder@21.4.0(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-devkit/architect': 0.2102.5(chokidar@5.0.0) + '@angular-devkit/core': 21.2.5(chokidar@5.0.0) + '@angular/cli': 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - chokidar '@angular-eslint/bundled-angular-compiler@21.3.1': {} - '@angular-eslint/eslint-plugin-template@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/bundled-angular-compiler@21.4.0': {} + + '@angular-eslint/eslint-plugin-template@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 21.3.1 - '@angular-eslint/template-parser': 21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/template-parser': 21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) aria-query: 5.3.2 axobject-query: 4.1.0 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 - '@angular-eslint/eslint-plugin@21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/eslint-plugin-template@21.4.0(@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.4.0 + '@angular-eslint/template-parser': 21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/utils': 21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + aria-query: 5.3.2 + axobject-query: 4.1.0 + eslint: 10.4.0(jiti@2.7.0) + typescript: 5.9.3 + + '@angular-eslint/eslint-plugin@21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 21.3.1 - '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@angular-eslint/utils': 21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + + '@angular-eslint/eslint-plugin@21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.4.0 + '@angular-eslint/utils': 21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 - '@angular-eslint/schematics@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/schematics@21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-devkit/core': 21.2.5(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.5(chokidar@5.0.0) - '@angular-eslint/eslint-plugin': 21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin-template': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular/cli': 21.2.7(@types/node@25.5.0)(chokidar@5.0.0) + '@angular-eslint/eslint-plugin': 21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular/cli': 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) ignore: 7.0.5 semver: 7.7.4 strip-json-comments: 3.1.1 @@ -9643,26 +9758,58 @@ snapshots: - eslint - typescript - '@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/schematics@21.4.0(@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-devkit/core': 21.2.5(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.5(chokidar@5.0.0) + '@angular-eslint/eslint-plugin': 21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.4.0(@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular/cli': 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) + ignore: 7.0.5 + semver: 7.7.4 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - '@angular-eslint/template-parser' + - '@typescript-eslint/types' + - '@typescript-eslint/utils' + - chokidar + - eslint + - typescript + + '@angular-eslint/template-parser@21.3.1(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 21.3.1 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-scope: 9.1.2 typescript: 5.9.3 - '@angular-eslint/utils@21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.4.0 + eslint: 10.4.0(jiti@2.7.0) + eslint-scope: 9.1.2 + typescript: 5.9.3 + + '@angular-eslint/utils@21.3.1(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@angular-eslint/bundled-angular-compiler': 21.3.1 - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) + typescript: 5.9.3 + + '@angular-eslint/utils@21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': + dependencies: + '@angular-eslint/bundled-angular-compiler': 21.4.0 + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 - '@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': + '@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 - '@angular/build@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.0)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.10)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': + '@angular/build@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@25.8.0)(chokidar@5.0.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(postcss@8.5.14)(terser@5.46.0)(tslib@2.8.1)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) @@ -9671,8 +9818,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.21(@types/node@25.5.0) - '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)) + '@inquirer/confirm': 5.1.21(@types/node@25.8.0) + '@vitejs/plugin-basic-ssl': 2.1.4(vite@7.3.2(@types/node@25.8.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0)) beasties: 0.4.1 browserslist: 4.28.2 esbuild: 0.27.3 @@ -9685,7 +9832,7 @@ snapshots: parse5-html-rewriting-stream: 8.0.0 picomatch: 4.0.4 piscina: 5.1.4 - rolldown: 1.0.0-rc.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + rolldown: 1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) sass: 1.97.3 semver: 7.7.4 source-map-support: 0.5.21 @@ -9693,14 +9840,14 @@ snapshots: tslib: 2.8.1 typescript: 5.9.3 undici: 7.24.4 - vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + vite: 7.3.2(@types/node@25.8.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) watchpack: 2.5.1 optionalDependencies: - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) less: 4.4.2 lmdb: 3.5.1 - postcss: 8.5.10 + postcss: 8.5.14 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' @@ -9716,22 +9863,22 @@ snapshots: - tsx - yaml - '@angular/cdk@21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/cdk@21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) parse5: 8.0.1 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0)': + '@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0)': dependencies: '@angular-devkit/architect': 0.2102.7(chokidar@5.0.0) '@angular-devkit/core': 21.2.7(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.7(chokidar@5.0.0) - '@inquirer/prompts': 7.10.1(@types/node@25.5.0) - '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.5.0))(@types/node@25.5.0)(listr2@9.0.5) + '@inquirer/prompts': 7.10.1(@types/node@25.8.0) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.8.0))(@types/node@25.8.0)(listr2@9.0.5) '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) '@schematics/angular': 21.2.7(chokidar@5.0.0) '@yarnpkg/lockfile': 1.1.0 @@ -9751,9 +9898,9 @@ snapshots: - chokidar - supports-color - '@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2)': + '@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2)': dependencies: - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) rxjs: 7.8.2 tslib: 2.8.1 @@ -9765,7 +9912,7 @@ snapshots: chokidar: 5.0.0 convert-source-map: 1.9.0 reflect-metadata: 0.2.2 - semver: 7.7.4 + semver: 7.8.0 tslib: 2.8.1 yargs: 18.0.0 optionalDependencies: @@ -9777,56 +9924,56 @@ snapshots: dependencies: tslib: 2.8.1 - '@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)': + '@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: '@angular/compiler': 21.2.9 - zone.js: 0.16.1 + zone.js: 0.16.2 - '@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 '@angular/language-service@21.2.6': {} - '@angular/material@21.2.7(0d5a9f31d3db06fde122ff91a62de812)': + '@angular/material@21.2.7(9b1d9887e76b4bf579d2fa4e777cf88c)': dependencies: - '@angular/cdk': 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/cdk': 21.2.7(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser-dynamic@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))': + '@angular/platform-browser-dynamic@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) '@angular/compiler': 21.2.9 - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) tslib: 2.8.1 - '@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': + '@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 optionalDependencies: - '@angular/animations': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/animations': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) - '@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)': + '@angular/router@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) rxjs: 7.8.2 tslib: 2.8.1 @@ -9852,7 +9999,7 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.3': {} '@babel/core@7.28.6': dependencies: @@ -9861,7 +10008,7 @@ snapshots: '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -9881,7 +10028,7 @@ snapshots: '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -9896,7 +10043,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -9908,13 +10055,13 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 @@ -9927,7 +10074,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 @@ -10082,7 +10229,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 @@ -10345,7 +10492,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10353,7 +10500,7 @@ snapshots: '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10361,7 +10508,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10369,7 +10516,7 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10767,7 +10914,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.6)': dependencies: '@babel/core': 7.28.6 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10775,7 +10922,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10784,7 +10931,7 @@ snapshots: dependencies: '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.28.6) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10793,7 +10940,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -10956,7 +11103,7 @@ snapshots: '@babel/preset-env@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.28.6 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 @@ -11032,7 +11179,7 @@ snapshots: '@babel/preset-env@7.29.0(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 @@ -11127,7 +11274,7 @@ snapshots: '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': @@ -11135,7 +11282,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -11159,7 +11306,7 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@compodoc/compodoc@1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(typescript@5.9.3)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1))': + '@compodoc/compodoc@1.2.1(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.4.0)(typescript@5.9.3)(vis-data@8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1))': dependencies: '@angular-devkit/schematics': 21.1.0(chokidar@5.0.0) '@babel/core': 7.28.6 @@ -11179,7 +11326,7 @@ snapshots: es6-shim: 0.35.8 fancy-log: 2.0.0 fast-glob: 3.3.3 - fs-extra: 11.3.4 + fs-extra: 11.3.5 glob: 13.0.6 handlebars: 4.7.9 html-entities: 2.6.0 @@ -11197,13 +11344,13 @@ snapshots: picocolors: 1.1.1 polka: 0.5.2 prismjs: 1.30.0 - semver: 7.7.4 + semver: 7.8.0 sirv: 3.0.2 svg-pan-zoom: 3.6.2 - tablesort: 5.7.0 + tablesort: 5.7.1 ts-morph: 27.0.2 uuid: 13.0.1 - vis-network: 10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@13.0.1)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + vis-network: 10.1.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.4.0)(uuid@13.0.1)(vis-data@8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) transitivePeerDependencies: - '@egjs/hammerjs' - component-emitter @@ -11243,7 +11390,7 @@ snapshots: '@aduh95/viz.js': 3.4.0 '@compodoc/ngd-core': 2.1.1 dot: 2.0.0-beta.1 - fs-extra: 11.3.4 + fs-extra: 11.3.5 '@cspotcode/source-map-support@0.8.1': dependencies: @@ -11275,29 +11422,13 @@ snapshots: dependencies: '@types/hammerjs': 2.0.46 - '@emnapi/core@1.9.1': - dependencies: - '@emnapi/wasi-threads': 1.2.0 - tslib: 2.8.1 - optional: true - - '@emnapi/core@1.9.2': + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.9.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.9.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.0': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true @@ -11310,202 +11441,196 @@ snapshots: '@esbuild/aix-ppc64@0.27.3': optional: true - '@esbuild/aix-ppc64@0.27.4': + '@esbuild/aix-ppc64@0.28.0': optional: true '@esbuild/android-arm64@0.27.3': optional: true - '@esbuild/android-arm64@0.27.4': + '@esbuild/android-arm64@0.28.0': optional: true '@esbuild/android-arm@0.27.3': optional: true - '@esbuild/android-arm@0.27.4': + '@esbuild/android-arm@0.28.0': optional: true '@esbuild/android-x64@0.27.3': optional: true - '@esbuild/android-x64@0.27.4': + '@esbuild/android-x64@0.28.0': optional: true '@esbuild/darwin-arm64@0.27.3': optional: true - '@esbuild/darwin-arm64@0.27.4': + '@esbuild/darwin-arm64@0.28.0': optional: true '@esbuild/darwin-x64@0.27.3': optional: true - '@esbuild/darwin-x64@0.27.4': + '@esbuild/darwin-x64@0.28.0': optional: true '@esbuild/freebsd-arm64@0.27.3': optional: true - '@esbuild/freebsd-arm64@0.27.4': + '@esbuild/freebsd-arm64@0.28.0': optional: true '@esbuild/freebsd-x64@0.27.3': optional: true - '@esbuild/freebsd-x64@0.27.4': + '@esbuild/freebsd-x64@0.28.0': optional: true '@esbuild/linux-arm64@0.27.3': optional: true - '@esbuild/linux-arm64@0.27.4': + '@esbuild/linux-arm64@0.28.0': optional: true '@esbuild/linux-arm@0.27.3': optional: true - '@esbuild/linux-arm@0.27.4': + '@esbuild/linux-arm@0.28.0': optional: true '@esbuild/linux-ia32@0.27.3': optional: true - '@esbuild/linux-ia32@0.27.4': + '@esbuild/linux-ia32@0.28.0': optional: true '@esbuild/linux-loong64@0.27.3': optional: true - '@esbuild/linux-loong64@0.27.4': + '@esbuild/linux-loong64@0.28.0': optional: true '@esbuild/linux-mips64el@0.27.3': optional: true - '@esbuild/linux-mips64el@0.27.4': + '@esbuild/linux-mips64el@0.28.0': optional: true '@esbuild/linux-ppc64@0.27.3': optional: true - '@esbuild/linux-ppc64@0.27.4': + '@esbuild/linux-ppc64@0.28.0': optional: true '@esbuild/linux-riscv64@0.27.3': optional: true - '@esbuild/linux-riscv64@0.27.4': + '@esbuild/linux-riscv64@0.28.0': optional: true '@esbuild/linux-s390x@0.27.3': optional: true - '@esbuild/linux-s390x@0.27.4': + '@esbuild/linux-s390x@0.28.0': optional: true '@esbuild/linux-x64@0.27.3': optional: true - '@esbuild/linux-x64@0.27.4': + '@esbuild/linux-x64@0.28.0': optional: true '@esbuild/netbsd-arm64@0.27.3': optional: true - '@esbuild/netbsd-arm64@0.27.4': + '@esbuild/netbsd-arm64@0.28.0': optional: true '@esbuild/netbsd-x64@0.27.3': optional: true - '@esbuild/netbsd-x64@0.27.4': + '@esbuild/netbsd-x64@0.28.0': optional: true '@esbuild/openbsd-arm64@0.27.3': optional: true - '@esbuild/openbsd-arm64@0.27.4': + '@esbuild/openbsd-arm64@0.28.0': optional: true '@esbuild/openbsd-x64@0.27.3': optional: true - '@esbuild/openbsd-x64@0.27.4': + '@esbuild/openbsd-x64@0.28.0': optional: true '@esbuild/openharmony-arm64@0.27.3': optional: true - '@esbuild/openharmony-arm64@0.27.4': + '@esbuild/openharmony-arm64@0.28.0': optional: true '@esbuild/sunos-x64@0.27.3': optional: true - '@esbuild/sunos-x64@0.27.4': + '@esbuild/sunos-x64@0.28.0': optional: true '@esbuild/win32-arm64@0.27.3': optional: true - '@esbuild/win32-arm64@0.27.4': + '@esbuild/win32-arm64@0.28.0': optional: true '@esbuild/win32-ia32@0.27.3': optional: true - '@esbuild/win32-ia32@0.27.4': + '@esbuild/win32-ia32@0.28.0': optional: true '@esbuild/win32-x64@0.27.3': optional: true - '@esbuild/win32-x64@0.27.4': + '@esbuild/win32-x64@0.28.0': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.0(jiti@2.7.0))': dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.0.3(eslint@10.1.0(jiti@2.6.1))': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/core': 1.1.1 - optionalDependencies: - eslint: 10.1.0(jiti@2.6.1) - - '@eslint/config-array@0.23.3': - dependencies: - '@eslint/object-schema': 3.0.3 + '@eslint/object-schema': 3.0.5 debug: 4.4.3 - minimatch: 10.2.4 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.5.3': + '@eslint/config-helpers@0.6.0': dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.1 - '@eslint/core@1.1.1': + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.1.0(jiti@2.6.1))': + '@eslint/js@10.0.1(eslint@10.4.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) - '@eslint/object-schema@3.0.3': {} + '@eslint/object-schema@3.0.5': {} - '@eslint/plugin-kit@0.6.1': + '@eslint/plugin-kit@0.7.1': dependencies: - '@eslint/core': 1.1.1 + '@eslint/core': 1.2.1 levn: 0.4.1 - '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': + '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) '@fortawesome/fontawesome-svg-core': 7.2.0 tslib: 2.8.1 @@ -11530,160 +11655,165 @@ snapshots: '@harperfast/extended-iterable@1.0.3': optional: true - '@hono/node-server@1.19.14(hono@4.12.18)': + '@hono/node-server@1.19.14(hono@4.12.19)': dependencies: - hono: 4.12.18 + hono: 4.12.19 - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.5.0)': + '@inquirer/checkbox@4.3.2(@types/node@25.8.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/confirm@5.1.21(@types/node@25.5.0)': + '@inquirer/confirm@5.1.21(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/core@10.3.2(@types/node@25.5.0)': + '@inquirer/core@10.3.2(@types/node@25.8.0)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/editor@4.2.23(@types/node@25.5.0)': + '@inquirer/editor@4.2.23(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/external-editor': 1.0.3(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/external-editor': 1.0.3(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/expand@4.0.23(@types/node@25.5.0)': + '@inquirer/expand@4.0.23(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/external-editor@1.0.3(@types/node@25.5.0)': + '@inquirer/external-editor@1.0.3(@types/node@25.8.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.5.0)': + '@inquirer/input@4.3.1(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/number@3.0.23(@types/node@25.5.0)': + '@inquirer/number@3.0.23(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/password@4.0.23(@types/node@25.5.0)': + '@inquirer/password@4.0.23(@types/node@25.8.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 - - '@inquirer/prompts@7.10.1(@types/node@25.5.0)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.5.0) - '@inquirer/confirm': 5.1.21(@types/node@25.5.0) - '@inquirer/editor': 4.2.23(@types/node@25.5.0) - '@inquirer/expand': 4.0.23(@types/node@25.5.0) - '@inquirer/input': 4.3.1(@types/node@25.5.0) - '@inquirer/number': 3.0.23(@types/node@25.5.0) - '@inquirer/password': 4.0.23(@types/node@25.5.0) - '@inquirer/rawlist': 4.1.11(@types/node@25.5.0) - '@inquirer/search': 3.2.2(@types/node@25.5.0) - '@inquirer/select': 4.4.2(@types/node@25.5.0) + '@types/node': 25.8.0 + + '@inquirer/prompts@7.10.1(@types/node@25.8.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.8.0) + '@inquirer/confirm': 5.1.21(@types/node@25.8.0) + '@inquirer/editor': 4.2.23(@types/node@25.8.0) + '@inquirer/expand': 4.0.23(@types/node@25.8.0) + '@inquirer/input': 4.3.1(@types/node@25.8.0) + '@inquirer/number': 3.0.23(@types/node@25.8.0) + '@inquirer/password': 4.0.23(@types/node@25.8.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.8.0) + '@inquirer/search': 3.2.2(@types/node@25.8.0) + '@inquirer/select': 4.4.2(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 - - '@inquirer/prompts@7.3.2(@types/node@25.5.0)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.5.0) - '@inquirer/confirm': 5.1.21(@types/node@25.5.0) - '@inquirer/editor': 4.2.23(@types/node@25.5.0) - '@inquirer/expand': 4.0.23(@types/node@25.5.0) - '@inquirer/input': 4.3.1(@types/node@25.5.0) - '@inquirer/number': 3.0.23(@types/node@25.5.0) - '@inquirer/password': 4.0.23(@types/node@25.5.0) - '@inquirer/rawlist': 4.1.11(@types/node@25.5.0) - '@inquirer/search': 3.2.2(@types/node@25.5.0) - '@inquirer/select': 4.4.2(@types/node@25.5.0) + '@types/node': 25.8.0 + + '@inquirer/prompts@7.3.2(@types/node@25.8.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.8.0) + '@inquirer/confirm': 5.1.21(@types/node@25.8.0) + '@inquirer/editor': 4.2.23(@types/node@25.8.0) + '@inquirer/expand': 4.0.23(@types/node@25.8.0) + '@inquirer/input': 4.3.1(@types/node@25.8.0) + '@inquirer/number': 3.0.23(@types/node@25.8.0) + '@inquirer/password': 4.0.23(@types/node@25.8.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.8.0) + '@inquirer/search': 3.2.2(@types/node@25.8.0) + '@inquirer/select': 4.4.2(@types/node@25.8.0) optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/rawlist@4.1.11(@types/node@25.5.0)': + '@inquirer/rawlist@4.1.11(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/search@3.2.2(@types/node@25.5.0)': + '@inquirer/search@3.2.2(@types/node@25.8.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/select@4.4.2(@types/node@25.5.0)': + '@inquirer/select@4.4.2(@types/node@25.8.0)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.5.0) + '@inquirer/core': 10.3.2(@types/node@25.8.0) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 - '@inquirer/type@3.0.10(@types/node@25.5.0)': + '@inquirer/type@3.0.10(@types/node@25.8.0)': optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@isaacs/cliui@8.0.2': dependencies: @@ -11706,18 +11836,18 @@ snapshots: js-yaml: 3.14.2 resolve-from: 5.0.0 - '@istanbuljs/schema@0.1.3': {} + '@istanbuljs/schema@0.1.6': {} '@jest/console@30.3.0': dependencies: '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 chalk: 4.1.2 jest-message-util: 30.3.0 jest-util: 30.3.0 slash: 3.0.0 - '@jest/core@30.3.0(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3))': + '@jest/core@30.3.0(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3))': dependencies: '@jest/console': 30.3.0 '@jest/pattern': 30.0.1 @@ -11725,14 +11855,14 @@ snapshots: '@jest/test-result': 30.3.0 '@jest/transform': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.4.0 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.3.0 - jest-config: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + jest-config: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) jest-haste-map: 30.3.0 jest-message-util: 30.3.0 jest-regex-util: 30.0.1 @@ -11754,28 +11884,41 @@ snapshots: '@jest/diff-sequences@30.3.0': {} - '@jest/environment-jsdom-abstract@30.3.0(jsdom@26.1.0)': + '@jest/diff-sequences@30.4.0': {} + + '@jest/environment-jsdom-abstract@30.4.1(jsdom@26.1.0)': dependencies: - '@jest/environment': 30.3.0 - '@jest/fake-timers': 30.3.0 - '@jest/types': 30.3.0 + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 '@types/jsdom': 21.1.7 - '@types/node': 25.5.0 - jest-mock: 30.3.0 - jest-util: 30.3.0 + '@types/node': 25.8.0 + jest-mock: 30.4.1 + jest-util: 30.4.1 jsdom: 26.1.0 '@jest/environment@30.3.0': dependencies: '@jest/fake-timers': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 jest-mock: 30.3.0 + '@jest/environment@30.4.1': + dependencies: + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.8.0 + jest-mock: 30.4.1 + '@jest/expect-utils@30.3.0': dependencies: '@jest/get-type': 30.1.0 + '@jest/expect-utils@30.4.1': + dependencies: + '@jest/get-type': 30.1.0 + '@jest/expect@30.3.0': dependencies: expect: 30.3.0 @@ -11783,15 +11926,31 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/expect@30.4.1': + dependencies: + expect: 30.4.1 + jest-snapshot: 30.4.1 + transitivePeerDependencies: + - supports-color + '@jest/fake-timers@30.3.0': dependencies: '@jest/types': 30.3.0 - '@sinonjs/fake-timers': 15.1.1 - '@types/node': 25.5.0 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 25.8.0 jest-message-util: 30.3.0 jest-mock: 30.3.0 jest-util: 30.3.0 + '@jest/fake-timers@30.4.1': + dependencies: + '@jest/types': 30.4.1 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 25.8.0 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + '@jest/get-type@30.1.0': {} '@jest/globals@30.3.0': @@ -11803,11 +11962,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/globals@30.4.1': + dependencies: + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/types': 30.4.1 + jest-mock: 30.4.1 + transitivePeerDependencies: + - supports-color + '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 jest-regex-util: 30.0.1 + '@jest/pattern@30.4.0': + dependencies: + '@types/node': 25.8.0 + jest-regex-util: 30.4.0 + '@jest/reporters@30.3.0': dependencies: '@bcoe/v8-coverage': 0.2.3 @@ -11816,7 +11989,7 @@ snapshots: '@jest/transform': 30.3.0 '@jest/types': 30.3.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.5.0 + '@types/node': 25.8.0 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -11838,7 +12011,11 @@ snapshots: '@jest/schemas@30.0.5': dependencies: - '@sinclair/typebox': 0.34.48 + '@sinclair/typebox': 0.34.49 + + '@jest/schemas@30.4.1': + dependencies: + '@sinclair/typebox': 0.34.49 '@jest/snapshot-utils@30.3.0': dependencies: @@ -11847,6 +12024,13 @@ snapshots: graceful-fs: 4.2.11 natural-compare: 1.4.0 + '@jest/snapshot-utils@30.4.1': + dependencies: + '@jest/types': 30.4.1 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + '@jest/source-map@30.0.1': dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -11886,13 +12070,42 @@ snapshots: transitivePeerDependencies: - supports-color + '@jest/transform@30.4.1': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 30.4.1 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.4.1 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + '@jest/types@30.3.0': dependencies: '@jest/pattern': 30.0.1 '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.5.0 + '@types/node': 25.8.0 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jest/types@30.4.1': + dependencies: + '@jest/pattern': 30.4.0 + '@jest/schemas': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.8.0 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -11949,58 +12162,58 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-core@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-node-to-fsa@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-fsa': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-node-utils@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-node@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-core': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) thingies: 2.6.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': dependencies: - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.57.1(tslib@2.8.1)': + '@jsonjoy.com/fs-snapshot@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) '@jsonjoy.com/json-pack': 17.67.0(tslib@2.8.1) '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) tslib: 2.8.1 @@ -12056,10 +12269,10 @@ snapshots: '@leichtgewicht/ip-codec@2.0.5': {} - '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.5.0))(@types/node@25.5.0)(listr2@9.0.5)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.8.0))(@types/node@25.8.0)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 7.10.1(@types/node@25.5.0) - '@inquirer/type': 3.0.10(@types/node@25.5.0) + '@inquirer/prompts': 7.10.1(@types/node@25.8.0) + '@inquirer/type': 3.0.10(@types/node@25.8.0) listr2: 9.0.5 transitivePeerDependencies: - '@types/node' @@ -12091,18 +12304,18 @@ snapshots: '@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.18) + '@hono/node-server': 1.19.14(hono@4.12.19) ajv: 8.18.0 ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.8 express: 5.2.1 - express-rate-limit: 8.3.2(express@5.2.1) - hono: 4.12.18 - jose: 6.2.2 + express-rate-limit: 8.5.2(express@5.2.1) + hono: 4.12.19 + jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 @@ -12203,40 +12416,33 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 - '@tybys/wasm-util': 0.10.1 - optional: true - - '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': - dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 optional: true - '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': + '@nestjs/cache-manager@3.1.2(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cache-manager: 7.2.8 keyv: 5.6.0 rxjs: 7.8.2 - '@nestjs/cli@11.0.21(@types/node@25.5.0)': + '@nestjs/cli@11.0.21(@types/node@25.8.0)(prettier@3.8.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.24(@types/node@25.5.0)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@25.5.0) - '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.9.3) + '@angular-devkit/schematics-cli': 19.2.24(@types/node@25.8.0)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@25.8.0) + '@nestjs/schematics': 11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3) ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 @@ -12251,12 +12457,22 @@ snapshots: webpack: 5.106.0 webpack-node-externals: 3.0.0 transitivePeerDependencies: + - '@minify-html/node' + - '@swc/css' + - '@swc/html' - '@types/node' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss + - prettier - uglify-js - webpack-cli - '@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: file-type: 21.3.4 iterare: 1.2.1 @@ -12270,17 +12486,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/config@4.0.4(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + '@nestjs/config@4.0.4(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) dotenv: 17.4.1 dotenv-expand: 12.0.3 lodash: 4.18.1 rxjs: 7.8.2 - '@nestjs/core@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -12290,13 +12506,13 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/platform-express@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cors: 2.8.6 express: 5.2.1 multer: 2.1.1 @@ -12305,10 +12521,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 socket.io: 4.8.3 tslib: 2.8.1 @@ -12317,75 +12533,77 @@ snapshots: - supports-color - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)': + '@nestjs/schedule@6.1.3(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) cron: 4.4.0 - '@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)': + '@nestjs/schematics@11.1.0(chokidar@4.0.3)(prettier@3.8.3)(typescript@5.9.3)': dependencies: - '@angular-devkit/core': 19.2.17(chokidar@4.0.3) - '@angular-devkit/schematics': 19.2.17(chokidar@4.0.3) - comment-json: 4.4.1 + '@angular-devkit/core': 19.2.24(chokidar@4.0.3) + '@angular-devkit/schematics': 19.2.24(chokidar@4.0.3) + comment-json: 5.0.0 jsonc-parser: 3.3.1 pluralize: 8.0.0 typescript: 5.9.3 + optionalDependencies: + prettier: 3.8.3 transitivePeerDependencies: - chokidar - '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(express@5.2.1)': + '@nestjs/serve-static@5.0.5(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(express@5.2.1)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) path-to-regexp: 8.4.2 optionalDependencies: express: 5.2.1 - '@nestjs/testing@11.1.17(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-express@11.1.19)': + '@nestjs/testing@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-express@11.1.21)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19) + '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) - '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.1(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - typeorm: 0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + typeorm: 0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) - '@nestjs/websockets@11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/platform-socket.io@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) iterare: 1.2.1 object-hash: 3.0.0 reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-socket.io': 11.1.19(@nestjs/common@11.1.19(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.19)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) - '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3))': + '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14))': dependencies: '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) typescript: 5.9.3 - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) - '@ngx-translate/core@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': + '@ngx-translate/core@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 - '@ngx-translate/http-loader@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))': + '@ngx-translate/http-loader@17.0.0(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))': dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) tslib: 2.8.1 '@noble/hashes@1.4.0': {} @@ -12409,7 +12627,7 @@ snapshots: agent-base: 7.1.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - lru-cache: 11.3.5 + lru-cache: 11.3.6 socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color @@ -12423,7 +12641,7 @@ snapshots: '@gar/promise-retry': 1.0.3 '@npmcli/promise-spawn': 9.0.1 ini: 6.0.0 - lru-cache: 11.3.3 + lru-cache: 11.3.6 npm-pick-manifest: 11.0.3 proc-log: 6.1.0 semver: 7.7.4 @@ -12440,7 +12658,7 @@ snapshots: dependencies: '@npmcli/git': 7.0.2 glob: 13.0.6 - hosted-git-info: 9.0.2 + hosted-git-info: 9.0.3 json-parse-even-better-errors: 5.0.0 proc-log: 6.1.0 semver: 7.7.4 @@ -12457,32 +12675,18 @@ snapshots: '@npmcli/node-gyp': 5.0.0 '@npmcli/package-json': 7.0.5 '@npmcli/promise-spawn': 9.0.1 - node-gyp: 12.2.0 + node-gyp: 12.3.0 proc-log: 6.1.0 - transitivePeerDependencies: - - supports-color '@nuxt/opencollective@0.4.1': dependencies: consola: 3.4.2 - '@nx/nx-darwin-arm64@22.6.5': - optional: true - - '@nx/nx-darwin-x64@22.6.5': - optional: true - - '@nx/nx-linux-x64-gnu@22.6.5': - optional: true - - '@nx/nx-win32-x64-msvc@22.6.5': - optional: true - '@opentelemetry/api@1.9.0': {} '@oxc-project/types@0.113.0': {} - '@oxc-project/types@0.124.0': {} + '@oxc-project/types@0.130.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -12549,91 +12753,95 @@ snapshots: '@parcel/watcher-win32-x64': 2.5.6 optional: true - '@peculiar/asn1-cms@2.6.1': + '@peculiar/asn1-cms@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - '@peculiar/asn1-x509-attr': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-csr@2.6.1': + '@peculiar/asn1-csr@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-ecc@2.6.1': + '@peculiar/asn1-ecc@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pfx@2.6.1': + '@peculiar/asn1-pfx@2.7.0': dependencies: - '@peculiar/asn1-cms': 2.6.1 - '@peculiar/asn1-pkcs8': 2.6.1 - '@peculiar/asn1-rsa': 2.6.1 - '@peculiar/asn1-schema': 2.6.0 - asn1js: 3.0.7 + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pkcs8@2.6.1': + '@peculiar/asn1-pkcs8@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-pkcs9@2.6.1': + '@peculiar/asn1-pkcs9@2.7.0': dependencies: - '@peculiar/asn1-cms': 2.6.1 - '@peculiar/asn1-pfx': 2.6.1 - '@peculiar/asn1-pkcs8': 2.6.1 - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - '@peculiar/asn1-x509-attr': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-pfx': 2.7.0 + '@peculiar/asn1-pkcs8': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + '@peculiar/asn1-x509-attr': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-rsa@2.6.1': + '@peculiar/asn1-rsa@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-schema@2.6.0': + '@peculiar/asn1-schema@2.7.0': dependencies: - asn1js: 3.0.7 - pvtsutils: 1.3.6 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-x509-attr@2.6.1': + '@peculiar/asn1-x509-attr@2.7.0': dependencies: - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 - asn1js: 3.0.7 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 + asn1js: 3.0.10 tslib: 2.8.1 - '@peculiar/asn1-x509@2.6.1': + '@peculiar/asn1-x509@2.7.0': + dependencies: + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/utils': 2.0.3 + asn1js: 3.0.10 + tslib: 2.8.1 + + '@peculiar/utils@2.0.3': dependencies: - '@peculiar/asn1-schema': 2.6.0 - asn1js: 3.0.7 - pvtsutils: 1.3.6 tslib: 2.8.1 '@peculiar/x509@1.14.3': dependencies: - '@peculiar/asn1-cms': 2.6.1 - '@peculiar/asn1-csr': 2.6.1 - '@peculiar/asn1-ecc': 2.6.1 - '@peculiar/asn1-pkcs9': 2.6.1 - '@peculiar/asn1-rsa': 2.6.1 - '@peculiar/asn1-schema': 2.6.0 - '@peculiar/asn1-x509': 2.6.1 + '@peculiar/asn1-cms': 2.7.0 + '@peculiar/asn1-csr': 2.7.0 + '@peculiar/asn1-ecc': 2.7.0 + '@peculiar/asn1-pkcs9': 2.7.0 + '@peculiar/asn1-rsa': 2.7.0 + '@peculiar/asn1-schema': 2.7.0 + '@peculiar/asn1-x509': 2.7.0 pvtsutils: 1.3.6 reflect-metadata: 0.2.2 tslib: 2.8.1 @@ -12644,9 +12852,9 @@ snapshots: '@pkgr/core@0.2.9': {} - '@playwright/test@1.58.2': + '@playwright/test@1.60.0': dependencies: - playwright: 1.58.2 + playwright: 1.60.0 '@polka/send-type@0.5.2': {} @@ -12654,204 +12862,184 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@redis/bloom@5.11.0(@redis/client@5.11.0)': - dependencies: - '@redis/client': 5.11.0 - - '@redis/client@5.11.0': - dependencies: - cluster-key-slot: 1.1.2 - - '@redis/json@5.11.0(@redis/client@5.11.0)': - dependencies: - '@redis/client': 5.11.0 - - '@redis/search@5.11.0(@redis/client@5.11.0)': - dependencies: - '@redis/client': 5.11.0 - - '@redis/time-series@5.11.0(@redis/client@5.11.0)': - dependencies: - '@redis/client': 5.11.0 - - '@rolldown/binding-android-arm64@1.0.0-rc.15': - optional: true - '@rolldown/binding-android-arm64@1.0.0-rc.4': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + '@rolldown/binding-android-arm64@1.0.1': optional: true '@rolldown/binding-darwin-arm64@1.0.0-rc.4': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.15': + '@rolldown/binding-darwin-arm64@1.0.1': optional: true '@rolldown/binding-darwin-x64@1.0.0-rc.4': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + '@rolldown/binding-darwin-x64@1.0.1': optional: true '@rolldown/binding-freebsd-x64@1.0.0-rc.4': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + '@rolldown/binding-freebsd-x64@1.0.1': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.4': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-arm-gnueabihf@1.0.1': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.4': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + '@rolldown/binding-linux-arm64-gnu@1.0.1': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-rc.4': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-arm64-musl@1.0.1': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-ppc64-gnu@1.0.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + '@rolldown/binding-linux-s390x-gnu@1.0.1': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-rc.4': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + '@rolldown/binding-linux-x64-gnu@1.0.1': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-rc.4': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + '@rolldown/binding-linux-x64-musl@1.0.1': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-rc.4': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': - dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@rolldown/binding-openharmony-arm64@1.0.1': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + '@rolldown/binding-wasm32-wasi@1.0.1': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.4': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + '@rolldown/binding-win32-arm64-msvc@1.0.1': optional: true '@rolldown/binding-win32-x64-msvc@1.0.0-rc.4': optional: true - '@rolldown/pluginutils@1.0.0-rc.15': {} + '@rolldown/binding-win32-x64-msvc@1.0.1': + optional: true '@rolldown/pluginutils@1.0.0-rc.4': {} - '@rollup/rollup-android-arm-eabi@4.60.1': + '@rolldown/pluginutils@1.0.1': {} + + '@rollup/rollup-android-arm-eabi@4.60.4': optional: true - '@rollup/rollup-android-arm64@4.60.1': + '@rollup/rollup-android-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-arm64@4.60.1': + '@rollup/rollup-darwin-arm64@4.60.4': optional: true - '@rollup/rollup-darwin-x64@4.60.1': + '@rollup/rollup-darwin-x64@4.60.4': optional: true - '@rollup/rollup-freebsd-arm64@4.60.1': + '@rollup/rollup-freebsd-arm64@4.60.4': optional: true - '@rollup/rollup-freebsd-x64@4.60.1': + '@rollup/rollup-freebsd-x64@4.60.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.60.1': + '@rollup/rollup-linux-arm-musleabihf@4.60.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.60.1': + '@rollup/rollup-linux-arm64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.60.1': + '@rollup/rollup-linux-arm64-musl@4.60.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.60.1': + '@rollup/rollup-linux-loong64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-loong64-musl@4.60.1': + '@rollup/rollup-linux-loong64-musl@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.60.1': + '@rollup/rollup-linux-ppc64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-ppc64-musl@4.60.1': + '@rollup/rollup-linux-ppc64-musl@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.60.1': + '@rollup/rollup-linux-riscv64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.60.1': + '@rollup/rollup-linux-riscv64-musl@4.60.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.60.1': + '@rollup/rollup-linux-s390x-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.60.1': + '@rollup/rollup-linux-x64-gnu@4.60.4': optional: true - '@rollup/rollup-linux-x64-musl@4.60.1': + '@rollup/rollup-linux-x64-musl@4.60.4': optional: true - '@rollup/rollup-openbsd-x64@4.60.1': + '@rollup/rollup-openbsd-x64@4.60.4': optional: true - '@rollup/rollup-openharmony-arm64@4.60.1': + '@rollup/rollup-openharmony-arm64@4.60.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.60.1': + '@rollup/rollup-win32-arm64-msvc@4.60.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.60.1': + '@rollup/rollup-win32-ia32-msvc@4.60.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.60.1': + '@rollup/rollup-win32-x64-gnu@4.60.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.60.1': + '@rollup/rollup-win32-x64-msvc@4.60.4': optional: true '@rtsao/scc@1.1.0': {} - '@schematics/angular@21.2.5(chokidar@5.0.0)': + '@schematics/angular@21.2.11(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 21.2.5(chokidar@5.0.0) - '@angular-devkit/schematics': 21.2.5(chokidar@5.0.0) + '@angular-devkit/core': 21.2.11(chokidar@5.0.0) + '@angular-devkit/schematics': 21.2.11(chokidar@5.0.0) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -12864,37 +13052,37 @@ snapshots: transitivePeerDependencies: - chokidar - '@secretlint/config-creator@11.4.1': + '@secretlint/config-creator@11.7.1': dependencies: - '@secretlint/types': 11.4.1 + '@secretlint/types': 11.7.1 - '@secretlint/config-loader@11.4.1': + '@secretlint/config-loader@11.7.1': dependencies: - '@secretlint/profiler': 11.4.1 - '@secretlint/resolver': 11.4.1 - '@secretlint/types': 11.4.1 - ajv: 8.18.0 + '@secretlint/profiler': 11.7.1 + '@secretlint/resolver': 11.7.1 + '@secretlint/types': 11.7.1 + ajv: 8.20.0 debug: 4.4.3 rc-config-loader: 4.1.4 transitivePeerDependencies: - supports-color - '@secretlint/core@11.4.1': + '@secretlint/core@11.7.1': dependencies: - '@secretlint/profiler': 11.4.1 - '@secretlint/types': 11.4.1 + '@secretlint/profiler': 11.7.1 + '@secretlint/types': 11.7.1 debug: 4.4.3 structured-source: 4.0.0 transitivePeerDependencies: - supports-color - '@secretlint/formatter@11.4.1': + '@secretlint/formatter@11.7.1': dependencies: - '@secretlint/resolver': 11.4.1 - '@secretlint/types': 11.4.1 - '@textlint/linter-formatter': 15.5.2 - '@textlint/module-interop': 15.5.2 - '@textlint/types': 15.5.2 + '@secretlint/resolver': 11.7.1 + '@secretlint/types': 11.7.1 + '@textlint/linter-formatter': 15.7.0 + '@textlint/module-interop': 15.7.0 + '@textlint/types': 15.7.0 chalk: 5.6.2 debug: 4.4.3 pluralize: 8.0.0 @@ -12904,31 +13092,31 @@ snapshots: transitivePeerDependencies: - supports-color - '@secretlint/node@11.4.1': + '@secretlint/node@11.7.1': dependencies: - '@secretlint/config-loader': 11.4.1 - '@secretlint/core': 11.4.1 - '@secretlint/formatter': 11.4.1 - '@secretlint/profiler': 11.4.1 - '@secretlint/source-creator': 11.4.1 - '@secretlint/types': 11.4.1 + '@secretlint/config-loader': 11.7.1 + '@secretlint/core': 11.7.1 + '@secretlint/formatter': 11.7.1 + '@secretlint/profiler': 11.7.1 + '@secretlint/source-creator': 11.7.1 + '@secretlint/types': 11.7.1 debug: 4.4.3 p-map: 7.0.4 transitivePeerDependencies: - supports-color - '@secretlint/profiler@11.4.1': {} + '@secretlint/profiler@11.7.1': {} - '@secretlint/resolver@11.4.1': {} + '@secretlint/resolver@11.7.1': {} - '@secretlint/secretlint-rule-preset-recommend@11.4.1': {} + '@secretlint/secretlint-rule-preset-recommend@11.7.1': {} - '@secretlint/source-creator@11.4.1': + '@secretlint/source-creator@11.7.1': dependencies: - '@secretlint/types': 11.4.1 + '@secretlint/types': 11.7.1 istextorbinary: 9.5.0 - '@secretlint/types@11.4.1': {} + '@secretlint/types@11.7.1': {} '@sigstore/bundle@4.0.0': dependencies: @@ -12962,7 +13150,7 @@ snapshots: '@sigstore/core': 3.2.0 '@sigstore/protobuf-specs': 0.5.1 - '@sinclair/typebox@0.34.48': {} + '@sinclair/typebox@0.34.49': {} '@sindresorhus/merge-streams@2.3.0': {} @@ -12970,7 +13158,7 @@ snapshots: dependencies: type-detect: 4.0.8 - '@sinonjs/fake-timers@15.1.1': + '@sinonjs/fake-timers@15.4.0': dependencies: '@sinonjs/commons': 3.0.1 @@ -12980,25 +13168,25 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.1.0(jiti@2.6.1))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.4.0(jiti@2.7.0))': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) - '@typescript-eslint/types': 8.57.2 - eslint: 10.1.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/types': 8.59.3 + eslint: 10.4.0(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.4 - '@textlint/ast-node-types@15.5.2': {} + '@textlint/ast-node-types@15.7.0': {} - '@textlint/linter-formatter@15.5.2': + '@textlint/linter-formatter@15.7.0': dependencies: '@azu/format-text': 1.0.2 '@azu/style-format': 1.0.1 - '@textlint/module-interop': 15.5.2 - '@textlint/resolver': 15.5.2 - '@textlint/types': 15.5.2 + '@textlint/module-interop': 15.7.0 + '@textlint/resolver': 15.7.0 + '@textlint/types': 15.7.0 chalk: 4.1.2 debug: 4.4.3 js-yaml: 4.1.1 @@ -13011,13 +13199,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@textlint/module-interop@15.5.2': {} + '@textlint/module-interop@15.7.0': {} - '@textlint/resolver@15.5.2': {} + '@textlint/resolver@15.7.0': {} - '@textlint/types@15.5.2': + '@textlint/types@15.7.0': dependencies: - '@textlint/ast-node-types': 15.5.2 + '@textlint/ast-node-types': 15.7.0 '@thednp/event-listener@2.0.15': {} @@ -13057,14 +13245,14 @@ snapshots: '@tufjs/canonical-json': 2.0.0 minimatch: 10.2.5 - '@tybys/wasm-util@0.10.1': + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -13076,7 +13264,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': @@ -13086,11 +13274,11 @@ snapshots: '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/bonjour@3.5.13': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/cache-manager@5.0.0': dependencies: @@ -13099,11 +13287,11 @@ snapshots: '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.1.1 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/connect@3.4.38': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/cookie-parser@1.4.10(@types/express@5.0.6)': dependencies: @@ -13113,7 +13301,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/cron@2.4.3': dependencies: @@ -13239,28 +13427,30 @@ snapshots: '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} + '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 25.5.0 - '@types/qs': 6.15.0 + '@types/node': 25.8.0 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 25.5.0 - '@types/qs': 6.15.0 + '@types/node': 25.8.0 + '@types/qs': 6.15.1 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -13268,7 +13458,7 @@ snapshots: dependencies: '@types/body-parser': 1.19.6 '@types/express-serve-static-core': 4.19.8 - '@types/qs': 6.15.0 + '@types/qs': 6.15.1 '@types/serve-static': 1.15.10 '@types/express@5.0.6': @@ -13285,7 +13475,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/istanbul-lib-coverage@2.0.6': {} @@ -13299,12 +13489,12 @@ snapshots: '@types/jest@30.0.0': dependencies: - expect: 30.3.0 - pretty-format: 30.3.0 + expect: 30.4.1 + pretty-format: 30.4.1 '@types/jsdom@21.1.7': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -13315,7 +13505,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/luxon@3.7.1': {} @@ -13327,15 +13517,15 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@25.5.0': + '@types/node@25.8.0': dependencies: - undici-types: 7.18.2 + undici-types: 7.24.6 '@types/normalize-package-data@2.4.4': {} '@types/pako@2.0.4': {} - '@types/qs@6.15.0': {} + '@types/qs@6.15.1': {} '@types/raf@3.4.3': optional: true @@ -13351,11 +13541,11 @@ snapshots: '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/send@1.2.1': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/serve-index@1.9.4': dependencies: @@ -13364,17 +13554,17 @@ snapshots: '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/send': 0.17.6 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/sockjs@0.3.36': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/stack-utils@2.0.3': {} @@ -13382,7 +13572,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 25.5.0 + '@types/node': 25.8.0 form-data: 4.0.5 '@types/supertest@7.2.0': @@ -13403,7 +13593,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/yargs-parser@21.0.3': {} @@ -13411,15 +13601,15 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/type-utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.2 - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/parser': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/type-utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.3 + eslint: 10.4.0(jiti@2.7.0) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -13427,82 +13617,82 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.3 debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.57.2(typescript@5.9.3)': + '@typescript-eslint/project-service@8.59.3(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) - '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.57.2': + '@typescript-eslint/scope-manager@8.59.3': dependencies: - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 - '@typescript-eslint/tsconfig-utils@8.57.2(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.59.3(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.57.2': {} + '@typescript-eslint/types@8.59.3': {} - '@typescript-eslint/typescript-estree@8.57.2(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.59.3(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.57.2(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.57.2(typescript@5.9.3) - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/visitor-keys': 8.57.2 + '@typescript-eslint/project-service': 8.59.3(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@5.9.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 debug: 4.4.3 - minimatch: 10.2.4 - semver: 7.7.4 - tinyglobby: 0.2.15 + minimatch: 10.2.5 + semver: 7.8.0 + tinyglobby: 0.2.16 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.57.2 - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.57.2': + '@typescript-eslint/visitor-keys@8.59.3': dependencies: - '@typescript-eslint/types': 8.57.2 + '@typescript-eslint/types': 8.59.3 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.1': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -13565,9 +13755,9 @@ snapshots: '@vercel/oidc@3.2.0': {} - '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))': + '@vitejs/plugin-basic-ssl@2.1.4(vite@7.3.2(@types/node@25.8.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0))': dependencies: - vite: 7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) + vite: 7.3.2(@types/node@25.8.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0) '@webassemblyjs/ast@1.14.1': dependencies: @@ -13704,16 +13894,16 @@ snapshots: optionalDependencies: ajv: 8.18.0 - ajv-keywords@3.5.2(ajv@6.14.0): + ajv-keywords@3.5.2(ajv@6.15.0): dependencies: - ajv: 6.14.0 + ajv: 6.15.0 ajv-keywords@5.1.0(ajv@8.18.0): dependencies: ajv: 8.18.0 fast-deep-equal: 3.1.3 - ajv@6.14.0: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -13727,6 +13917,13 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.2 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + algoliasearch@5.48.1: dependencies: '@algolia/abtesting': 1.14.1 @@ -13747,21 +13944,21 @@ snapshots: amdefine@1.0.1: optional: true - angular-eslint@21.3.1(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript-eslint@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3): + angular-eslint@21.4.0(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript-eslint@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(typescript@5.9.3): dependencies: '@angular-devkit/core': 21.2.5(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.5(chokidar@5.0.0) - '@angular-eslint/builder': 21.3.1(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin': 21.3.1(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin-template': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/schematics': 21.3.1(@angular-eslint/template-parser@21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.5.0)(chokidar@5.0.0))(@typescript-eslint/types@8.57.2)(@typescript-eslint/utils@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/template-parser': 21.3.1(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@angular/cli': 21.2.7(@types/node@25.5.0)(chokidar@5.0.0) - '@typescript-eslint/types': 8.57.2 - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@angular-eslint/builder': 21.4.0(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/eslint-plugin': 21.4.0(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.4.0(@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/schematics': 21.4.0(@angular-eslint/template-parser@21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(@angular/cli@21.2.7(@types/node@25.8.0)(chokidar@5.0.0))(@typescript-eslint/types@8.59.3)(@typescript-eslint/utils@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(chokidar@5.0.0)(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular-eslint/template-parser': 21.4.0(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@angular/cli': 21.2.7(@types/node@25.8.0)(chokidar@5.0.0) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 - typescript-eslint: 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - chokidar - supports-color @@ -13798,6 +13995,8 @@ snapshots: ansis@4.2.0: {} + ansis@4.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -13832,10 +14031,10 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 @@ -13845,41 +14044,41 @@ snapshots: array.prototype.findlastindex@1.2.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 asap@2.0.6: {} - asn1js@3.0.7: + asn1js@3.0.10: dependencies: pvtsutils: 1.3.6 pvutils: 1.1.5 @@ -13889,17 +14088,15 @@ snapshots: async-function@1.0.0: {} - async@3.2.6: {} - asynckit@0.4.0: {} - autoprefixer@10.4.27(postcss@8.5.10): + autoprefixer@10.4.27(postcss@8.5.14): dependencies: browserslist: 4.28.2 - caniuse-lite: 1.0.30001787 + caniuse-lite: 1.0.30001792 fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.10 + postcss: 8.5.14 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -13921,17 +14118,17 @@ snapshots: transitivePeerDependencies: - supports-color - babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)): + babel-loader@10.0.0(@babel/core@7.29.0)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: '@babel/core': 7.29.0 find-up: 5.0.0 - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) babel-plugin-istanbul@7.0.1: dependencies: '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 + '@istanbuljs/schema': 0.1.6 istanbul-lib-instrument: 6.0.3 test-exclude: 6.0.0 transitivePeerDependencies: @@ -13943,7 +14140,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.28.6): dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.28.6 '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.28.6) semver: 6.3.1 @@ -13952,7 +14149,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) semver: 6.3.1 @@ -14033,7 +14230,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.10.18: {} + baseline-browser-mapping@2.10.30: {} basic-auth@2.0.1: dependencies: @@ -14051,9 +14248,9 @@ snapshots: domhandler: 5.0.3 htmlparser2: 10.1.0 picocolors: 1.1.1 - postcss: 8.5.15 + postcss: 8.5.14 postcss-media-query-parser: 0.2.3 - postcss-safe-parser: 7.0.1(postcss@8.5.15) + postcss-safe-parser: 7.0.1(postcss@8.5.14) big.js@5.2.2: {} @@ -14069,7 +14266,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@1.20.4: + body-parser@1.20.5: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -14079,7 +14276,7 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.2 + qs: 6.15.1 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 @@ -14094,9 +14291,9 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.1 raw-body: 3.0.2 - type-is: 2.0.1 + type-is: 2.1.0 transitivePeerDependencies: - supports-color @@ -14115,16 +14312,16 @@ snapshots: boundary@2.0.0: {} - brace-expansion@1.1.13: + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.3: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.5: + brace-expansion@5.0.6: dependencies: balanced-match: 4.0.4 @@ -14134,10 +14331,10 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.18 - caniuse-lite: 1.0.30001787 - electron-to-chromium: 1.5.335 - node-releases: 2.0.37 + baseline-browser-mapping: 2.10.30 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.358 + node-releases: 2.0.44 update-browserslist-db: 1.2.3(browserslist@4.28.2) bs-logger@0.2.6: @@ -14179,7 +14376,7 @@ snapshots: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 glob: 13.0.6 - lru-cache: 11.3.3 + lru-cache: 11.3.6 minipass: 7.1.3 minipass-collect: 2.0.1 minipass-flush: 1.0.7 @@ -14197,7 +14394,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -14217,7 +14414,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001787: {} + caniuse-lite@1.0.30001792: {} canvg@3.0.11: dependencies: @@ -14262,7 +14459,7 @@ snapshots: parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.24.0 + undici: 7.25.0 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -14300,8 +14497,8 @@ snapshots: class-validator@0.15.1: dependencies: '@types/validator': 13.15.10 - libphonenumber-js: 1.12.40 - validator: 13.15.26 + libphonenumber-js: 1.13.2 + validator: 13.15.35 cli-cursor@3.1.0: dependencies: @@ -14324,7 +14521,7 @@ snapshots: cli-truncate@5.2.0: dependencies: slice-ansi: 8.0.0 - string-width: 8.2.0 + string-width: 8.2.1 cli-width@4.1.0: {} @@ -14348,8 +14545,6 @@ snapshots: clone@1.0.4: {} - cluster-key-slot@1.1.2: {} - co@4.6.0: {} code-block-writer@13.0.3: {} @@ -14382,10 +14577,9 @@ snapshots: commander@7.2.0: {} - comment-json@4.4.1: + comment-json@5.0.0: dependencies: array-timsort: 1.0.3 - core-util-is: 1.0.3 esprima: 4.0.1 component-emitter@1.3.1: {} @@ -14445,6 +14639,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@1.9.0: {} convert-source-map@2.0.0: {} @@ -14468,14 +14664,14 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@14.0.0(webpack@5.105.2(esbuild@0.27.3)): + copy-webpack-plugin@14.0.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 7.0.5 - tinyglobby: 0.2.16 - webpack: 5.105.2(esbuild@0.27.3) + tinyglobby: 0.2.15 + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) core-js-compat@3.49.0: dependencies: @@ -14527,18 +14723,18 @@ snapshots: utrie: 1.0.2 optional: true - css-loader@7.1.3(webpack@5.105.2(esbuild@0.27.3)): + css-loader@7.1.3(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.15) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.15) - postcss-modules-scope: 3.2.1(postcss@8.5.15) - postcss-modules-values: 4.0.0(postcss@8.5.15) + icss-utils: 5.1.0(postcss@8.5.14) + postcss: 8.5.14 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.14) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.14) + postcss-modules-scope: 3.2.1(postcss@8.5.14) + postcss-modules-values: 4.0.0(postcss@8.5.14) postcss-value-parser: 4.2.0 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) css-select@5.2.2: dependencies: @@ -14886,7 +15082,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.335: {} + electron-to-chromium@1.5.358: {} emittery@0.13.1: {} @@ -14925,10 +15121,10 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.6: + engine.io@6.6.7: dependencies: '@types/cors': 2.8.19 - '@types/node': 25.5.0 + '@types/node': 25.8.0 '@types/ws': 8.18.1 accepts: 1.3.8 base64id: 2.0.0 @@ -14942,10 +15138,10 @@ snapshots: - supports-color - utf-8-validate - enhanced-resolve@5.20.1: + enhanced-resolve@5.21.3: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.2 + tapable: 2.3.3 entities@4.5.0: {} @@ -14970,12 +15166,12 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-abstract@1.24.1: + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -14994,7 +15190,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -15012,7 +15208,7 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 @@ -15031,7 +15227,7 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@2.0.0: {} + es-module-lexer@2.1.0: {} es-object-atoms@1.1.1: dependencies: @@ -15042,11 +15238,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 es-to-primitive@1.3.0: dependencies: @@ -15058,7 +15254,7 @@ snapshots: esbuild-wasm@0.27.3: {} - esbuild-wasm@0.27.4: {} + esbuild-wasm@0.28.0: {} esbuild@0.27.3: optionalDependencies: @@ -15089,34 +15285,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 - esbuild@0.27.4: + esbuild@0.28.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.4 - '@esbuild/android-arm': 0.27.4 - '@esbuild/android-arm64': 0.27.4 - '@esbuild/android-x64': 0.27.4 - '@esbuild/darwin-arm64': 0.27.4 - '@esbuild/darwin-x64': 0.27.4 - '@esbuild/freebsd-arm64': 0.27.4 - '@esbuild/freebsd-x64': 0.27.4 - '@esbuild/linux-arm': 0.27.4 - '@esbuild/linux-arm64': 0.27.4 - '@esbuild/linux-ia32': 0.27.4 - '@esbuild/linux-loong64': 0.27.4 - '@esbuild/linux-mips64el': 0.27.4 - '@esbuild/linux-ppc64': 0.27.4 - '@esbuild/linux-riscv64': 0.27.4 - '@esbuild/linux-s390x': 0.27.4 - '@esbuild/linux-x64': 0.27.4 - '@esbuild/netbsd-arm64': 0.27.4 - '@esbuild/netbsd-x64': 0.27.4 - '@esbuild/openbsd-arm64': 0.27.4 - '@esbuild/openbsd-x64': 0.27.4 - '@esbuild/openharmony-arm64': 0.27.4 - '@esbuild/sunos-x64': 0.27.4 - '@esbuild/win32-arm64': 0.27.4 - '@esbuild/win32-ia32': 0.27.4 - '@esbuild/win32-x64': 0.27.4 + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + optional: true escalade@3.2.0: {} @@ -15134,52 +15331,52 @@ snapshots: optionalDependencies: source-map: 0.1.43 - eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)): + eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)): dependencies: - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-import-context@0.1.9(unrs-resolver@1.11.1): dependencies: - get-tsconfig: 4.13.7 + get-tsconfig: 4.14.0 stable-hash-x: 0.2.0 optionalDependencies: unrs-resolver: 1.11.1 - eslint-import-resolver-node@0.3.9: + eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 - resolve: 1.22.11 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@10.1.0(jiti@2.6.1)): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)): dependencies: debug: 4.4.3 - eslint: 10.1.0(jiti@2.6.1) + eslint: 10.4.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) - get-tsconfig: 4.13.7 + get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash-x: 0.2.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.1.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@10.1.0(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@10.1.0(jiti@2.6.1)) + '@typescript-eslint/parser': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 4.4.4(eslint-plugin-import@2.32.0)(eslint@10.4.0(jiti@2.7.0)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.1.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -15188,11 +15385,11 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.1.0(jiti@2.6.1) - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@10.1.0(jiti@2.6.1)) - hasown: 2.0.2 - is-core-module: 2.16.1 + eslint: 10.4.0(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.4)(eslint@10.4.0(jiti@2.7.0)) + hasown: 2.0.3 + is-core-module: 2.16.2 is-glob: 4.0.3 minimatch: 3.1.5 object.fromentries: 2.0.8 @@ -15202,40 +15399,32 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@29.15.1(@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3): + eslint-plugin-jest@29.15.1(@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + jest: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) typescript: 5.9.3 transitivePeerDependencies: - supports-color - eslint-plugin-nestjs@1.2.3: + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.4.0(jiti@2.7.0)))(eslint@10.4.0(jiti@2.7.0))(prettier@3.8.3): dependencies: - tslib: 1.14.1 - - eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))(prettier@3.8.1): - dependencies: - eslint: 10.1.0(jiti@2.6.1) - prettier: 3.8.1 + eslint: 10.4.0(jiti@2.7.0) + prettier: 3.8.3 prettier-linter-helpers: 1.0.1 synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@10.1.0(jiti@2.6.1)) - - eslint-plugin-typeorm@0.0.19: - dependencies: - requireindex: 1.2.0 + eslint-config-prettier: 10.1.8(eslint@10.4.0(jiti@2.7.0)) eslint-scope@5.1.1: dependencies: @@ -15245,7 +15434,7 @@ snapshots: eslint-scope@9.1.2: dependencies: '@types/esrecurse': 4.3.1 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -15255,19 +15444,19 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.1.0(jiti@2.6.1): + eslint@10.4.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.3 - '@eslint/config-helpers': 0.5.3 - '@eslint/core': 1.1.1 - '@eslint/plugin-kit': 0.6.1 - '@humanfs/node': 0.16.7 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - ajv: 6.14.0 + '@types/estree': 1.0.9 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 @@ -15284,11 +15473,11 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - minimatch: 10.2.4 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color @@ -15344,13 +15533,11 @@ snapshots: events@3.3.0: {} - eventsource-parser@3.0.6: {} - eventsource-parser@3.0.8: {} eventsource@3.0.7: dependencies: - eventsource-parser: 3.0.6 + eventsource-parser: 3.0.8 execa@4.1.0: dependencies: @@ -15387,18 +15574,27 @@ snapshots: jest-mock: 30.3.0 jest-util: 30.3.0 + expect@30.4.1: + dependencies: + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + exponential-backoff@3.1.3: {} - express-rate-limit@8.3.2(express@5.2.1): + express-rate-limit@8.5.2(express@5.2.1): dependencies: express: 5.2.1 ip-address: 10.2.0 - express@4.22.1: + express@4.22.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.4 + body-parser: 1.20.5 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.7.2 @@ -15417,7 +15613,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.13 proxy-addr: 2.0.7 - qs: 6.14.2 + qs: 6.15.1 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -15458,7 +15654,7 @@ snapshots: send: 1.2.1 serve-static: 2.2.1 statuses: 2.0.2 - type-is: 2.0.1 + type-is: 2.1.0 vary: 1.1.2 transitivePeerDependencies: - supports-color @@ -15509,7 +15705,7 @@ snapshots: optionalDependencies: picomatch: 4.0.4 - fflate@0.8.2: {} + fflate@0.8.3: {} file-entry-cache@8.0.0: dependencies: @@ -15607,8 +15803,8 @@ snapshots: minimatch: 3.1.5 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.4 - tapable: 2.3.2 + semver: 7.8.0 + tapable: 2.3.3 typescript: 5.9.3 webpack: 5.106.0 @@ -15617,7 +15813,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 formidable@3.5.4: @@ -15639,13 +15835,13 @@ snapshots: fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 - fs-extra@11.3.4: + fs-extra@11.3.5: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fs-minipass@3.0.3: @@ -15666,11 +15862,11 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.2 + hasown: 2.0.3 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -15681,7 +15877,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.5.0: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: dependencies: @@ -15693,7 +15889,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-package-type@0.1.0: {} @@ -15715,7 +15911,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.13.7: + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 @@ -15757,7 +15953,7 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - globals@17.4.0: {} + globals@17.6.0: {} globalthis@1.0.4: dependencies: @@ -15810,11 +16006,11 @@ snapshots: dependencies: hookified: 1.15.1 - hasown@2.0.2: + hasown@2.0.3: dependencies: function-bind: 1.1.2 - hono@4.12.18: {} + hono@4.12.19: {} hookified@1.15.1: {} @@ -15822,9 +16018,9 @@ snapshots: dependencies: lru-cache: 10.4.3 - hosted-git-info@9.0.2: + hosted-git-info@9.0.3: dependencies: - lru-cache: 11.3.3 + lru-cache: 11.3.6 hpack.js@2.1.6: dependencies: @@ -15954,9 +16150,9 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.5.15): + icss-utils@5.1.0(postcss@8.5.14): dependencies: - postcss: 8.5.15 + postcss: 8.5.14 ieee754@1.2.1: {} @@ -16001,7 +16197,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.3 side-channel: 1.1.0 internmap@2.0.3: {} @@ -16012,11 +16208,11 @@ snapshots: ipaddr.js@1.9.1: {} - ipaddr.js@2.3.0: {} + ipaddr.js@2.4.0: {} is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -16045,13 +16241,13 @@ snapshots: is-bun-module@2.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.0 is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-data-view@1.0.2: dependencies: @@ -16078,7 +16274,7 @@ snapshots: is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 is-generator-fn@2.1.0: {} @@ -16108,7 +16304,7 @@ snapshots: is-negative-zero@2.0.3: {} - is-network-error@1.3.1: {} + is-network-error@1.3.2: {} is-number-object@1.1.1: dependencies: @@ -16134,7 +16330,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 is-set@2.0.3: {} @@ -16201,8 +16397,8 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 - '@istanbuljs/schema': 0.1.3 + '@babel/parser': 7.29.3 + '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 semver: 7.7.4 transitivePeerDependencies: @@ -16258,7 +16454,7 @@ snapshots: '@jest/expect': 30.3.0 '@jest/test-result': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.2 @@ -16278,15 +16474,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)): + jest-cli@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)): dependencies: - '@jest/core': 30.3.0(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + '@jest/core': 30.3.0(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) '@jest/test-result': 30.3.0 '@jest/types': 30.3.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + jest-config: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) jest-util: 30.3.0 jest-validate: 30.3.0 yargs: 17.7.2 @@ -16297,7 +16493,7 @@ snapshots: - supports-color - ts-node - jest-config@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)): + jest-config@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)): dependencies: '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 @@ -16323,8 +16519,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 25.5.0 - ts-node: 10.9.2(@types/node@25.5.0)(typescript@5.9.3) + '@types/node': 25.8.0 + ts-node: 10.9.2(@types/node@25.8.0)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -16336,6 +16532,13 @@ snapshots: chalk: 4.1.2 pretty-format: 30.3.0 + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + jest-docblock@30.2.0: dependencies: detect-newline: 3.1.0 @@ -16348,10 +16551,10 @@ snapshots: jest-util: 30.3.0 pretty-format: 30.3.0 - jest-environment-jsdom@30.3.0: + jest-environment-jsdom@30.4.1: dependencies: - '@jest/environment': 30.3.0 - '@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0) + '@jest/environment': 30.4.1 + '@jest/environment-jsdom-abstract': 30.4.1(jsdom@26.1.0) jsdom: 26.1.0 transitivePeerDependencies: - bufferutil @@ -16363,7 +16566,7 @@ snapshots: '@jest/environment': 30.3.0 '@jest/fake-timers': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 jest-mock: 30.3.0 jest-util: 30.3.0 jest-validate: 30.3.0 @@ -16371,7 +16574,7 @@ snapshots: jest-haste-map@30.3.0: dependencies: '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -16383,6 +16586,21 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + jest-haste-map@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.8.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + jest-worker: 30.4.1 + picomatch: 4.0.4 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + jest-leak-detector@30.3.0: dependencies: '@jest/get-type': 30.1.0 @@ -16395,6 +16613,13 @@ snapshots: jest-diff: 30.3.0 pretty-format: 30.3.0 + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + jest-message-util@30.3.0: dependencies: '@babel/code-frame': 7.29.0 @@ -16407,33 +16632,52 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 + jest-message-util@30.4.1: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 30.4.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.4.1 + picomatch: 4.0.4 + pretty-format: 30.4.1 + slash: 3.0.0 + stack-utils: 2.0.6 + jest-mock@30.3.0: dependencies: '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 jest-util: 30.3.0 + jest-mock@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.8.0 + jest-util: 30.4.1 + jest-pnp-resolver@1.2.3(jest-resolve@30.3.0): optionalDependencies: jest-resolve: 30.3.0 - jest-preset-angular@16.1.1(28bd700369ae87f04923e58e037d4bde): + jest-preset-angular@16.1.5(38ed6dc12e43f709a9a6350e673a80e1): dependencies: '@angular/compiler-cli': 21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)) - '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))) - '@jest/environment-jsdom-abstract': 30.3.0(jsdom@26.1.0) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/platform-browser': 21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)) + '@angular/platform-browser-dynamic': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/compiler@21.2.9)(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))) + '@jest/environment-jsdom-abstract': 30.4.1(jsdom@26.1.0) bs-logger: 0.2.6 - esbuild-wasm: 0.27.4 - jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) - jest-util: 30.3.0 + esbuild-wasm: 0.28.0 + jest: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) + jest-util: 30.4.1 jsdom: 26.1.0 - pretty-format: 30.3.0 - ts-jest: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.27.4)(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3) + pretty-format: 30.4.1 + ts-jest: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.28.0)(jest-util@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(typescript@5.9.3) typescript: 5.9.3 optionalDependencies: - esbuild: 0.27.4 + esbuild: 0.28.0 transitivePeerDependencies: - '@babel/core' - '@jest/transform' @@ -16443,6 +16687,8 @@ snapshots: jest-regex-util@30.0.1: {} + jest-regex-util@30.4.0: {} + jest-resolve-dependencies@30.3.0: dependencies: jest-regex-util: 30.0.1 @@ -16468,7 +16714,7 @@ snapshots: '@jest/test-result': 30.3.0 '@jest/transform': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -16497,7 +16743,7 @@ snapshots: '@jest/test-result': 30.3.0 '@jest/transform': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 chalk: 4.1.2 cjs-module-lexer: 2.2.0 collect-v8-coverage: 1.0.3 @@ -16536,7 +16782,33 @@ snapshots: jest-message-util: 30.3.0 jest-util: 30.3.0 pretty-format: 30.3.0 - semver: 7.7.4 + semver: 7.8.0 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.4.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 30.4.1 + graceful-fs: 4.2.11 + jest-diff: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + pretty-format: 30.4.1 + semver: 7.8.0 synckit: 0.11.12 transitivePeerDependencies: - supports-color @@ -16544,7 +16816,16 @@ snapshots: jest-util@30.3.0: dependencies: '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jest-util@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.8.0 chalk: 4.1.2 ci-info: 4.4.0 graceful-fs: 4.2.11 @@ -16563,7 +16844,7 @@ snapshots: dependencies: '@jest/test-result': 30.3.0 '@jest/types': 30.3.0 - '@types/node': 25.5.0 + '@types/node': 25.8.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -16572,24 +16853,32 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@30.3.0: dependencies: - '@types/node': 25.5.0 - '@ungap/structured-clone': 1.3.0 + '@types/node': 25.8.0 + '@ungap/structured-clone': 1.3.1 jest-util: 30.3.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)): + jest-worker@30.4.1: + dependencies: + '@types/node': 25.8.0 + '@ungap/structured-clone': 1.3.1 + jest-util: 30.4.1 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)): dependencies: - '@jest/core': 30.3.0(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + '@jest/core': 30.3.0(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) '@jest/types': 30.3.0 import-local: 3.2.0 - jest-cli: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + jest-cli: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -16613,9 +16902,9 @@ snapshots: lex-parser: 0.1.4 nomnom: 1.5.2 - jiti@2.6.1: {} + jiti@2.7.0: {} - jose@6.2.2: {} + jose@6.2.3: {} js-tokens@4.0.0: {} @@ -16648,7 +16937,7 @@ snapshots: whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 14.2.0 - ws: 8.20.0 + ws: 8.20.1 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -16681,7 +16970,7 @@ snapshots: jsonc-parser@3.3.1: {} - jsonfile@6.2.0: + jsonfile@6.2.1: dependencies: universalify: 2.0.1 optionalDependencies: @@ -16705,13 +16994,13 @@ snapshots: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 7.7.4 + semver: 7.8.0 jspdf@4.2.1: dependencies: '@babel/runtime': 7.29.2 fast-png: 6.4.0 - fflate: 0.8.2 + fflate: 0.8.3 optionalDependencies: canvg: 3.0.11 core-js: 3.49.0 @@ -16733,7 +17022,7 @@ snapshots: dependencies: source-map-support: 0.5.21 - keycharm@0.2.0: {} + keycharm@0.4.0: {} keyv@4.5.4: dependencies: @@ -16754,11 +17043,11 @@ snapshots: dependencies: dayjs: 1.11.20 - less-loader@12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)): + less-loader@12.3.1(less@4.4.2)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: less: 4.4.2 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) less@4.4.2: dependencies: @@ -16787,13 +17076,13 @@ snapshots: dependencies: isomorphic.js: 0.2.5 - libphonenumber-js@1.12.40: {} + libphonenumber-js@1.13.2: {} - license-webpack-plugin@4.0.2(webpack@5.105.2(esbuild@0.27.3)): + license-webpack-plugin@4.0.2(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) lie@3.1.1: dependencies: @@ -16862,7 +17151,7 @@ snapshots: lmdb@3.5.1: dependencies: '@harperfast/extended-iterable': 1.0.3 - msgpackr: 1.11.9 + msgpackr: 1.11.12 node-addon-api: 6.1.0 node-gyp-build-optional-packages: 5.2.2 ordered-binary: 1.6.1 @@ -16879,7 +17168,7 @@ snapshots: load-esm@1.0.3: {} - loader-runner@4.3.1: {} + loader-runner@4.3.2: {} loader-utils@2.0.4: dependencies: @@ -16947,9 +17236,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.3: {} - - lru-cache@11.3.5: {} + lru-cache@11.3.6: {} lru-cache@5.1.1: dependencies: @@ -16977,7 +17264,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.4 + semver: 7.8.0 make-error@1.3.6: {} @@ -17020,16 +17307,16 @@ snapshots: dependencies: fs-monkey: 1.1.0 - memfs@4.57.1(tslib@2.8.1): + memfs@4.57.2(tslib@2.8.1): dependencies: - '@jsonjoy.com/fs-core': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.57.1(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.57.1(tslib@2.8.1) + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) glob-to-regex.js: 1.2.0(tslib@2.8.1) @@ -17072,29 +17359,25 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.10.0(webpack@5.105.2(esbuild@0.27.3)): + mini-css-extract-plugin@2.10.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: schema-utils: 4.3.3 - tapable: 2.3.2 - webpack: 5.105.2(esbuild@0.27.3) + tapable: 2.3.3 + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) minimalistic-assert@1.0.1: {} - minimatch@10.2.4: - dependencies: - brace-expansion: 5.0.5 - minimatch@10.2.5: dependencies: - brace-expansion: 5.0.5 + brace-expansion: 5.0.6 minimatch@3.1.5: dependencies: - brace-expansion: 1.1.13 + brace-expansion: 1.1.14 minimatch@9.0.9: dependencies: - brace-expansion: 2.0.3 + brace-expansion: 2.1.0 minimist@1.2.8: {} @@ -17166,7 +17449,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 optional: true - msgpackr@1.11.9: + msgpackr@1.11.12: optionalDependencies: msgpackr-extract: 3.0.3 optional: true @@ -17185,8 +17468,6 @@ snapshots: mute-stream@2.0.0: {} - nanoid@3.3.11: {} - nanoid@3.3.12: {} napi-postinstall@0.3.4: {} @@ -17209,17 +17490,17 @@ snapshots: neotraverse@0.6.18: {} - ngx-color-picker@20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2)): + ngx-color-picker@20.1.1(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/forms@21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2)): dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) - '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1)))(rxjs@7.8.2) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) + '@angular/forms': 21.2.9(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(@angular/platform-browser@21.2.9(@angular/animations@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2)))(rxjs@7.8.2) tslib: 2.8.1 - ngx-toastr@20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2): + ngx-toastr@20.0.5(@angular/common@21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2))(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2): dependencies: - '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1))(rxjs@7.8.2) - '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.1) + '@angular/common': 21.2.9(@angular/core@21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2))(rxjs@7.8.2) + '@angular/core': 21.2.9(@angular/compiler@21.2.9)(rxjs@7.8.2)(zone.js@0.16.2) rxjs: 7.8.2 tslib: 2.8.1 @@ -17235,29 +17516,34 @@ snapshots: dependencies: lodash: 4.18.1 + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + node-gyp-build-optional-packages@5.2.2: dependencies: detect-libc: 2.1.2 optional: true - node-gyp@12.2.0: + node-gyp@12.3.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 graceful-fs: 4.2.11 - make-fetch-happen: 15.0.5 nopt: 9.0.0 proc-log: 6.1.0 semver: 7.7.4 - tar: 7.5.13 + tar: 7.5.15 tinyglobby: 0.2.16 + undici: 6.25.0 which: 6.0.1 - transitivePeerDependencies: - - supports-color node-int64@0.4.0: {} - node-releases@2.0.37: {} + node-releases@2.0.44: {} nomnom@1.5.2: dependencies: @@ -17271,7 +17557,7 @@ snapshots: normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 - semver: 7.7.4 + semver: 7.8.0 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} @@ -17288,7 +17574,7 @@ snapshots: npm-package-arg@13.0.2: dependencies: - hosted-git-info: 9.0.2 + hosted-git-info: 9.0.3 proc-log: 6.1.0 semver: 7.7.4 validate-npm-package-name: 7.0.2 @@ -17338,29 +17624,36 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 has-symbols: 1.1.0 object-keys: 1.1.1 + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 object.groupby@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -17443,7 +17736,7 @@ snapshots: is-unicode-supported: 2.1.0 log-symbols: 7.0.1 stdin-discarder: 0.2.2 - string-width: 8.2.0 + string-width: 8.2.1 strip-ansi: 7.2.0 ora@9.3.0: @@ -17454,8 +17747,8 @@ snapshots: is-interactive: 2.0.0 is-unicode-supported: 2.1.0 log-symbols: 7.0.1 - stdin-discarder: 0.3.1 - string-width: 8.2.0 + stdin-discarder: 0.3.2 + string-width: 8.2.1 ordered-binary@1.6.1: optional: true @@ -17492,7 +17785,7 @@ snapshots: p-retry@6.2.1: dependencies: '@types/retry': 0.12.2 - is-network-error: 1.3.1 + is-network-error: 1.3.2 retry: 0.13.1 p-try@2.2.0: {} @@ -17517,7 +17810,7 @@ snapshots: promise-retry: 2.0.1 sigstore: 4.1.0 ssri: 13.0.1 - tar: 7.5.13 + tar: 7.5.15 transitivePeerDependencies: - supports-color @@ -17590,7 +17883,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.5 + lru-cache: 11.3.6 minipass: 7.1.3 path-to-regexp@0.1.13: {} @@ -17667,17 +17960,17 @@ snapshots: pkijs@3.4.0: dependencies: '@noble/hashes': 1.4.0 - asn1js: 3.0.7 + asn1js: 3.0.10 bytestreamjs: 2.0.1 pvtsutils: 1.3.6 pvutils: 1.1.5 tslib: 2.8.1 - playwright-core@1.58.2: {} + playwright-core@1.60.0: {} - playwright@1.58.2: + playwright@1.60.0: dependencies: - playwright-core: 1.58.2 + playwright-core: 1.60.0 optionalDependencies: fsevents: 2.3.2 @@ -17692,43 +17985,43 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(postcss@8.5.10)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)): + postcss-loader@8.2.0(postcss@8.5.14)(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: cosmiconfig: 9.0.1(typescript@5.9.3) - jiti: 2.6.1 - postcss: 8.5.10 + jiti: 2.7.0 + postcss: 8.5.14 semver: 7.7.4 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) transitivePeerDependencies: - typescript postcss-media-query-parser@0.2.3: {} - postcss-modules-extract-imports@3.1.0(postcss@8.5.15): + postcss-modules-extract-imports@3.1.0(postcss@8.5.14): dependencies: - postcss: 8.5.15 + postcss: 8.5.14 - postcss-modules-local-by-default@4.2.0(postcss@8.5.15): + postcss-modules-local-by-default@4.2.0(postcss@8.5.14): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 + icss-utils: 5.1.0(postcss@8.5.14) + postcss: 8.5.14 postcss-selector-parser: 7.1.1 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.1(postcss@8.5.15): + postcss-modules-scope@3.2.1(postcss@8.5.14): dependencies: - postcss: 8.5.15 + postcss: 8.5.14 postcss-selector-parser: 7.1.1 - postcss-modules-values@4.0.0(postcss@8.5.15): + postcss-modules-values@4.0.0(postcss@8.5.14): dependencies: - icss-utils: 5.1.0(postcss@8.5.15) - postcss: 8.5.15 + icss-utils: 5.1.0(postcss@8.5.14) + postcss: 8.5.14 - postcss-safe-parser@7.0.1(postcss@8.5.15): + postcss-safe-parser@7.0.1(postcss@8.5.14): dependencies: - postcss: 8.5.15 + postcss: 8.5.14 postcss-selector-parser@7.1.1: dependencies: @@ -17737,13 +18030,7 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.10: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - postcss@8.5.15: + postcss@8.5.14: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -17761,20 +18048,13 @@ snapshots: powershell-utils@0.1.0: {} - pq@0.0.3: - dependencies: - async: 3.2.6 - redis: 5.11.0 - transitivePeerDependencies: - - '@node-rs/xxhash' - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 - prettier@3.8.1: {} + prettier@3.8.3: {} pretty-format@30.3.0: dependencies: @@ -17782,6 +18062,13 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.6 + prismjs@1.30.0: {} proc-log@6.1.0: {} @@ -17824,14 +18111,6 @@ snapshots: qrcode-generator@1.5.2: {} - qs@6.14.2: - dependencies: - side-channel: 1.1.0 - - qs@6.15.0: - dependencies: - side-channel: 1.1.0 - qs@6.15.1: dependencies: side-channel: 1.1.0 @@ -17870,6 +18149,8 @@ snapshots: react-is@18.3.1: {} + react-is@19.2.6: {} + read-pkg@9.0.1: dependencies: '@types/normalize-package-data': 2.4.4 @@ -17902,23 +18183,13 @@ snapshots: readdirp@5.0.0: {} - redis@5.11.0: - dependencies: - '@redis/bloom': 5.11.0(@redis/client@5.11.0) - '@redis/client': 5.11.0 - '@redis/json': 5.11.0(@redis/client@5.11.0) - '@redis/search': 5.11.0(@redis/client@5.11.0) - '@redis/time-series': 5.11.0(@redis/client@5.11.0) - transitivePeerDependencies: - - '@node-rs/xxhash' - reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -17938,7 +18209,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -17964,8 +18235,6 @@ snapshots: require-from-string@2.0.2: {} - requireindex@1.2.0: {} - requires-port@1.0.0: {} resolve-cwd@3.0.0: @@ -17983,19 +18252,22 @@ snapshots: adjust-sourcemap-loader: 4.0.0 convert-source-map: 1.9.0 loader-utils: 2.0.4 - postcss: 8.5.15 + postcss: 8.5.14 source-map: 0.6.1 - resolve@1.22.11: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - resolve@1.22.12: + resolve@2.0.0-next.7: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.1 + is-core-module: 2.16.2 + node-exports-info: 1.6.0 + object-keys: 1.1.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -18027,28 +18299,7 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.15: - dependencies: - '@oxc-project/types': 0.124.0 - '@rolldown/pluginutils': 1.0.0-rc.15 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 - '@rolldown/binding-darwin-x64': 1.0.0-rc.15 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 - - rolldown@1.0.0-rc.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2): + rolldown@1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: '@oxc-project/types': 0.113.0 '@rolldown/pluginutils': 1.0.0-rc.4 @@ -18063,42 +18314,63 @@ snapshots: '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.4 '@rolldown/binding-linux-x64-musl': 1.0.0-rc.4 '@rolldown/binding-openharmony-arm64': 1.0.0-rc.4 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.4 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.4 transitivePeerDependencies: - '@emnapi/core' - '@emnapi/runtime' - rollup@4.60.1: + rolldown@1.0.1: + dependencies: + '@oxc-project/types': 0.130.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.1 + '@rolldown/binding-darwin-arm64': 1.0.1 + '@rolldown/binding-darwin-x64': 1.0.1 + '@rolldown/binding-freebsd-x64': 1.0.1 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 + '@rolldown/binding-linux-arm64-gnu': 1.0.1 + '@rolldown/binding-linux-arm64-musl': 1.0.1 + '@rolldown/binding-linux-ppc64-gnu': 1.0.1 + '@rolldown/binding-linux-s390x-gnu': 1.0.1 + '@rolldown/binding-linux-x64-gnu': 1.0.1 + '@rolldown/binding-linux-x64-musl': 1.0.1 + '@rolldown/binding-openharmony-arm64': 1.0.1 + '@rolldown/binding-wasm32-wasi': 1.0.1 + '@rolldown/binding-win32-arm64-msvc': 1.0.1 + '@rolldown/binding-win32-x64-msvc': 1.0.1 + + rollup@4.60.4: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.1 - '@rollup/rollup-android-arm64': 4.60.1 - '@rollup/rollup-darwin-arm64': 4.60.1 - '@rollup/rollup-darwin-x64': 4.60.1 - '@rollup/rollup-freebsd-arm64': 4.60.1 - '@rollup/rollup-freebsd-x64': 4.60.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 - '@rollup/rollup-linux-arm-musleabihf': 4.60.1 - '@rollup/rollup-linux-arm64-gnu': 4.60.1 - '@rollup/rollup-linux-arm64-musl': 4.60.1 - '@rollup/rollup-linux-loong64-gnu': 4.60.1 - '@rollup/rollup-linux-loong64-musl': 4.60.1 - '@rollup/rollup-linux-ppc64-gnu': 4.60.1 - '@rollup/rollup-linux-ppc64-musl': 4.60.1 - '@rollup/rollup-linux-riscv64-gnu': 4.60.1 - '@rollup/rollup-linux-riscv64-musl': 4.60.1 - '@rollup/rollup-linux-s390x-gnu': 4.60.1 - '@rollup/rollup-linux-x64-gnu': 4.60.1 - '@rollup/rollup-linux-x64-musl': 4.60.1 - '@rollup/rollup-openbsd-x64': 4.60.1 - '@rollup/rollup-openharmony-arm64': 4.60.1 - '@rollup/rollup-win32-arm64-msvc': 4.60.1 - '@rollup/rollup-win32-ia32-msvc': 4.60.1 - '@rollup/rollup-win32-x64-gnu': 4.60.1 - '@rollup/rollup-win32-x64-msvc': 4.60.1 + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 fsevents: 2.3.3 router@2.2.0: @@ -18129,9 +18401,9 @@ snapshots: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -18162,14 +18434,14 @@ snapshots: is-plain-object: 5.0.0 launder: 1.7.1 parse-srcset: 1.0.2 - postcss: 8.5.15 + postcss: 8.5.14 - sass-loader@16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)): + sass-loader@16.0.7(sass@1.97.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: neo-async: 2.6.2 optionalDependencies: sass: 1.97.3 - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) sass@1.97.3: dependencies: @@ -18189,8 +18461,8 @@ snapshots: schema-utils@3.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 6.14.0 - ajv-keywords: 3.5.2(ajv@6.14.0) + ajv: 6.15.0 + ajv-keywords: 3.5.2(ajv@6.15.0) schema-utils@4.3.3: dependencies: @@ -18199,13 +18471,13 @@ snapshots: ajv-formats: 2.1.1(ajv@8.18.0) ajv-keywords: 5.1.0(ajv@8.18.0) - secretlint@11.4.1: + secretlint@11.7.1: dependencies: - '@secretlint/config-creator': 11.4.1 - '@secretlint/formatter': 11.4.1 - '@secretlint/node': 11.4.1 - '@secretlint/profiler': 11.4.1 - '@secretlint/resolver': 11.4.1 + '@secretlint/config-creator': 11.7.1 + '@secretlint/formatter': 11.7.1 + '@secretlint/node': 11.7.1 + '@secretlint/profiler': 11.7.1 + '@secretlint/resolver': 11.7.1 debug: 4.4.3 globby: 14.1.0 read-pkg: 9.0.1 @@ -18226,6 +18498,8 @@ snapshots: semver@7.7.4: {} + semver@7.8.0: {} + send@0.19.2: dependencies: debug: 2.6.9 @@ -18438,7 +18712,7 @@ snapshots: base64id: 2.0.0 cors: 2.8.6 debug: 4.4.3 - engine.io: 6.6.6 + engine.io: 6.6.7 socket.io-adapter: 2.5.6 socket.io-parser: 4.2.6 transitivePeerDependencies: @@ -18456,22 +18730,22 @@ snapshots: dependencies: agent-base: 7.1.4 debug: 4.4.3 - socks: 2.8.7 + socks: 2.8.9 transitivePeerDependencies: - supports-color - socks@2.8.7: + socks@2.8.9: dependencies: ip-address: 10.2.0 smart-buffer: 4.2.0 source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.105.2(esbuild@0.27.3)): + source-map-loader@5.0.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) source-map-support@0.5.13: dependencies: @@ -18563,7 +18837,7 @@ snapshots: stdin-discarder@0.2.2: {} - stdin-discarder@0.3.1: {} + stdin-discarder@0.3.2: {} stop-iteration-iterator@1.1.0: dependencies: @@ -18597,34 +18871,34 @@ snapshots: string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 - string-width@8.2.0: + string-width@8.2.1: dependencies: - get-east-asian-width: 1.5.0 + get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -18670,7 +18944,7 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.15.0 + qs: 6.15.1 transitivePeerDependencies: - supports-color @@ -18718,11 +18992,11 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - tablesort@5.7.0: {} + tablesort@5.7.1: {} - tapable@2.3.2: {} + tapable@2.3.3: {} - tar@7.5.13: + tar@7.5.15: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -18735,22 +19009,24 @@ snapshots: ansi-escapes: 7.3.0 supports-hyperlinks: 3.2.0 - terser-webpack-plugin@5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)): + terser-webpack-plugin@5.6.0(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 - webpack: 5.105.2(esbuild@0.27.3) + terser: 5.47.1 + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) optionalDependencies: esbuild: 0.27.3 + lightningcss: 1.32.0 + postcss: 8.5.14 - terser-webpack-plugin@5.4.0(webpack@5.106.0): + terser-webpack-plugin@5.6.0(webpack@5.106.0): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 - terser: 5.46.1 + terser: 5.47.1 webpack: 5.106.0 terser@5.46.0: @@ -18760,7 +19036,7 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.46.1: + terser@5.47.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.16.0 @@ -18769,7 +19045,7 @@ snapshots: test-exclude@6.0.0: dependencies: - '@istanbuljs/schema': 0.1.3 + '@istanbuljs/schema': 0.1.6 glob: 7.2.3 minimatch: 3.1.5 @@ -18852,33 +19128,33 @@ snapshots: dependencies: typescript: 5.9.3 - ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.3.0)(@jest/types@30.3.0)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.27.4)(jest-util@30.3.0)(jest@30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.4.1)(@jest/types@30.4.1)(babel-jest@30.3.0(@babel/core@7.29.0))(esbuild@0.28.0)(jest-util@30.4.1)(jest@30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.9 - jest: 30.3.0(@types/node@25.5.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)) + jest: 30.3.0(@types/node@25.8.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.4 + semver: 7.8.0 type-fest: 4.41.0 typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.29.0 - '@jest/transform': 30.3.0 - '@jest/types': 30.3.0 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 babel-jest: 30.3.0(@babel/core@7.29.0) - esbuild: 0.27.4 - jest-util: 30.3.0 + esbuild: 0.28.0 + jest-util: 30.4.1 - ts-loader@9.5.4(typescript@5.9.3)(webpack@5.106.0): + ts-loader@9.5.7(typescript@5.9.3)(webpack@5.106.0): dependencies: chalk: 4.1.2 - enhanced-resolve: 5.20.1 + enhanced-resolve: 5.21.3 micromatch: 4.0.8 - semver: 7.7.4 + semver: 7.8.0 source-map: 0.7.6 typescript: 5.9.3 webpack: 5.106.0 @@ -18888,14 +19164,14 @@ snapshots: '@ts-morph/common': 0.28.1 code-block-writer: 13.0.3 - ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3): + ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.12 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 25.5.0 + '@types/node': 25.8.0 acorn: 8.16.0 acorn-walk: 8.3.5 arg: 4.1.3 @@ -18909,8 +19185,8 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.20.1 - tapable: 2.3.2 + enhanced-resolve: 5.21.3 + tapable: 2.3.3 tsconfig-paths: 4.2.0 tsconfig-paths@3.15.0: @@ -18957,9 +19233,9 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 - type-is@2.0.1: + type-is@2.1.0: dependencies: - content-type: 1.0.5 + content-type: 2.0.0 media-typer: 1.1.0 mime-types: 3.0.2 @@ -18971,7 +19247,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -18980,7 +19256,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -18989,7 +19265,7 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 @@ -19000,10 +19276,10 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.28(pg@8.20.0)(redis@5.11.0)(ts-node@10.9.2(@types/node@25.5.0)(typescript@5.9.3)): + typeorm@0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)): dependencies: '@sqltools/formatter': 1.2.5 - ansis: 4.2.0 + ansis: 4.3.0 app-root-path: 3.1.0 buffer: 6.0.3 dayjs: 1.11.20 @@ -19019,19 +19295,18 @@ snapshots: yargs: 17.7.2 optionalDependencies: pg: 8.20.0 - redis: 5.11.0 - ts-node: 10.9.2(@types/node@25.5.0)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.8.0)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - typescript-eslint@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.57.2(@typescript-eslint/parser@8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.57.2(typescript@5.9.3) - '@typescript-eslint/utils': 8.57.2(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 10.1.0(jiti@2.6.1) + '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.59.3(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.3(eslint@10.4.0(jiti@2.7.0))(typescript@5.9.3) + eslint: 10.4.0(jiti@2.7.0) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -19056,12 +19331,14 @@ snapshots: underscore@1.13.8: {} - undici-types@7.18.2: {} + undici-types@7.24.6: {} - undici@7.24.0: {} + undici@6.25.0: {} undici@7.24.4: {} + undici@7.25.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-match-property-ecmascript@2.0.0: @@ -19140,7 +19417,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - valibot@1.3.1(typescript@5.9.3): + valibot@1.4.0(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 @@ -19151,24 +19428,24 @@ snapshots: validate-npm-package-name@7.0.2: {} - validator@13.15.26: {} + validator@13.15.35: {} vary@1.1.2: {} version-range@4.15.0: {} - vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): + vis-data@8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): dependencies: uuid: 13.0.1 vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1) - vis-network@10.0.2(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.2.0)(uuid@13.0.1)(vis-data@8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): + vis-network@10.1.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)(keycharm@0.4.0)(uuid@13.0.1)(vis-data@8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)))(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)): dependencies: '@egjs/hammerjs': 2.0.17 component-emitter: 1.3.1 - keycharm: 0.2.0 + keycharm: 0.4.0 uuid: 13.0.1 - vis-data: 8.0.3(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) + vis-data: 8.0.4(uuid@13.0.1)(vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1)) vis-util: 6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1) vis-util@6.0.0(@egjs/hammerjs@2.0.17)(component-emitter@1.3.1): @@ -19176,38 +19453,38 @@ snapshots: '@egjs/hammerjs': 2.0.17 component-emitter: 1.3.1 - vite@7.3.2(@types/node@25.5.0)(jiti@2.6.1)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): + vite@7.3.2(@types/node@25.8.0)(jiti@2.7.0)(less@4.4.2)(lightningcss@1.32.0)(sass@1.97.3)(terser@5.46.0): dependencies: - esbuild: 0.27.4 + esbuild: 0.27.3 fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 - postcss: 8.5.15 - rollup: 4.60.1 - tinyglobby: 0.2.16 + postcss: 8.5.14 + rollup: 4.60.4 + tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.5.0 + '@types/node': 25.8.0 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 less: 4.4.2 lightningcss: 1.32.0 sass: 1.97.3 terser: 5.46.0 - vite@8.0.8(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(less@4.4.2)(sass@1.97.3)(terser@5.46.1): + vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(jiti@2.7.0)(less@4.4.2)(sass@1.97.3)(terser@5.47.1): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.10 - rolldown: 1.0.0-rc.15 + postcss: 8.5.14 + rolldown: 1.0.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.0 - esbuild: 0.27.4 + '@types/node': 25.8.0 + esbuild: 0.28.0 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 2.7.0 less: 4.4.2 sass: 1.97.3 - terser: 5.46.1 + terser: 5.47.1 w3c-xmlserializer@5.0.0: dependencies: @@ -19235,20 +19512,20 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: colorette: 2.0.20 - memfs: 4.57.1(tslib@2.8.1) + memfs: 4.57.2(tslib@2.8.1) mime-types: 3.0.2 on-finished: 2.4.1 range-parser: 1.2.1 schema-utils: 4.3.3 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) transitivePeerDependencies: - tslib - webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)): + webpack-dev-server@5.2.3(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -19264,10 +19541,10 @@ snapshots: colorette: 2.0.20 compression: 1.8.1 connect-history-api-fallback: 2.0.0 - express: 4.22.1 + express: 4.22.2 graceful-fs: 4.2.11 http-proxy-middleware: 2.0.9(@types/express@4.17.25) - ipaddr.js: 2.3.0 + ipaddr.js: 2.4.0 launch-editor: 2.13.2 open: 10.2.0 p-retry: 6.2.1 @@ -19276,10 +19553,10 @@ snapshots: serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)) - ws: 8.20.0 + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) + ws: 8.20.1 optionalDependencies: - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) transitivePeerDependencies: - bufferutil - debug @@ -19295,17 +19572,17 @@ snapshots: webpack-node-externals@3.0.0: {} - webpack-sources@3.3.4: {} + webpack-sources@3.4.1: {} - webpack-subresource-integrity@5.1.0(webpack@5.105.2(esbuild@0.27.3)): + webpack-subresource-integrity@5.1.0(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: typed-assert: 1.0.9 - webpack: 5.105.2(esbuild@0.27.3) + webpack: 5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14) - webpack@5.105.2(esbuild@0.27.3): + webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -19314,30 +19591,39 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.20.1 - es-module-lexer: 2.0.0 + enhanced-resolve: 5.21.3 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 - tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(esbuild@0.27.3)(webpack@5.105.2(esbuild@0.27.3)) + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14)) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js webpack@5.106.0: dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 @@ -19346,24 +19632,33 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.16.0) browserslist: 4.28.2 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.20.1 - es-module-lexer: 2.0.0 + enhanced-resolve: 5.21.3 + es-module-lexer: 2.1.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.1 + loader-runner: 4.3.2 mime-types: 2.1.35 neo-async: 2.6.2 schema-utils: 4.3.3 - tapable: 2.3.2 - terser-webpack-plugin: 5.4.0(webpack@5.106.0) + tapable: 2.3.3 + terser-webpack-plugin: 5.6.0(webpack@5.106.0) watchpack: 2.5.1 - webpack-sources: 3.3.4 + webpack-sources: 3.4.1 transitivePeerDependencies: + - '@minify-html/node' - '@swc/core' + - '@swc/css' + - '@swc/html' + - clean-css + - cssnano + - csso - esbuild + - html-minifier-terser + - lightningcss + - postcss - uglify-js websocket-driver@0.7.4: @@ -19419,7 +19714,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -19477,7 +19772,7 @@ snapshots: ws@8.18.3: {} - ws@8.20.0: {} + ws@8.20.1: {} wsl-utils@0.1.0: dependencies: @@ -19556,4 +19851,4 @@ snapshots: zod@4.3.6: {} - zone.js@0.16.1: {} + zone.js@0.16.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7f607aad..e947f116 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,8 @@ packages: - 'teammapper-backend' - 'teammapper-frontend' - 'teammapper-frontend/packages/*' +# 7-day quarantine on newly published versions (mitigates fast-revoked malicious releases). +minimumReleaseAge: 10080 allowBuilds: '@compodoc/compodoc': false '@nestjs/core': false @@ -11,3 +13,18 @@ allowBuilds: lmdb: false msgpackr-extract: false unrs-resolver: false +# Transitive-dep CVE pins. Audit periodically with `pnpm why ` and remove +# entries whose selector no longer matches any node in pnpm-lock.yaml. +overrides: + 'multer@<2.1.1': '2.1.1' + 'ajv@>=7.0.0-alpha.0 <8.18.0': '8.18.0' + 'underscore@<=1.13.7': '1.13.8' + 'socket.io-parser@>=4.0.0 <4.2.6': '4.2.6' + 'picomatch@>=4.0.0 <4.0.4': '>=4.0.4' + 'lodash@<4.18.0': '>=4.18.0' + 'follow-redirects@<1.16.0': '>=1.16.0' + 'hono@<4.12.18': '>=4.12.18' + 'fast-uri@<3.1.2': '>=3.1.2' + 'postcss@<8.5.10': '>=8.5.10' + '@babel/plugin-transform-modules-systemjs@<7.29.4': '>=7.29.4' + 'uuid@>=11.0.0 <11.1.1': '>=11.1.1' diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index 8b502db1..ef6293b1 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -60,7 +60,6 @@ "jsonwebtoken": "^9.0.3", "lib0": "^0.2.117", "pg": "^8.20.0", - "pq": "^0.0.3", "reflect-metadata": "^0.2.2", "rimraf": "^6.1.3", "rxjs": "^7.8.2", @@ -71,11 +70,9 @@ "valibot": "^1.3.1", "ws": "^8.20.0", "y-protocols": "^1.0.7", - "y-websocket": "^3.0.0", "yjs": "^13.6.30" }, "devDependencies": { - "@eslint/compat": "^2.0.3", "@eslint/js": "^10.0.1", "@golevelup/ts-jest": "^3.0.0", "@jest/globals": "^30.3.0", @@ -100,9 +97,7 @@ "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-import": "2.32.0", "eslint-plugin-jest": "29.15.1", - "eslint-plugin-nestjs": "1.2.3", "eslint-plugin-prettier": "^5.5.5", - "eslint-plugin-typeorm": "0.0.19", "globals": "^17.4.0", "jest": "30.3.0", "prettier": "^3.8.1", diff --git a/teammapper-frontend/e2e/node-editing.spec.ts-snapshots/node-after-drag-webkit-linux.png b/teammapper-frontend/e2e/node-editing.spec.ts-snapshots/node-after-drag-webkit-linux.png index 7e6c1813377e7f3723502e326444fe00e1963edb..20078d95922a86f9ab8664b247b5d602742b2190 100644 GIT binary patch literal 36665 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%0Ad`fsHqN;l-c77Xf@(UQT*ElTR%SF-jfE{RQ9L!~ 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 u-YFwAtNaBC+N%CA#%P^7bUom^BBtCM+ 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 literal 33692 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 Date: Sun, 24 May 2026 15:06:06 +0200 Subject: [PATCH 08/14] cleanup dockerfile folder (#1299) --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 575625d5..265e0e6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,12 +48,12 @@ RUN apk add --no-cache tini postgresql-client USER node ENV NODE_ENV=production -ENV APP_BACKEND_PATH=${APP_PATH}/teammapper-backend -WORKDIR $APP_BACKEND_PATH +ENV APP_PROD_PATH=${APP_PATH}/teammapper +WORKDIR $APP_PROD_PATH COPY --from=builder --chown=node:node /home/node/deploy/ ./ COPY --chown=node:node teammapper-backend/config ./config COPY --chown=node:node --chmod=755 entrypoint.prod.sh ./ ENTRYPOINT ["/sbin/tini", "--"] -CMD ["/home/node/app/teammapper-backend/entrypoint.prod.sh"] +CMD ["/home/node/app/teammapper/entrypoint.prod.sh"] From e846712cde9576109014987b23714549eb0db184 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 24 May 2026 17:16:45 +0200 Subject: [PATCH 09/14] refactor: cleanup after yjs integration (#1300) --- .env.default | 1 - README.md | 3 - docker-compose.yml | 3 +- openspec/changes/yjs-introduction/tasks.md | 30 +- pnpm-lock.yaml | 250 +----- teammapper-backend/.env.default | 1 - teammapper-backend/README.md | 1 - teammapper-backend/package.json | 11 +- teammapper-backend/src/config.service.spec.ts | 27 - teammapper-backend/src/config.service.ts | 5 - .../src/filters/global-exception.filter.ts | 23 +- .../src/map/controllers/gateway-helpers.ts | 189 ----- .../map/controllers/maps.controller.spec.ts | 143 +++- .../src/map/controllers/maps.controller.ts | 26 +- .../src/map/controllers/maps.gateway.spec.ts | 677 ---------------- .../src/map/controllers/maps.gateway.ts | 551 ------------- .../src/map/guards/edit.guard.spec.ts | 91 --- .../src/map/guards/edit.guard.ts | 23 - teammapper-backend/src/map/map.module.ts | 36 +- .../src/map/schemas/gateway.schema.spec.ts | 315 -------- .../src/map/schemas/gateway.schema.ts | 150 ---- .../src/map/schemas/maps.schema.spec.ts | 23 +- .../src/map/schemas/maps.schema.ts | 12 +- .../src/map/services/maps.service.spec.ts | 620 +-------------- .../src/map/services/maps.service.ts | 744 +----------------- teammapper-backend/src/map/types.ts | 103 +-- .../src/map/utils/clientServerMapping.spec.ts | 60 -- .../src/map/utils/clientServerMapping.ts | 28 - .../src/map/utils/nodeValidation.spec.ts | 122 --- .../src/map/utils/nodeValidation.ts | 12 - .../src/settings/settings.service.spec.ts | 23 +- .../src/settings/settings.service.ts | 1 - .../src/settings/settings.types.ts | 1 - teammapper-backend/test/app.e2e-spec.ts | 231 ------ teammapper-backend/test/jest-e2e.json | 3 +- teammapper-backend/test/jest-setup.ts | 7 - .../test/map-operations-error.e2e-spec.ts | 278 ------- teammapper-backend/tsconfig.json | 2 +- teammapper-frontend/package.json | 1 - .../core/services/dialog/dialog.service.ts | 33 - .../map-sync/map-sync-error-handler.spec.ts | 210 ----- .../map-sync/map-sync-error-handler.ts | 155 ---- .../map-sync/map-sync.service.spec.ts | 53 +- .../services/map-sync/map-sync.service.ts | 66 +- .../core/services/map-sync/server-types.ts | 132 +--- .../map-sync/socket-io-sync.service.spec.ts | 244 ------ .../map-sync/socket-io-sync.service.ts | 664 ---------------- .../core/services/map-sync/sync-strategy.ts | 31 - .../services/map-sync/yjs-sync.service.ts | 9 +- .../core/services/toast/toast.service.spec.ts | 259 ------ .../app/core/services/toast/toast.service.ts | 104 --- .../dialog-critical-error.component.html | 19 - .../dialog-critical-error.component.scss | 39 - .../dialog-critical-error.component.spec.ts | 64 -- .../dialog-critical-error.component.ts | 55 -- .../toolbar/toolbar.component.spec.ts | 2 +- .../src/app/shared/models/settings.model.ts | 1 - teammapper-frontend/src/assets/i18n/de.json | 10 +- teammapper-frontend/src/assets/i18n/en.json | 10 +- teammapper-frontend/src/assets/i18n/es.json | 10 +- teammapper-frontend/src/assets/i18n/fr.json | 10 +- teammapper-frontend/src/assets/i18n/it.json | 10 +- teammapper-frontend/src/assets/i18n/ja.json | 10 +- .../src/assets/i18n/pt-br.json | 10 +- .../src/assets/i18n/zh-cn.json | 10 +- .../src/assets/i18n/zh-tw.json | 10 +- teammapper-frontend/src/proxy.conf.json | 6 - .../src/test/mocks/utils-service.mock.ts | 29 +- 68 files changed, 325 insertions(+), 6767 deletions(-) delete mode 100644 teammapper-backend/src/map/controllers/gateway-helpers.ts delete mode 100644 teammapper-backend/src/map/controllers/maps.gateway.spec.ts delete mode 100644 teammapper-backend/src/map/controllers/maps.gateway.ts delete mode 100644 teammapper-backend/src/map/guards/edit.guard.spec.ts delete mode 100644 teammapper-backend/src/map/guards/edit.guard.ts delete mode 100644 teammapper-backend/src/map/schemas/gateway.schema.spec.ts delete mode 100644 teammapper-backend/src/map/schemas/gateway.schema.ts delete mode 100644 teammapper-backend/src/map/utils/nodeValidation.spec.ts delete mode 100644 teammapper-backend/src/map/utils/nodeValidation.ts delete mode 100644 teammapper-backend/test/app.e2e-spec.ts delete mode 100644 teammapper-backend/test/jest-setup.ts delete mode 100644 teammapper-backend/test/map-operations-error.e2e-spec.ts delete mode 100644 teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.spec.ts delete mode 100644 teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.ts delete mode 100644 teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.spec.ts delete mode 100644 teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.ts delete mode 100644 teammapper-frontend/src/app/core/services/map-sync/sync-strategy.ts delete mode 100644 teammapper-frontend/src/app/core/services/toast/toast.service.spec.ts delete mode 100644 teammapper-frontend/src/app/core/services/toast/toast.service.ts delete mode 100644 teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.html delete mode 100644 teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.scss delete mode 100644 teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.spec.ts delete mode 100644 teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.ts diff --git a/.env.default b/.env.default index d09d0275..e433e85e 100644 --- a/.env.default +++ b/.env.default @@ -41,7 +41,6 @@ POSTGRES_STATEMENT_TIMEOUT=100000 DELETE_AFTER_DAYS=30 -YJS_ENABLED=true AI_ENABLED=false DEV_BUILD_CONTEXT= diff --git a/README.md b/README.md index 15446368..0264f460 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,6 @@ services: POSTGRES_QUERY_TIMEOUT: 100000 POSTGRES_STATEMENT_TIMEOUT: 100000 DELETE_AFTER_DAYS: 30 - YJS_ENABLED: true AI_ENABLED: false ports: - 80:3000 @@ -237,7 +236,6 @@ The settings configuration includes the following feature flags: - `pictograms`: Disables/Enables the pictogram feature (default: disabled). Note: You have to set this flag before build time in the JSON config! - `ai`: Disables/Enables AI functionality like generating mindmaps with AI. Can be controlled via the `AI_ENABLED` environment variable (default: `false`). -- `yjs`: Disables/Enables the Yjs-based real-time collaboration. Can be controlled via the `YJS_ENABLED` environment variable (default: `false`). #### Environment variable overrides @@ -246,7 +244,6 @@ The following environment variables override the feature flags from the JSON con | Variable | Description | Default | | --- | --- | --- | | `AI_ENABLED` | Enable AI features (mindmap generation) | `false` | -| `YJS_ENABLED` | Enable Yjs-based real-time collaboration | `false` | | `DELETE_AFTER_DAYS` | Number of days before mindmaps are deleted | `30` | | `LOG_LEVEL` | NestJS log verbosity (`error`, `warn`, `log`, `debug`, `verbose`) | `debug` in DEV, `log` otherwise | diff --git a/docker-compose.yml b/docker-compose.yml index 1b96afb8..9fe54afb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,8 +31,7 @@ services: AI_LLM_TPM: ${DOCKER_COMPOSE_APP_ENV_AI_LLM_API_TPM} AI_LLM_TPD: ${DOCKER_COMPOSE_APP_ENV_AI_LLM_API_TPD} JWT_SECRET: ${JWT_SECRET} - YJS_ENABLED: ${YJS_ENABLED:-true} - LOG_LEVEL: ${LOG_LEVEL:-info} + LOG_LEVEL: ${LOG_LEVEL:-debug} TESTING_PLAYWRIGHT_WS_ENDPOINT: "ws://playwright:9323" TESTING_PLAYWRIGHT_BASE_URL: "http://app:4200" diff --git a/openspec/changes/yjs-introduction/tasks.md b/openspec/changes/yjs-introduction/tasks.md index 6e55f954..3c2b27db 100644 --- a/openspec/changes/yjs-introduction/tasks.md +++ b/openspec/changes/yjs-introduction/tasks.md @@ -207,18 +207,18 @@ > **PR scope**: Remove all Socket.io code paths, dependencies, and feature flag branching. The Yjs path becomes the only path. > **App state after merge**: App uses Yjs exclusively. Codebase is clean — no dual code paths, no unused dependencies. -- [ ] 13.1 Remove `MapsGateway` (Socket.io gateway with all `@SubscribeMessage` handlers) -- [ ] 13.2 Remove `EditGuard` (auth now at WebSocket handshake in `YjsGateway`) -- [ ] 13.3 Remove `cache-manager` client tracking logic from backend (replaced by Yjs Awareness) -- [ ] 13.4 Remove per-operation validation methods from `MapsService` that are only used by the Socket.io path (`mapConstraintErrorToValidationResponse`, `validateBusinessRules`, `handleDatabaseConstraintError`, `addNode`, `addNodes`, `addNodesFromClient`, `updateNode`, `removeNode`, `updateMapByDiff`) — keep methods used by REST API, persistence, or `updateMap` -- [ ] 13.5 Remove `@nestjs/platform-socket.io`, `socket.io`, `@nestjs/cache-manager`, and `cache-manager` from backend dependencies -- [ ] 13.6 Remove Socket.io types from backend `types.ts` that are no longer referenced (`IMmpClientNodeRequest`, `IMmpClientNodeAddRequest`, `IMmpClientUndoRedoRequest`, `IMmpClientEditingRequest`, `OperationResponse`, `ValidationErrorResponse`, etc.) — keep types still used -- [ ] 13.7 Remove the Socket.io code path from frontend `MapSyncService`: delete `initSocketIo()`, all `socket.emit` methods, all `socket.on` listener setup methods, `handleOperationResponse`, error handling helpers, `isValidServerMap`, `isValidErrorResponse` -- [ ] 13.8 Remove Socket.io types from frontend `server-types.ts` that are no longer used -- [ ] 13.9 Remove `socket.io-client` from frontend dependencies -- [ ] 13.10 Remove the `/socket.io` entry from frontend `proxy.conf.json` -- [ ] 13.11 Remove `featureFlagYjs` from frontend environments (no longer needed — Yjs is the only path) -- [ ] 13.12 Remove `YJS_ENABLED` from backend `ConfigService` and `YjsGateway` flag check (no longer needed) -- [ ] 13.13 Simplify `MapSyncService`: remove the branching logic, make the Yjs path the direct implementation -- [ ] 13.14 Verify backend and frontend build, lint, and all tests pass -- [ ] 13.15 Run E2E tests (`pnpm run playwright test --reporter=list`) +- [x] 13.1 Remove `MapsGateway` (Socket.io gateway with all `@SubscribeMessage` handlers) +- [x] 13.2 Remove `EditGuard` (auth now at WebSocket handshake in `YjsGateway`) +- [x] 13.3 Remove `cache-manager` client tracking logic from backend (replaced by Yjs Awareness) +- [x] 13.4 Remove per-operation validation methods from `MapsService` that are only used by the Socket.io path (`mapConstraintErrorToValidationResponse`, `validateBusinessRules`, `handleDatabaseConstraintError`, `addNode`, `addNodes`, `addNodesFromClient`, `updateNode`, `removeNode`, `updateMapByDiff`) — keep methods used by REST API, persistence, or `updateMap` +- [x] 13.5 Remove `@nestjs/platform-socket.io`, `socket.io`, `@nestjs/cache-manager`, and `cache-manager` from backend dependencies +- [x] 13.6 Remove Socket.io types from backend `types.ts` that are no longer referenced (`IMmpClientNodeRequest`, `IMmpClientNodeAddRequest`, `IMmpClientUndoRedoRequest`, `IMmpClientEditingRequest`, `OperationResponse`, `ValidationErrorResponse`, etc.) — keep types still used +- [x] 13.7 Remove the Socket.io code path from frontend `MapSyncService`: delete `initSocketIo()`, all `socket.emit` methods, all `socket.on` listener setup methods, `handleOperationResponse`, error handling helpers, `isValidServerMap`, `isValidErrorResponse` +- [x] 13.8 Remove Socket.io types from frontend `server-types.ts` that are no longer used +- [x] 13.9 Remove `socket.io-client` from frontend dependencies +- [x] 13.10 Remove the `/socket.io` entry from frontend `proxy.conf.json` +- [x] 13.11 Remove `featureFlagYjs` from frontend environments (no longer needed — Yjs is the only path) +- [x] 13.12 Remove `YJS_ENABLED` from backend `ConfigService` and `YjsGateway` flag check (no longer needed) +- [x] 13.13 Simplify `MapSyncService`: remove the branching logic, make the Yjs path the direct implementation +- [x] 13.14 Verify backend and frontend build, lint, and all tests pass +- [x] 13.15 Run E2E tests (`pnpm run playwright test --reporter=list`) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf3b31d0..110ef691 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,9 +49,6 @@ importers: '@ai-sdk/openai-compatible': specifier: 2.0.41 version: 2.0.41(zod@4.3.6) - '@nestjs/cache-manager': - specifier: ^3.1.0 - version: 3.1.2(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) '@nestjs/cli': specifier: ^11.0.21 version: 11.0.21(@types/node@25.8.0)(prettier@3.8.3) @@ -67,9 +64,6 @@ importers: '@nestjs/platform-express': specifier: ^11.1.19 version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) - '@nestjs/platform-socket.io': - specifier: ^11.1.19 - version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) '@nestjs/schedule': specifier: ^6.1.3 version: 6.1.3(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) @@ -81,16 +75,13 @@ importers: version: 11.0.1(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3))) '@nestjs/websockets': specifier: ^11.1.19 - version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + version: 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/uuid': specifier: ^11.0.0 version: 11.0.0 ai: specifier: 6.0.168 version: 6.0.168(zod@4.3.6) - cache-manager: - specifier: ^7.2.8 - version: 7.2.8 class-validator: specifier: ^0.15.1 version: 0.15.1 @@ -124,9 +115,6 @@ importers: sanitize-html: specifier: ^2.17.4 version: 2.17.4 - socket.io: - specifier: 4.8.3 - version: 4.8.3 typeorm: specifier: ^0.3.28 version: 0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) @@ -164,9 +152,6 @@ importers: '@stylistic/eslint-plugin': specifier: ^5.10.0 version: 5.10.0(eslint@10.4.0(jiti@2.7.0)) - '@types/cache-manager': - specifier: 5.0.0 - version: 5.0.0 '@types/cookie-parser': specifier: ^1.4.10 version: 1.4.10(@types/express@5.0.6) @@ -230,9 +215,6 @@ importers: prettier: specifier: ^3.8.1 version: 3.8.3 - socket.io-client: - specifier: ^4.8.3 - version: 4.8.3 supertest: specifier: ^7.2.2 version: 7.2.2 @@ -350,9 +332,6 @@ importers: rxjs: specifier: ~7.8.2 version: 7.8.2 - socket.io-client: - specifier: ~4.8.3 - version: 4.8.3 tslib: specifier: ^2.8.1 version: 2.8.1 @@ -1589,9 +1568,6 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} - '@cacheable/utils@2.4.1': - resolution: {integrity: sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==} - '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -2508,9 +2484,6 @@ packages: peerDependencies: tslib: '2' - '@keyv/serialize@1.1.1': - resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} - '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} @@ -2725,15 +2698,6 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@nestjs/cache-manager@3.1.2': - resolution: {integrity: sha512-Eglt8lUzC3Q3OZ2hFt4vLZ190M94YSJXUiKo67K/zlUgZQGtvxL0AYeKbG96x8+1gJTF7QhFpYw/RkQ28416Mw==} - peerDependencies: - '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 - '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 - cache-manager: '>=6' - keyv: '>=5' - rxjs: ^7.8.1 - '@nestjs/cli@11.0.21': resolution: {integrity: sha512-F8mV0Sj/zVEouzR3NxBuJy08YHTUOmC5Xdcx3qIIaJWzrm8Vw86CHkhkaPBJ5ewRMHPDCShPmhsfwhpCcjts3A==} engines: {node: '>= 20.11'} @@ -2790,13 +2754,6 @@ packages: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.21': - resolution: {integrity: sha512-Tq5JgaVS+auD3DXuRBy8UMU3mf69HJO8Ep+BuRS9GYMXGd/5sdMHqIQvXlXkGih9tQXdeeG9WoqURe/+IjPKng==} - peerDependencies: - '@nestjs/common': ^11.0.0 - '@nestjs/websockets': ^11.0.0 - rxjs: ^7.1.0 - '@nestjs/schedule@6.1.3': resolution: {integrity: sha512-RflMFOpR16Dwd1jAUbeB4mfGTCh65fvEdL4mSjQPJChpkRGRjIXjb+6YQcK2faQrVT60c9DmLmoVR7/ONCtuYQ==} peerDependencies: @@ -3510,9 +3467,6 @@ packages: '@sinonjs/fake-timers@15.4.0': resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} - '@socket.io/component-emitter@3.1.2': - resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} - '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} @@ -3603,10 +3557,6 @@ packages: '@types/bonjour@3.5.13': resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} - '@types/cache-manager@5.0.0': - resolution: {integrity: sha512-YaQBBhn2OIShxGMH7l1qzGgXCQJ2TRYTqMhFMXRBkDcXQIlqXv+XFkBGstwXSyv7q+JQm3HbpVhaVF8l5tr+Hg==} - deprecated: This is a stub types definition. cache-manager provides its own type definitions, so you do not need this installed. - '@types/connect-history-api-fallback@1.5.4': resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} @@ -3621,9 +3571,6 @@ packages: '@types/cookiejar@2.1.5': resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} - '@types/cors@2.8.19': - resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/cron@2.4.3': resolution: {integrity: sha512-ViRBkoZD9Rk0hGeMdd2GHGaOaZuH9mDmwsE5/Zo53Ftwcvh7h9VJc8lIt2wdgEwS4EW5lbtTX6vlE0idCLPOyA==} deprecated: This is a stub types definition. cron provides its own type definitions, so you do not need this installed. @@ -4414,10 +4361,6 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - base64id@2.0.0: - resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} - engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.10.30: resolution: {integrity: sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==} engines: {node: '>=6.0.0'} @@ -4530,9 +4473,6 @@ packages: resolution: {integrity: sha512-M3Lab8NPYlZU2exsL3bMVvMrMqgwCnMWfdZbK28bn3pK6APT/Te/I8hjRPNu1uwORY9a1eEQoifXbKPQMfMTOA==} engines: {node: ^20.17.0 || >=22.9.0} - cache-manager@7.2.8: - resolution: {integrity: sha512-0HDaDLBBY/maa/LmUVAr70XUOwsiQD+jyzCBjmUErYZUKdMS9dT59PqW59PpVqfGM7ve6H0J6307JTpkCYefHQ==} - call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -5247,17 +5187,6 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.4: - resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==} - - engine.io-parser@5.2.3: - resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} - engines: {node: '>=10.0.0'} - - engine.io@6.6.7: - resolution: {integrity: sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ==} - engines: {node: '>=10.2.0'} - enhanced-resolve@5.21.3: resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} engines: {node: '>=10.13.0'} @@ -5905,10 +5834,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hashery@1.5.1: - resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} - engines: {node: '>=20'} - hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -5917,9 +5842,6 @@ packages: resolution: {integrity: sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ==} engines: {node: '>=16.9.0'} - hookified@1.15.1: - resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} - hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -6663,9 +6585,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.6.0: - resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -8138,21 +8057,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socket.io-adapter@2.5.6: - resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} - - socket.io-client@4.8.3: - resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} - engines: {node: '>=10.0.0'} - - socket.io-parser@4.2.6: - resolution: {integrity: sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==} - engines: {node: '>=10.0.0'} - - socket.io@4.8.3: - resolution: {integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==} - engines: {node: '>=10.2.0'} - sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} @@ -9157,18 +9061,6 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.20.1: resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} engines: {node: '>=10.0.0'} @@ -9196,10 +9088,6 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - xmlhttprequest-ssl@2.1.2: - resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} - engines: {node: '>=0.4.0'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -11298,11 +11186,6 @@ snapshots: '@borewit/text-codec@0.2.2': {} - '@cacheable/utils@2.4.1': - dependencies: - hashery: 1.5.1 - keyv: 5.6.0 - '@colors/colors@1.5.0': optional: true @@ -12265,8 +12148,6 @@ snapshots: '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) tslib: 2.8.1 - '@keyv/serialize@1.1.1': {} - '@leichtgewicht/ip-codec@2.0.5': {} '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.8.0))(@types/node@25.8.0)(listr2@9.0.5)': @@ -12428,14 +12309,6 @@ snapshots: '@tybys/wasm-util': 0.10.2 optional: true - '@nestjs/cache-manager@3.1.2(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': - dependencies: - '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cache-manager: 7.2.8 - keyv: 5.6.0 - rxjs: 7.8.2 - '@nestjs/cli@11.0.21(@types/node@25.8.0)(prettier@3.8.3)': dependencies: '@angular-devkit/core': 19.2.24(chokidar@4.0.3) @@ -12507,7 +12380,7 @@ snapshots: uid: 2.0.2 optionalDependencies: '@nestjs/platform-express': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21) - '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: @@ -12521,18 +12394,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2)': - dependencies: - '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) - rxjs: 7.8.2 - socket.io: 4.8.3 - tslib: 2.8.1 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@nestjs/schedule@6.1.3(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)': dependencies: '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -12576,7 +12437,7 @@ snapshots: rxjs: 7.8.2 typeorm: 0.3.29(pg@8.20.0)(ts-node@10.9.2(@types/node@25.8.0)(typescript@5.9.3)) - '@nestjs/websockets@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(@nestjs/platform-socket.io@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: '@nestjs/common': 11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.21)(@nestjs/websockets@11.1.21)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -12585,8 +12446,6 @@ snapshots: reflect-metadata: 0.2.2 rxjs: 7.8.2 tslib: 2.8.1 - optionalDependencies: - '@nestjs/platform-socket.io': 11.1.21(@nestjs/common@11.1.21(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.21)(rxjs@7.8.2) '@ngtools/webpack@21.2.7(@angular/compiler-cli@21.2.9(@angular/compiler@21.2.9)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.2(esbuild@0.27.3)(lightningcss@1.32.0)(postcss@8.5.14))': dependencies: @@ -13162,8 +13021,6 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@socket.io/component-emitter@3.1.2': {} - '@sqltools/formatter@1.2.5': {} '@standard-schema/spec@1.1.0': {} @@ -13280,10 +13137,6 @@ snapshots: dependencies: '@types/node': 25.8.0 - '@types/cache-manager@5.0.0': - dependencies: - cache-manager: 7.2.8 - '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.1.1 @@ -13299,10 +13152,6 @@ snapshots: '@types/cookiejar@2.1.5': {} - '@types/cors@2.8.19': - dependencies: - '@types/node': 25.8.0 - '@types/cron@2.4.3': dependencies: cron: 4.4.0 @@ -14228,8 +14077,6 @@ snapshots: base64-js@1.5.1: {} - base64id@2.0.0: {} - baseline-browser-mapping@2.10.30: {} basic-auth@2.0.1: @@ -14384,11 +14231,6 @@ snapshots: p-map: 7.0.4 ssri: 13.0.1 - cache-manager@7.2.8: - dependencies: - '@cacheable/utils': 2.4.1 - keyv: 5.6.0 - call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -15107,37 +14949,6 @@ snapshots: dependencies: once: 1.4.0 - engine.io-client@6.6.4: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 - engine.io-parser: 5.2.3 - ws: 8.18.3 - xmlhttprequest-ssl: 2.1.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - engine.io-parser@5.2.3: {} - - engine.io@6.6.7: - dependencies: - '@types/cors': 2.8.19 - '@types/node': 25.8.0 - '@types/ws': 8.18.1 - accepts: 1.3.8 - base64id: 2.0.0 - cookie: 0.7.2 - cors: 2.8.6 - debug: 4.4.3 - engine.io-parser: 5.2.3 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - enhanced-resolve@5.21.3: dependencies: graceful-fs: 4.2.11 @@ -16002,18 +15813,12 @@ snapshots: dependencies: has-symbols: 1.1.0 - hashery@1.5.1: - dependencies: - hookified: 1.15.1 - hasown@2.0.3: dependencies: function-bind: 1.1.2 hono@4.12.19: {} - hookified@1.15.1: {} - hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -17028,10 +16833,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.6.0: - dependencies: - '@keyv/serialize': 1.1.1 - kind-of@6.0.3: {} launch-editor@2.13.2: @@ -18679,47 +18480,6 @@ snapshots: smart-buffer@4.2.0: {} - socket.io-adapter@2.5.6: - dependencies: - debug: 4.4.3 - ws: 8.18.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - socket.io-client@4.8.3: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 - engine.io-client: 6.6.4 - socket.io-parser: 4.2.6 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - socket.io-parser@4.2.6: - dependencies: - '@socket.io/component-emitter': 3.1.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - socket.io@4.8.3: - dependencies: - accepts: 1.3.8 - base64id: 2.0.0 - cors: 2.8.6 - debug: 4.4.3 - engine.io: 6.6.7 - socket.io-adapter: 2.5.6 - socket.io-parser: 4.2.6 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - sockjs@0.3.24: dependencies: faye-websocket: 0.11.4 @@ -19770,8 +19530,6 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.18.3: {} - ws@8.20.1: {} wsl-utils@0.1.0: @@ -19787,8 +19545,6 @@ snapshots: xmlchars@2.2.0: {} - xmlhttprequest-ssl@2.1.2: {} - xtend@4.0.2: {} y-protocols@1.0.7(yjs@13.6.30): diff --git a/teammapper-backend/.env.default b/teammapper-backend/.env.default index be58ba1a..6001bc1c 100644 --- a/teammapper-backend/.env.default +++ b/teammapper-backend/.env.default @@ -14,7 +14,6 @@ POSTGRES_TEST_DATABASE= DELETE_AFTER_DAYS= -YJS_ENABLED=true ## Logging (valid values: error, warn, log, debug, verbose) ## Defaults to 'debug' when MODE=DEV, 'log' otherwise diff --git a/teammapper-backend/README.md b/teammapper-backend/README.md index 05bc1d48..4ab2229b 100644 --- a/teammapper-backend/README.md +++ b/teammapper-backend/README.md @@ -84,7 +84,6 @@ Copy `.env.default` to `.env` and configure the variables below. | Variable | Description | Default | |---|---|---| -| `YJS_ENABLED` | Enable Yjs WebSocket server | `false` | | `FEATURE_YJS_RATE_LIMITING` | Enable WebSocket connection rate limiting | `false` | | `WS_TRUST_PROXY` | Trust `X-Forwarded-For` header for client IP resolution (enable when behind a reverse proxy) | `false` | | `WS_GLOBAL_MAX_CONNECTIONS` | Maximum total WebSocket connections | `500` | diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index ef6293b1..78962f97 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -39,20 +39,17 @@ "dependencies": { "@ai-sdk/openai": "3.0.53", "@ai-sdk/openai-compatible": "2.0.41", - "@nestjs/cache-manager": "^3.1.0", "@nestjs/cli": "^11.0.21", "@nestjs/common": "^11.1.19", "@nestjs/config": "4.0.4", "@nestjs/core": "^11.1.19", "@nestjs/platform-express": "^11.1.19", - "@nestjs/platform-socket.io": "^11.1.19", "@nestjs/schedule": "^6.1.3", "@nestjs/serve-static": "^5.0.5", "@nestjs/typeorm": "^11.0.1", "@nestjs/websockets": "^11.1.19", "@types/uuid": "^11.0.0", "ai": "6.0.168", - "cache-manager": "^7.2.8", "class-validator": "^0.15.1", "cookie-parser": "^1.4.7", "deepmerge": "^4.3.1", @@ -64,7 +61,6 @@ "rimraf": "^6.1.3", "rxjs": "^7.8.2", "sanitize-html": "^2.17.4", - "socket.io": "4.8.3", "typeorm": "^0.3.28", "uuid": "11.1.1", "valibot": "^1.3.1", @@ -79,7 +75,6 @@ "@nestjs/schematics": "^11.0.9", "@nestjs/testing": "^11.1.17", "@stylistic/eslint-plugin": "^5.10.0", - "@types/cache-manager": "5.0.0", "@types/cookie-parser": "^1.4.10", "@types/cron": "^2.4.3", "@types/express": "^5.0.6", @@ -101,7 +96,6 @@ "globals": "^17.4.0", "jest": "30.3.0", "prettier": "^3.8.1", - "socket.io-client": "^4.8.3", "supertest": "^7.2.2", "ts-jest": "29.4.6", "ts-loader": "^9.5.4", @@ -128,9 +122,6 @@ "" ], "coverageDirectory": "../coverage", - "testEnvironment": "node", - "setupFiles": [ - "./test/jest-setup.ts" - ] + "testEnvironment": "node" } } diff --git a/teammapper-backend/src/config.service.spec.ts b/teammapper-backend/src/config.service.spec.ts index 05d4cb4f..9c02c0e1 100644 --- a/teammapper-backend/src/config.service.spec.ts +++ b/teammapper-backend/src/config.service.spec.ts @@ -73,31 +73,4 @@ describe('ConfigService', () => { expect(config.getLogLevels()).toEqual(['error', 'warn', 'log', 'debug']) }) }) - - describe('isYjsEnabled', () => { - it('returns true when YJS_ENABLED is not set', () => { - const config = createConfigService({}) - expect(config.isYjsEnabled()).toBe(true) - }) - - it('returns true when YJS_ENABLED is "true"', () => { - const config = createConfigService({ YJS_ENABLED: 'true' }) - expect(config.isYjsEnabled()).toBe(true) - }) - - it('returns false when YJS_ENABLED is "false"', () => { - const config = createConfigService({ YJS_ENABLED: 'false' }) - expect(config.isYjsEnabled()).toBe(false) - }) - - it('returns false when YJS_ENABLED is "FALSE" (case-insensitive)', () => { - const config = createConfigService({ YJS_ENABLED: 'FALSE' }) - expect(config.isYjsEnabled()).toBe(false) - }) - - it('returns true for any value other than "false"', () => { - const config = createConfigService({ YJS_ENABLED: 'yes' }) - expect(config.isYjsEnabled()).toBe(true) - }) - }) }) diff --git a/teammapper-backend/src/config.service.ts b/teammapper-backend/src/config.service.ts index 498cde5a..c18e46a6 100644 --- a/teammapper-backend/src/config.service.ts +++ b/teammapper-backend/src/config.service.ts @@ -70,11 +70,6 @@ class ConfigService { return parseInt(this.getValue('DELETE_AFTER_DAYS', false) || '30') } - public isYjsEnabled(): boolean { - const value = this.getValue('YJS_ENABLED', false) - return value?.toLowerCase() !== 'false' - } - public isAiEnabled(): boolean { const value = this.getValue('AI_ENABLED', false) return value?.toLowerCase() === 'true' diff --git a/teammapper-backend/src/filters/global-exception.filter.ts b/teammapper-backend/src/filters/global-exception.filter.ts index 1095bcf8..e541bade 100644 --- a/teammapper-backend/src/filters/global-exception.filter.ts +++ b/teammapper-backend/src/filters/global-exception.filter.ts @@ -6,8 +6,9 @@ import { Logger, } from '@nestjs/common' -// This is for any unhandled gateway and "internal" NestJS related errors, like if the gateway can't reach clients or things like that. -// It will try to always keep clients and their websockets alive and gracefully send errors over the wire, without revealing internal error reasons. +// Catches unhandled errors from the HTTP pipeline. The Yjs websocket +// gateway is a raw ws.Server mounted outside Nest's filter pipeline, so it +// handles its own connection errors directly and never reaches this filter. @Catch() export class GlobalExceptionFilter implements ExceptionFilter { private readonly logger = new Logger(GlobalExceptionFilter.name) @@ -44,24 +45,6 @@ export class GlobalExceptionFilter implements ExceptionFilter { }) } - case 'ws': { - const client = host.switchToWs().getClient() - const error = { - event: 'error', - data: { - message: 'Internal server error', - timestamp: new Date().toISOString(), - }, - } - - if (typeof client.emit === 'function') { - client.emit('error', error) - } else if (typeof client.send === 'function') { - client.send(JSON.stringify(error)) - } - break - } - default: { // Handle any runtime errors outside HTTP/WS contexts this.logger.error(`Unhandled exception type: ${ctx}`) diff --git a/teammapper-backend/src/map/controllers/gateway-helpers.ts b/teammapper-backend/src/map/controllers/gateway-helpers.ts deleted file mode 100644 index dfd16ae0..00000000 --- a/teammapper-backend/src/map/controllers/gateway-helpers.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { Logger } from '@nestjs/common' -import { Server } from 'socket.io' -import { QueryFailedError } from 'typeorm' -import { MmpNode } from '../entities/mmpNode.entity' -import { MapsService } from '../services/maps.service' -import { - IMmpClientMap, - IMmpClientNode, - OperationResponse, - ValidationErrorResponse, -} from '../types' -import { mapMmpNodeToClient } from '../utils/clientServerMapping' - -export class GatewayHelpers { - constructor( - private readonly server: Server, - private readonly mapsService: MapsService, - private readonly logger: Logger - ) {} - - async safeExportMapToClient( - mapId: string - ): Promise { - try { - return await this.mapsService.exportMapToClient(mapId) - } catch (exportError) { - this.logger.error( - `Failed to export map state for error recovery: ${exportError instanceof Error ? exportError.message : String(exportError)}` - ) - return undefined - } - } - - async buildErrorResponse( - errorType: 'validation', - code: - | 'INVALID_PARENT' - | 'CONSTRAINT_VIOLATION' - | 'MISSING_REQUIRED_FIELD' - | 'CIRCULAR_REFERENCE' - | 'DUPLICATE_NODE', - message: string, - mapId: string - ): Promise> - - async buildErrorResponse( - errorType: 'critical', - code: - | 'SERVER_ERROR' - | 'NETWORK_TIMEOUT' - | 'AUTH_FAILED' - | 'MALFORMED_REQUEST' - | 'RATE_LIMIT_EXCEEDED', - message: string, - mapId: string - ): Promise> - - async buildErrorResponse( - errorType: 'validation' | 'critical', - code: string, - message: string, - mapId: string - ): Promise> { - const fullMapState = await this.safeExportMapToClient(mapId) - return { - success: false, - errorType, - code, - message, - fullMapState, - } as OperationResponse - } - - async handleDatabaseConstraintError( - error: QueryFailedError, - node: MmpNode, - mapId: string - ): Promise> { - const validationResponse = - await this.mapsService.mapConstraintErrorToValidationResponse( - error, - node, - mapId - ) - const fullMapState = await this.safeExportMapToClient(mapId) - return { - ...validationResponse, - fullMapState, - } as OperationResponse - } - - async handleUnexpectedOperationError( - error: unknown, - mapId: string, - operationContext: string - ): Promise> { - this.logger.error( - `${operationContext}: ${error instanceof Error ? error.message : String(error)}` - ) - return this.buildErrorResponse( - 'critical', - 'SERVER_ERROR', - 'CRITICAL_ERROR.SERVER_UNAVAILABLE', - mapId - ) - } - - buildSuccessResponse(data: T): OperationResponse { - return { success: true, data } - } - - isValidationError( - result: MmpNode | ValidationErrorResponse - ): result is ValidationErrorResponse { - return 'errorType' in result && result.errorType === 'validation' - } - - broadcastToRoom>( - mapId: string, - eventName: string, - payload: T - ): void { - this.server.to(mapId).emit(eventName, payload) - } - - async processAddNodeResults( - results: (MmpNode | ValidationErrorResponse)[] | null, - mapId: string - ): Promise< - | OperationResponse - | { validationError: ValidationErrorResponse } - | { successfulNodes: MmpNode[] } - > { - if (!results || results.length === 0) { - return this.buildErrorResponse( - 'validation', - 'CONSTRAINT_VIOLATION', - 'VALIDATION_ERROR.CONSTRAINT_VIOLATION', - mapId - ) - } - - if (results.length === 1 && this.isValidationError(results[0])) { - return { validationError: results[0] } - } - - return { successfulNodes: results as MmpNode[] } - } - - broadcastSuccessfulNodeAddition( - mapId: string, - clientId: string, - nodes: MmpNode[] - ): void { - const clientNodes = nodes.map((node) => mapMmpNodeToClient(node)) - this.broadcastToRoom(mapId, 'nodesAdded', { clientId, nodes: clientNodes }) - - if (nodes.length === 1 && nodes[0]?.id) { - this.broadcastToRoom(mapId, 'selectionUpdated', { - clientId, - nodeId: nodes[0].id, - selected: true, - }) - } - } - - async handleNodeUpdateResult( - result: MmpNode | ValidationErrorResponse | null, - mapId: string - ): Promise | { validNode: MmpNode }> { - if (!result) { - return this.buildErrorResponse( - 'validation', - 'INVALID_PARENT', - 'VALIDATION_ERROR.INVALID_PARENT', - mapId - ) - } - - if (this.isValidationError(result)) { - return { - ...result, - fullMapState: await this.safeExportMapToClient(mapId), - } - } - - return { validNode: result as MmpNode } - } -} diff --git a/teammapper-backend/src/map/controllers/maps.controller.spec.ts b/teammapper-backend/src/map/controllers/maps.controller.spec.ts index 79779df1..a6682172 100644 --- a/teammapper-backend/src/map/controllers/maps.controller.spec.ts +++ b/teammapper-backend/src/map/controllers/maps.controller.spec.ts @@ -1,7 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing' import MapsController from './maps.controller' import { MapsService } from '../services/maps.service' -import { NotFoundException } from '@nestjs/common' +import { YjsDocManagerService } from '../services/yjs-doc-manager.service' +import { YjsGateway } from './yjs-gateway.service' +import { INestApplication, NotFoundException } from '@nestjs/common' import { MmpMap } from '../entities/mmpMap.entity' import { IMmpClientMap, IMmpClientPrivateMap, Request } from '../types' import { MmpNode } from '../entities/mmpNode.entity' @@ -11,10 +13,12 @@ import { createMmpMap, } from '../utils/tests/mapFactories' import MalformedUUIDError from '../services/uuid.error' +import request from 'supertest' describe('MapsController', () => { let mapsController: MapsController let mapsService: MapsService + let yjsDocManager: YjsDocManagerService beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -33,11 +37,20 @@ describe('MapsController', () => { getMapsOfUser: jest.fn(), }, }, + { + provide: YjsDocManagerService, + useValue: { destroyDoc: jest.fn() }, + }, + { + provide: YjsGateway, + useValue: { closeConnectionsForMap: jest.fn() }, + }, ], }).compile() mapsController = module.get(MapsController) mapsService = module.get(MapsService) + yjsDocManager = module.get(YjsDocManagerService) }) describe('duplicate', () => { @@ -177,6 +190,36 @@ describe('MapsController', () => { expect(response).toEqual({ ...exportedMap, writable: false }) }) + + it('bumps lastAccessed for an authorized (writable) read', async () => { + const mapId = 'e7f66b65-ffd5-4387-b645-35f8e794c7e7' + const exportedMap: IMmpClientMap = createMmpClientMap({ id: mapId }) + const mmpMap = createMmpMap({ modificationSecret: 'my-secret' }) + + jest + .spyOn(mapsService, 'exportMapToClient') + .mockResolvedValueOnce(exportedMap) + jest.spyOn(mapsService, 'findMap').mockResolvedValueOnce(mmpMap) + + await mapsController.findOne(mapId, 'my-secret') + + expect(mapsService.updateLastAccessed).toHaveBeenCalledWith(mapId) + }) + + it('does not bump lastAccessed for an anonymous read of a protected map', async () => { + const mapId = 'e7f66b65-ffd5-4387-b645-35f8e794c7e7' + const exportedMap: IMmpClientMap = createMmpClientMap({ id: mapId }) + const mmpMap = createMmpMap({ modificationSecret: 'my-secret' }) + + jest + .spyOn(mapsService, 'exportMapToClient') + .mockResolvedValueOnce(exportedMap) + jest.spyOn(mapsService, 'findMap').mockResolvedValueOnce(mmpMap) + + await mapsController.findOne(mapId) + + expect(mapsService.updateLastAccessed).not.toHaveBeenCalled() + }) }) describe('findAll', () => { @@ -208,7 +251,6 @@ describe('MapsController', () => { await mapsController.delete(existingMap.id, { adminId: existingMap.adminId, - mapId: existingMap.id, }) expect(mapsService.deleteMap).toHaveBeenCalledWith(existingMap.id) @@ -221,11 +263,29 @@ describe('MapsController', () => { await mapsController.delete(existingMap.id, { adminId: 'wrong-admin-id', - mapId: existingMap.id, }) expect(mapsService.deleteMap).not.toHaveBeenCalledWith(existingMap.id) }) + + it('deletes the DB row before destroying the in-memory Y.Doc', async () => { + const existingMap = createMmpMap() + const callOrder: string[] = [] + + jest.spyOn(mapsService, 'findMap').mockResolvedValueOnce(existingMap) + jest.spyOn(mapsService, 'deleteMap').mockImplementation(async () => { + callOrder.push('deleteMap') + }) + jest.spyOn(yjsDocManager, 'destroyDoc').mockImplementation(() => { + callOrder.push('destroyDoc') + }) + + await mapsController.delete(existingMap.id, { + adminId: existingMap.adminId, + }) + + expect(callOrder).toEqual(['deleteMap', 'destroyDoc']) + }) }) describe('create', () => { @@ -288,3 +348,80 @@ describe('MapsController', () => { }) }) }) + +// HTTP-layer regression tests: exercise the real request pipeline so that +// JSON body → schema validation → controller wiring is verified against the +// exact wire format the frontend produces. The unit tests above call the +// controller method directly and so cannot catch wire-format drift. +describe('MapsController (HTTP wire contract)', () => { + let app: INestApplication + let mapsService: MapsService + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MapsController], + providers: [ + { + provide: MapsService, + useValue: { + findMap: jest.fn(), + deleteMap: jest.fn(), + }, + }, + { + provide: YjsDocManagerService, + useValue: { destroyDoc: jest.fn() }, + }, + { + provide: YjsGateway, + useValue: { closeConnectionsForMap: jest.fn() }, + }, + ], + }).compile() + + app = module.createNestApplication() + await app.init() + mapsService = module.get(MapsService) + }) + + afterAll(async () => { + await app.close() + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + describe('DELETE /api/maps/:id', () => { + it('accepts the body the frontend actually sends ({ adminId } only)', async () => { + const existingMap = createMmpMap() + jest.spyOn(mapsService, 'findMap').mockResolvedValueOnce(existingMap) + jest.spyOn(mapsService, 'deleteMap').mockResolvedValueOnce(undefined) + + await request(app.getHttpServer()) + .delete(`/api/maps/${existingMap.id}`) + .send({ adminId: existingMap.adminId }) + .expect(200) + + expect(mapsService.deleteMap).toHaveBeenCalledWith(existingMap.id) + }) + + it('returns 400 when the body omits adminId', async () => { + await request(app.getHttpServer()) + .delete('/api/maps/any-id') + .send({}) + .expect(400) + + expect(mapsService.deleteMap).not.toHaveBeenCalled() + }) + + it('rejects adminId=null so legacy NULL-admin rows are not deletable', async () => { + await request(app.getHttpServer()) + .delete('/api/maps/any-id') + .send({ adminId: null }) + .expect(400) + + expect(mapsService.deleteMap).not.toHaveBeenCalled() + }) + }) +}) diff --git a/teammapper-backend/src/map/controllers/maps.controller.ts b/teammapper-backend/src/map/controllers/maps.controller.ts index 4e746005..68feaf1d 100644 --- a/teammapper-backend/src/map/controllers/maps.controller.ts +++ b/teammapper-backend/src/map/controllers/maps.controller.ts @@ -10,8 +10,6 @@ import { Post, Query, Logger, - Optional, - Inject, } from '@nestjs/common' import * as v from 'valibot' import { MapsService } from '../services/maps.service' @@ -34,12 +32,8 @@ export default class MapsController { private readonly logger = new Logger(MapsController.name) constructor( private mapsService: MapsService, - @Optional() - @Inject(YjsDocManagerService) - private yjsDocManager?: YjsDocManagerService, - @Optional() - @Inject(YjsGateway) - private yjsGateway?: YjsGateway + private yjsDocManager: YjsDocManagerService, + private yjsGateway: YjsGateway ) {} @Get(':id') @@ -48,7 +42,6 @@ export default class MapsController { @Query('secret') secret?: string ): Promise { try { - await this.mapsService.updateLastAccessed(mapId) const map = await this.mapsService.exportMapToClient(mapId) if (!map) throw new NotFoundException() @@ -58,6 +51,12 @@ export default class MapsController { secret ?? null ) + // Only authorized readers should reset the retention clock; otherwise + // anonymous GETs (crawlers, attackers) could keep a map alive forever. + if (writable) { + await this.mapsService.updateLastAccessed(mapId) + } + return { ...map, writable } } catch (e) { if (e instanceof MalformedUUIDError || e instanceof EntityNotFoundError) { @@ -88,11 +87,12 @@ export default class MapsController { } const mmpMap = await this.mapsService.findMap(mapId) if (mmpMap && mmpMap.adminId === result.output.adminId) { - if (this.yjsGateway && this.yjsDocManager) { - this.yjsGateway.closeConnectionsForMap(mapId) - this.yjsDocManager.destroyDoc(mapId) - } + this.yjsGateway.closeConnectionsForMap(mapId) + // Delete DB row before destroying the in-memory Y.Doc so that any + // concurrent reconnection's hydrateDocFromDb sees a missing row and + // refuses to create a phantom doc. await this.mapsService.deleteMap(mapId) + this.yjsDocManager.destroyDoc(mapId) } } diff --git a/teammapper-backend/src/map/controllers/maps.gateway.spec.ts b/teammapper-backend/src/map/controllers/maps.gateway.spec.ts deleted file mode 100644 index 09c51998..00000000 --- a/teammapper-backend/src/map/controllers/maps.gateway.spec.ts +++ /dev/null @@ -1,677 +0,0 @@ -import { INestApplication } from '@nestjs/common' -import { CACHE_MANAGER } from '@nestjs/cache-manager' -import { Test } from '@nestjs/testing' -import { Repository } from 'typeorm' -import { getRepositoryToken } from '@nestjs/typeorm' -import { io, Socket } from 'socket.io-client' -import { MmpMap } from '../entities/mmpMap.entity' -import { MapsService } from '../services/maps.service' -import { MapsGateway } from './maps.gateway' -import { MmpNode } from '../entities/mmpNode.entity' -import { createMock } from '@golevelup/ts-jest' -import { IMmpClientNode, OperationResponse } from '../types' - -const crypto = require('crypto') // eslint-disable-line @typescript-eslint/no-require-imports - -describe('WebSocketGateway', () => { - let app: INestApplication - let mapsService: MapsService - let socket: Socket - - const map: MmpMap = new MmpMap() - map.id = '123' - map.modificationSecret = 'abc' - - beforeAll(async () => { - mapsService = createMock({ - findMap: (_uuid: string) => - new Promise((resolve, _reject) => { - resolve(map) - }), - removeNode: (_clientNode: IMmpClientNode, _mapId: string) => - new Promise((resolve, _reject) => { - const node = new MmpNode() - node.createdAt = new Date('2021-01-31T00:00:00.000Z') - node.lastModified = new Date('2021-01-31T00:00:00.000Z') - resolve(node) - }), - }) - const testingModule = await Test.createTestingModule({ - providers: [ - MapsGateway, - { provide: MapsService, useValue: mapsService }, - { - provide: getRepositoryToken(MmpMap), - useValue: createMock>(), - }, - { - provide: getRepositoryToken(MmpNode), - useValue: createMock>(), - }, - { - provide: CACHE_MANAGER, - useValue: createMock(), - }, - ], - }).compile() - app = testingModule.createNestApplication() - await app.init() - await app.listen(3000) - }) - - describe('checkModificationSecret', () => { - it(`returns false if wrong`, (done) => { - socket = io('http://localhost:3000') - - socket.emit( - 'checkModificationSecret', - { - mapId: map.id, - modificationSecret: 'wrong', - }, - (result: boolean) => { - expect(result).toEqual(false) - done() - } - ) - }) - - it(`returns true if correct`, (done) => { - socket = io('http://localhost:3000') - - socket.emit( - 'checkModificationSecret', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - }, - (result: boolean) => { - expect(result).toEqual(true) - done() - } - ) - }) - }) - - describe('addNode', () => { - it(`will return error response if no new nodes are being added`, (done) => { - socket = io('http://localhost:3000') - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [{}], - }, - (result: OperationResponse) => { - expect(result.success).toEqual(false) - if (!result.success) { - expect(result.errorType).toEqual('validation') - } - done() - } - ) - }) - }) - - describe('updateNode', () => { - it(`allows request when modification secret is set`, (done) => { - socket = io('http://localhost:3000') - - const mockNode = new MmpNode() - mockNode.id = crypto.randomUUID() - mockNode.name = 'Test' - - jest.spyOn(mapsService, 'updateNode').mockResolvedValueOnce(mockNode) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { id: mockNode.id, name: 'Test' }, - updatedProperty: 'name', - }, - (result: OperationResponse) => { - expect(result.success).toEqual(true) - done() - } - ) - }) - }) - - describe('removeNode', () => { - it(`allows request when modification secret is set`, (done) => { - socket = io('http://localhost:3000') - - // Date objects are serialised to JSON in the result, so we'll need to be explicit in setting these here - const defaultNode = { - id: crypto.randomUUID(), - createdAt: new Date('2021-01-31T00:00:00.000Z').toISOString(), - lastModified: new Date('2021-01-31T00:00:00.000Z').toISOString(), - } - - socket.emit( - 'removeNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: defaultNode, - }, - (result: OperationResponse) => { - expect(result.success).toEqual(true) - done() - } - ) - }) - }) - - describe('updateMap', () => { - it(`allows request when modification secret is set`, (done) => { - socket = io('http://localhost:3000') - - socket.emit( - 'updateMap', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - map: {}, - }, - (result: boolean) => { - expect(result).toEqual(true) - done() - } - ) - }) - }) - - describe('applyMapChangesByDiff', () => { - it('updates the map based off of a diff', (done) => { - socket = io('http://localhost:3000') - const rootNodeId = crypto.randomUUID() - - const diff = { - added: {}, - deleted: {}, - updated: { - [rootNodeId]: { - name: 'Thema', - }, - }, - } - - socket.emit( - 'applyMapChangesByDiff', - { - mapId: map.id, - diff, - modificationSecret: map.modificationSecret, - }, - (result: OperationResponse) => { - expect(result.success).toEqual(true) - if (result.success) { - expect(result.data).toEqual(diff) - } - done() - } - ) - }) - }) - - describe('updateMapOptions', () => { - it(`allows request when modification secret is set`, (done) => { - socket = io('http://localhost:3000') - - socket.emit( - 'updateMapOptions', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - options: {}, - }, - (result: MmpNode | undefined) => { - expect(result).toEqual(true) - done() - } - ) - }) - }) - - describe('error handling in gateway methods', () => { - it('addNodes returns CriticalErrorResponse when service throws error', (done) => { - socket = io('http://localhost:3000') - - // Mock service to throw error - jest - .spyOn(mapsService, 'addNodesFromClient') - .mockRejectedValueOnce(new Error('Database error')) - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - name: 'test', - coordinates: { x: 1, y: 2 }, - isRoot: true, - }, - ], - }, - (result: OperationResponse) => { - expect(result.success).toEqual(false) - if (!result.success) { - expect(result.errorType).toEqual('critical') - } - done() - } - ) - }) - - it('updateNode returns CriticalErrorResponse when service throws error', (done) => { - socket = io('http://localhost:3000') - - // Mock service to throw error - jest - .spyOn(mapsService, 'updateNode') - .mockRejectedValueOnce(new Error('Database error')) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { - id: crypto.randomUUID(), - name: 'test', - coordinates: { x: 1, y: 2 }, - }, - updatedProperty: 'name', - }, - (result: OperationResponse) => { - expect(result.success).toEqual(false) - if (!result.success) { - expect(result.errorType).toEqual('critical') - } - done() - } - ) - }) - - it('applyMapChangesByDiff returns CriticalErrorResponse when service throws error', (done) => { - socket = io('http://localhost:3000') - - // Mock service to throw error - jest - .spyOn(mapsService, 'updateMapByDiff') - .mockRejectedValueOnce(new Error('Database error')) - - socket.emit( - 'applyMapChangesByDiff', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - diff: { - added: {}, - updated: {}, - deleted: {}, - }, - }, - (result: OperationResponse) => { - expect(result.success).toEqual(false) - if (!result.success) { - expect(result.errorType).toEqual('critical') - expect(result.code).toEqual('SERVER_ERROR') - } - done() - } - ) - }) - - it('updateMap returns false when service throws error', (done) => { - socket = io('http://localhost:3000') - - // Mock service to throw error - jest - .spyOn(mapsService, 'updateMap') - .mockRejectedValueOnce(new Error('Database error')) - - socket.emit( - 'updateMap', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - map: { - uuid: map.id, - data: [], - }, - }, - (result: boolean) => { - expect(result).toEqual(false) - done() - } - ) - }) - }) - - describe('addNodes - additional OperationResponse handling', () => { - it('returns SuccessResponse on successful node addition', (done) => { - socket = io('http://localhost:3000') - - const mockNode = new MmpNode() - mockNode.id = crypto.randomUUID() - mockNode.nodeMapId = map.id - mockNode.name = 'Test Node' - - jest - .spyOn(mapsService, 'addNodesFromClient') - .mockResolvedValueOnce([mockNode]) - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: mockNode.id, - name: 'Test Node', - coordinates: { x: 1, y: 2 }, - isRoot: true, - }, - ], - }, - (result: OperationResponse) => { - expect(result.success).toBe(true) - if (result.success) { - expect(result.data).toBeDefined() - expect(Array.isArray(result.data)).toBe(true) - expect(result.data.length).toBe(1) - } - done() - } - ) - }) - - it('returns ValidationErrorResponse when validation fails', (done) => { - socket = io('http://localhost:3000') - - jest.spyOn(mapsService, 'addNodesFromClient').mockResolvedValueOnce([]) - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: crypto.randomUUID(), - name: 'Invalid Node', - coordinates: { x: 1, y: 2 }, - }, - ], - }, - (result: OperationResponse) => { - expect(result.success).toBe(false) - if (!result.success) { - expect(result.errorType).toBe('validation') - expect(result.code).toBe('CONSTRAINT_VIOLATION') - } - done() - } - ) - }) - - it('does not broadcast when validation error occurs', (done) => { - socket = io('http://localhost:3000') - - jest.spyOn(mapsService, 'addNodesFromClient').mockResolvedValueOnce([]) - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: crypto.randomUUID(), - name: 'Invalid Node', - coordinates: { x: 1, y: 2 }, - }, - ], - }, - (result: OperationResponse) => { - expect(result.success).toBe(false) - // Validation errors should not result in broadcast - if (!result.success) { - expect(result.errorType).toBe('validation') - } - done() - } - ) - }) - - it('atomic operation: if one node fails, no nodes are created', (done) => { - socket = io('http://localhost:3000') - - // Mock service to return a validation error (atomic failure) - const validationError = { - success: false as const, - errorType: 'validation' as const, - code: 'INVALID_PARENT' as const, - message: 'VALIDATION_ERROR.INVALID_PARENT', - } - - jest - .spyOn(mapsService, 'addNodesFromClient') - .mockResolvedValueOnce([validationError]) - - // Mock exportMapToClient to return a valid map for fullMapState - jest.spyOn(mapsService, 'exportMapToClient').mockResolvedValueOnce({ - uuid: map.id, - data: [], - options: { - fontMaxSize: 24, - fontMinSize: 10, - fontIncrement: 2, - }, - deletedAt: new Date(), - deleteAfterDays: 30, - lastModified: new Date(), - lastAccessed: new Date(), - createdAt: new Date(), - }) - - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: crypto.randomUUID(), - name: 'Valid Node 1', - coordinates: { x: 1, y: 2 }, - isRoot: true, - }, - { - id: crypto.randomUUID(), - name: 'Invalid Node - Bad Parent', - coordinates: { x: 2, y: 3 }, - parent: 'nonexistent-parent', - }, - { - id: crypto.randomUUID(), - name: 'Valid Node 2', - coordinates: { x: 3, y: 4 }, - isRoot: false, - }, - ], - }, - (result: OperationResponse) => { - // The entire operation should fail - expect(result.success).toBe(false) - if (!result.success) { - expect(result.errorType).toBe('validation') - expect(result.code).toBe('INVALID_PARENT') - // Should include full map state for recovery - expect(result.fullMapState).toBeDefined() - } - done() - } - ) - }) - }) - - describe('updateNode - additional OperationResponse handling', () => { - it('returns SuccessResponse on successful node update', (done) => { - socket = io('http://localhost:3000') - - const mockNode = new MmpNode() - mockNode.id = crypto.randomUUID() - mockNode.nodeMapId = map.id - mockNode.name = 'Updated Node' - - jest.spyOn(mapsService, 'updateNode').mockResolvedValueOnce(mockNode) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { - id: mockNode.id, - name: 'Updated Node', - coordinates: { x: 1, y: 2 }, - }, - updatedProperty: 'name', - }, - (result: OperationResponse) => { - expect(result.success).toBe(true) - if (result.success) { - expect(result.data).toBeDefined() - expect(result.data.id).toBe(mockNode.id) - } - done() - } - ) - }) - - it('returns ValidationErrorResponse when service returns validation error', (done) => { - socket = io('http://localhost:3000') - - const validationError = { - success: false as const, - errorType: 'validation' as const, - code: 'INVALID_PARENT' as const, - message: 'VALIDATION_ERROR.INVALID_PARENT', - } - - jest - .spyOn(mapsService, 'updateNode') - .mockResolvedValueOnce(validationError) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { - id: crypto.randomUUID(), - name: 'Test', - coordinates: { x: 1, y: 2 }, - parent: 'invalid-parent', - }, - updatedProperty: 'parent', - }, - (result: OperationResponse) => { - expect(result.success).toBe(false) - if (!result.success) { - expect(result.errorType).toBe('validation') - expect(result.code).toBe('INVALID_PARENT') - } - done() - } - ) - }) - - it('does not broadcast when validation error occurs', (done) => { - socket = io('http://localhost:3000') - - const nodeId = crypto.randomUUID() - const validationError = { - success: false as const, - errorType: 'validation' as const, - code: 'INVALID_PARENT' as const, - message: 'VALIDATION_ERROR.INVALID_PARENT', - } - - jest - .spyOn(mapsService, 'updateNode') - .mockResolvedValueOnce(validationError) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { - id: nodeId, - name: 'Test', - coordinates: { x: 1, y: 2 }, - }, - updatedProperty: 'name', - }, - (result: OperationResponse) => { - expect(result.success).toBe(false) - // Validation errors should not result in broadcast - if (!result.success) { - expect(result.errorType).toBe('validation') - } - done() - } - ) - }) - - it('returns CriticalErrorResponse when service throws error', (done) => { - socket = io('http://localhost:3000') - - jest - .spyOn(mapsService, 'updateNode') - .mockRejectedValueOnce(new Error('Server error')) - - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - node: { - id: crypto.randomUUID(), - name: 'Test', - coordinates: { x: 1, y: 2 }, - }, - updatedProperty: 'name', - }, - (result: OperationResponse) => { - expect(result.success).toBe(false) - if (!result.success) { - expect(result.errorType).toBe('critical') - expect(result.code).toBe('SERVER_ERROR') - } - done() - } - ) - }) - }) - - afterEach(async () => { - socket.close() - jest.restoreAllMocks() - }) - - afterAll(async () => { - await app.close() - }) -}) diff --git a/teammapper-backend/src/map/controllers/maps.gateway.ts b/teammapper-backend/src/map/controllers/maps.gateway.ts deleted file mode 100644 index c5518ec4..00000000 --- a/teammapper-backend/src/map/controllers/maps.gateway.ts +++ /dev/null @@ -1,551 +0,0 @@ -import { CACHE_MANAGER } from '@nestjs/cache-manager' -import { Inject, Logger, UseGuards } from '@nestjs/common' -import { - ConnectedSocket, - MessageBody, - OnGatewayDisconnect, - SubscribeMessage, - WebSocketGateway, - WebSocketServer, -} from '@nestjs/websockets' -import { Cache } from 'cache-manager' -import { randomBytes } from 'crypto' -import { Server, Socket } from 'socket.io' -import { QueryFailedError } from 'typeorm' -import { MmpNode } from '../entities/mmpNode.entity' -import { EditGuard } from '../guards/edit.guard' -import { MapsService } from '../services/maps.service' -import { - IClientCache, - IMmpClientMap, - IMmpClientMapDiff, - IMmpClientNode, - OperationResponse, -} from '../types' -import { - JoinSchema, - CheckModificationSecretSchema, - NodeSelectionSchema, - UpdateMapOptionsSchema, - DeleteRequestSchema, - NodeAddRequestSchema, - NodeRequestSchema, - NodeRemoveRequestSchema, - UndoRedoRequestSchema, - MapRequestSchema, - validateWsPayload, -} from '../schemas/gateway.schema' -import { - mapClientNodeToMmpNode, - mapMmpNodeToClient, -} from '../utils/clientServerMapping' -import { GatewayHelpers } from './gateway-helpers' - -// For possible configuration options please see: -// https://socket.io/docs/v4/server-initialization/ -@WebSocketGateway({ cors: { credentials: true }, maxHttpBufferSize: 2e6 }) -export class MapsGateway implements OnGatewayDisconnect { - @WebSocketServer() - server: Server - - private readonly logger = new Logger(MapsService.name) - // 24 hours – entries are cleaned up explicitly on disconnect - private readonly CACHE_TTL_MS = 86_400_000 - private helpers: GatewayHelpers - - constructor( - private mapsService: MapsService, - @Inject(CACHE_MANAGER) private cacheManager: Cache - ) {} - - private getHelpers(): GatewayHelpers { - if (!this.helpers) { - this.helpers = new GatewayHelpers( - this.server, - this.mapsService, - this.logger - ) - } - return this.helpers - } - - @SubscribeMessage('leave') - async handleDisconnect(client: Socket) { - const mapId: string | undefined | null = await this.cacheManager.get( - client.id - ) - if (!mapId) return - - this.server.to(mapId).emit('clientDisconnect', client.id) - this.removeClientForMap(mapId, client.id) - this.cacheManager.del(client.id) - client.leave(mapId) - } - - @SubscribeMessage('join') - async onJoin( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload(client, JoinSchema, request) - if (!validated) return undefined - try { - const map = await this.mapsService.findMap(validated.mapId) - if (!map) { - this.logger.warn( - `onJoin(): Could not find map ${validated.mapId} when client ${client.id} tried to join` - ) - return - } - - const updatedClientCache = await this.setupClientRoomMembership( - client, - validated.mapId, - validated.color - ) - - this.server - .to(validated.mapId) - .emit('clientListUpdated', updatedClientCache) - - return await this.mapsService.exportMapToClient(validated.mapId) - } catch (error) { - this.logger.error( - `Failed to join map: ${error instanceof Error ? error.message : String(error)}` - ) - return undefined - } - } - - @SubscribeMessage('checkModificationSecret') - async checkmodificationSecret( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload( - client, - CheckModificationSecretSchema, - request - ) - if (!validated) return false - try { - const map = await this.mapsService.findMap(validated.mapId) - if (!map || !map.modificationSecret) return true - - return validated.modificationSecret === map?.modificationSecret - } catch (error) { - this.logger.error( - `Failed to check modification secret: ${error instanceof Error ? error.message : String(error)}` - ) - return false - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('updateMapOptions') - async onUpdateMap( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload(client, UpdateMapOptionsSchema, request) - if (!validated) return false - const updatedMap = await this.mapsService.updateMapOptions( - validated.mapId, - validated.options - ) - this.server.to(validated.mapId).emit('mapOptionsUpdated', updatedMap) - - return true - } - - @SubscribeMessage('deleteMap') - async onDeleteMap( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload(client, DeleteRequestSchema, request) - if (!validated) return false - try { - const mmpMap = await this.mapsService.findMap(validated.mapId) - if (mmpMap && mmpMap.adminId === validated.adminId) { - await this.mapsService.deleteMap(validated.mapId) - this.server.to(validated.mapId).emit('mapDeleted') - return true - } - return false - } catch (error) { - this.logger.error( - `Failed to delete map: ${error instanceof Error ? error.message : String(error)}` - ) - return false - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('addNodes') - async addNode( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise> { - const validated = validateWsPayload(client, NodeAddRequestSchema, request) - if (!validated) { - return { - success: false, - errorType: 'critical', - code: 'MALFORMED_REQUEST', - message: 'CRITICAL_ERROR.MALFORMED_REQUEST', - } - } - const h = this.getHelpers() - try { - const results = await this.mapsService.addNodesFromClient( - validated.mapId, - validated.nodes as IMmpClientNode[] - ) - - const processedResults = await h.processAddNodeResults( - results, - validated.mapId - ) - - if ('success' in processedResults) { - return processedResults - } - - if ('validationError' in processedResults) { - const fullMapState = await h.safeExportMapToClient(validated.mapId) - return { - ...processedResults.validationError, - fullMapState, - } as OperationResponse - } - - const { successfulNodes } = processedResults - h.broadcastSuccessfulNodeAddition( - validated.mapId, - client.id, - successfulNodes - ) - - const clientNodes = successfulNodes.map((node) => - mapMmpNodeToClient(node) - ) - return h.buildSuccessResponse(clientNodes) - } catch (error) { - if (error instanceof QueryFailedError) { - const mmpNode = mapClientNodeToMmpNode( - validated.nodes[0] as IMmpClientNode, - validated.mapId - ) - return h.handleDatabaseConstraintError( - error, - mmpNode as MmpNode, - validated.mapId - ) - } - - return h.handleUnexpectedOperationError( - error, - validated.mapId, - 'Failed to add nodes' - ) - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('updateNode') - async updateNode( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise> { - const validated = validateWsPayload(client, NodeRequestSchema, request) - if (!validated) { - return { - success: false, - errorType: 'critical', - code: 'MALFORMED_REQUEST', - message: 'CRITICAL_ERROR.MALFORMED_REQUEST', - } - } - const h = this.getHelpers() - try { - const updatedNode = await this.mapsService.updateNode( - validated.mapId, - validated.node as IMmpClientNode - ) - - const processedResult = await h.handleNodeUpdateResult( - updatedNode ?? null, - validated.mapId - ) - - if ('success' in processedResult) { - return processedResult - } - - const { validNode } = processedResult - const clientNode = mapMmpNodeToClient(validNode) - - h.broadcastToRoom(validated.mapId, 'nodeUpdated', { - clientId: client.id, - property: validated.updatedProperty, - node: clientNode, - }) - - return h.buildSuccessResponse(clientNode) - } catch (error) { - if (error instanceof QueryFailedError) { - const mmpNode = mapClientNodeToMmpNode( - validated.node as IMmpClientNode, - validated.mapId - ) - return h.handleDatabaseConstraintError( - error, - mmpNode as MmpNode, - validated.mapId - ) - } - - return h.handleUnexpectedOperationError( - error, - validated.mapId, - 'Failed to update node' - ) - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('applyMapChangesByDiff') - async applyMapChangesByDiff( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise> { - const validated = validateWsPayload(client, UndoRedoRequestSchema, request) - if (!validated) { - return { - success: false, - errorType: 'critical', - code: 'MALFORMED_REQUEST', - message: 'CRITICAL_ERROR.MALFORMED_REQUEST', - } - } - const h = this.getHelpers() - try { - if (!(await this.mapsService.findMap(validated.mapId))) { - return h.buildErrorResponse( - 'critical', - 'MALFORMED_REQUEST', - 'CRITICAL_ERROR.MAP_NOT_FOUND', - validated.mapId - ) - } - - await this.mapsService.updateMapByDiff(validated.mapId, validated.diff) - - h.broadcastToRoom(validated.mapId, 'mapChangesUndoRedo', { - clientId: client.id, - diff: validated.diff, - }) - - return h.buildSuccessResponse(validated.diff) - } catch (error) { - return h.handleUnexpectedOperationError( - error, - validated.mapId, - 'Failed to apply map changes by diff' - ) - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('updateMap') - async updateMap( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload(client, MapRequestSchema, request) - if (!validated) return false - const h = this.getHelpers() - try { - if (!(await this.mapsService.findMap(validated.mapId))) return false - - const mmpMap = { - ...validated.map, - uuid: validated.mapId, - } as unknown as IMmpClientMap - - h.broadcastToRoom(mmpMap.uuid, 'clientNotification', { - clientId: client.id, - message: 'TOASTS.WARNINGS.MAP_IMPORT_IN_PROGRESS', - type: 'warning', - }) - - const sockets = await this.disconnectAllClientsFromMap(validated.mapId) - - await this.mapsService.updateMap(mmpMap) - - this.reconnectClientsToMap(sockets, validated.mapId) - - const exportMap = await this.mapsService.exportMapToClient(mmpMap.uuid) - - if (exportMap) { - h.broadcastToRoom(mmpMap.uuid, 'mapUpdated', { - clientId: client.id, - map: exportMap, - }) - } - - h.broadcastToRoom(mmpMap.uuid, 'clientNotification', { - clientId: client.id, - message: 'TOASTS.MAP_IMPORT_SUCCESS', - type: 'success', - }) - - return true - } catch (error) { - this.logger.error( - `Failed to update map: ${error instanceof Error ? error.message : String(error)}` - ) - return false - } - } - - @UseGuards(EditGuard) - @SubscribeMessage('removeNode') - async removeNode( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise> { - const validated = validateWsPayload( - client, - NodeRemoveRequestSchema, - request - ) - if (!validated) { - return { - success: false, - errorType: 'critical', - code: 'MALFORMED_REQUEST', - message: 'CRITICAL_ERROR.MALFORMED_REQUEST', - } - } - const h = this.getHelpers() - try { - const removedNode = await this.mapsService.removeNode( - validated.node as IMmpClientNode, - validated.mapId - ) - - if (!removedNode) { - return h.buildErrorResponse( - 'critical', - 'MALFORMED_REQUEST', - 'CRITICAL_ERROR.NODE_NOT_FOUND', - validated.mapId - ) - } - - h.broadcastToRoom(validated.mapId, 'nodeRemoved', { - clientId: client.id, - nodeId: validated.node.id, - }) - - return h.buildSuccessResponse(mapMmpNodeToClient(removedNode)) - } catch (error) { - return h.handleUnexpectedOperationError( - error, - validated.mapId, - 'Failed to remove node' - ) - } - } - - @SubscribeMessage('updateNodeSelection') - async updateNodeSelection( - @ConnectedSocket() client: Socket, - @MessageBody() request: unknown - ): Promise { - const validated = validateWsPayload(client, NodeSelectionSchema, request) - if (!validated) return false - this.server.to(validated.mapId).emit('selectionUpdated', { - clientId: client.id, - nodeId: validated.nodeId, - selected: validated.selected, - }) - - return true - } - - // ============================================================ - // Client Cache Helpers - // ============================================================ - - private async updateClientCache( - mapId: string, - updateFn: (cache: IClientCache) => IClientCache - ): Promise { - const currentCache: IClientCache = - (await this.cacheManager.get(mapId)) || {} - const updatedCache = updateFn(currentCache) - await this.cacheManager.set(mapId, updatedCache, this.CACHE_TTL_MS) - return updatedCache - } - - private async addClientForMap( - mapId: string, - clientId: string, - color: string - ): Promise { - return this.updateClientCache(mapId, (cache) => ({ - ...cache, - [clientId]: this.chooseColor(cache, color), - })) - } - - private async removeClientForMap( - mapId: string, - clientId: string - ): Promise { - return this.updateClientCache(mapId, (cache) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { [clientId]: _, ...rest } = cache - return rest - }) - } - - private chooseColor(currentClientCache: IClientCache, color: string): string { - const usedColors: string[] = Object.values(currentClientCache) - if (usedColors.includes(color)) return `#${randomBytes(3).toString('hex')}` - - return color - } - - // ============================================================ - // Room Management Helpers - // ============================================================ - - private async disconnectAllClientsFromMap(mapId: string) { - const sockets = await this.server.in(mapId).fetchSockets() - this.server.in(mapId).socketsLeave(mapId) - return sockets - } - - private reconnectClientsToMap( - sockets: Awaited>, - mapId: string - ): void { - sockets.forEach((socket) => { - socket.join(mapId) - }) - } - - private async setupClientRoomMembership( - client: Socket, - mapId: string, - color: string - ): Promise { - client.join(mapId) - await this.cacheManager.set(client.id, mapId, this.CACHE_TTL_MS) - return await this.addClientForMap(mapId, client.id, color) - } -} diff --git a/teammapper-backend/src/map/guards/edit.guard.spec.ts b/teammapper-backend/src/map/guards/edit.guard.spec.ts deleted file mode 100644 index 7dc5a3d1..00000000 --- a/teammapper-backend/src/map/guards/edit.guard.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Test } from '@nestjs/testing' -import { EditGuard } from './edit.guard' -import { MapsService } from '../services/maps.service' -import { MmpMap } from '../entities/mmpMap.entity' -import { createMock } from '@golevelup/ts-jest' -import { ExecutionContext } from '@nestjs/common' - -describe('EditGuard', () => { - let guard: EditGuard - let mapsService: MapsService - - beforeEach(() => { - guard = new EditGuard(mapsService) - }) - - describe('canActivate', () => { - describe('with modificationSecret', () => { - const map: MmpMap = new MmpMap() - map.id = '123' - map.modificationSecret = 'abc' - - beforeAll(async () => { - mapsService = createMock({ - findMap: (_uuid: string) => - new Promise((resolve, _reject) => { - resolve(map) - }), - }) - await Test.createTestingModule({ - providers: [{ provide: MapsService, useValue: mapsService }], - }).compile() - }) - - it('should return true when user provides correct credentials', async () => { - const mockContext = createMock({ - switchToWs: () => ({ - getData: () => ({ - modificationSecret: 'abc', - mapId: '123', - }), - }), - }) - const canActivate = await guard.canActivate(mockContext) - - expect(canActivate).toBe(true) - }) - - it('should return false when user is not provided correct credentials', async () => { - const mockContext = createMock({ - switchToWs: () => ({ - getData: () => ({ - modificationSecret: 'wrong', - mapId: '123', - }), - }), - }) - const canActivate = await guard.canActivate(mockContext) - - expect(canActivate).toBe(false) - }) - }) - - describe('without modificationSecret', () => { - const map: MmpMap = new MmpMap() - map.id = '123' - - beforeAll(async () => { - mapsService = createMock({ - findMap: (_uuid: string) => - new Promise((resolve, _reject) => { - resolve(map) - }), - }) - await Test.createTestingModule({ - providers: [{ provide: MapsService, useValue: mapsService }], - }).compile() - }) - - it('should return true when map has no modification secret', async () => { - const mockContext = createMock({ - switchToWs: () => ({ - getData: () => ({ mapId: '123' }), - }), - }) - const canActivate = await guard.canActivate(mockContext) - - expect(canActivate).toBe(true) - }) - }) - }) -}) diff --git a/teammapper-backend/src/map/guards/edit.guard.ts b/teammapper-backend/src/map/guards/edit.guard.ts deleted file mode 100644 index 49d66852..00000000 --- a/teammapper-backend/src/map/guards/edit.guard.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common' -import { MapsService } from '../services/maps.service' -import { IMmpClientEditingRequest } from '../types' - -@Injectable() -export class EditGuard implements CanActivate { - constructor(private readonly mapsService: MapsService) {} - - canActivate(context: ExecutionContext): Promise { - const request = context.switchToWs().getData() - return this.validateRequest(request.mapId, request.modificationSecret) - } - - async validateRequest( - mapId: string, - givenModificationSecret: string - ): Promise { - const map = await this.mapsService.findMap(mapId) - if (!map || !map.modificationSecret) return true - - return givenModificationSecret === map.modificationSecret - } -} diff --git a/teammapper-backend/src/map/map.module.ts b/teammapper-backend/src/map/map.module.ts index b5e9dd7f..ebce26dc 100644 --- a/teammapper-backend/src/map/map.module.ts +++ b/teammapper-backend/src/map/map.module.ts @@ -1,9 +1,7 @@ -import { MiddlewareConsumer, Module, Provider } from '@nestjs/common' -import { CacheModule } from '@nestjs/cache-manager' +import { MiddlewareConsumer, Module } from '@nestjs/common' import { ScheduleModule } from '@nestjs/schedule' import { TypeOrmModule } from '@nestjs/typeorm' import MapsController from './controllers/maps.controller' -import { MapsGateway } from './controllers/maps.gateway' import { MmpMap } from './entities/mmpMap.entity' import { MmpNode } from './entities/mmpNode.entity' import { LlmUsageCounter } from './entities/llmUsageCounter.entity' @@ -20,36 +18,24 @@ import cookieParser from 'cookie-parser' import { PersonIdMiddleware } from '../auth/person-id.middleware' import configService from '../config.service' -// When Yjs is enabled, the Yjs providers replace the Socket.io MapsGateway. -// Both cannot bind to the same HTTP upgrade path simultaneously. -const baseProviders: Provider[] = [ - MapsService, - TasksService, - AiService, - LlmUsageCounterService, -] - -const yjsProviders: Provider[] = [ - YjsDocManagerService, - YjsPersistenceService, - WsConnectionLimiterService, - YjsGateway, -] - -const mapProviders: Provider[] = configService.isYjsEnabled() - ? [...baseProviders, ...yjsProviders] - : [...baseProviders, MapsGateway] - @Module({ imports: [ TypeOrmModule.forFeature([MmpMap, MmpNode, LlmUsageCounter]), - CacheModule.register(), ScheduleModule.forRoot(), ], controllers: configService.isAiEnabled() ? [MapsController, MermaidController] : [MapsController], - providers: mapProviders, + providers: [ + MapsService, + TasksService, + AiService, + LlmUsageCounterService, + YjsDocManagerService, + YjsPersistenceService, + WsConnectionLimiterService, + YjsGateway, + ], exports: [MapsService], }) export class MapModule { diff --git a/teammapper-backend/src/map/schemas/gateway.schema.spec.ts b/teammapper-backend/src/map/schemas/gateway.schema.spec.ts deleted file mode 100644 index 2f301496..00000000 --- a/teammapper-backend/src/map/schemas/gateway.schema.spec.ts +++ /dev/null @@ -1,315 +0,0 @@ -import * as v from 'valibot' -import { - JoinSchema, - EditingRequestSchema, - CheckModificationSecretSchema, - NodeSelectionSchema, - NodeRequestSchema, - NodeRemoveRequestSchema, - NodeAddRequestSchema, - UpdateMapOptionsSchema, - MapRequestSchema, - UndoRedoRequestSchema, - DeleteRequestSchema, - MapDiffSchema, - validateWsPayload, -} from './gateway.schema' - -describe('JoinSchema', () => { - it('accepts valid input', () => { - const result = v.safeParse(JoinSchema, { mapId: 'abc', color: '#fff' }) - expect(result.success).toBe(true) - }) - - it('rejects empty mapId', () => { - const result = v.safeParse(JoinSchema, { mapId: '', color: '#fff' }) - expect(result.success).toBe(false) - }) - - it('rejects empty color', () => { - const result = v.safeParse(JoinSchema, { mapId: 'abc', color: '' }) - expect(result.success).toBe(false) - }) - - it('rejects missing fields', () => { - expect(v.safeParse(JoinSchema, {}).success).toBe(false) - }) -}) - -describe('EditingRequestSchema / CheckModificationSecretSchema', () => { - it('accepts valid input', () => { - const result = v.safeParse(EditingRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - }) - expect(result.success).toBe(true) - }) - - it('CheckModificationSecretSchema is the same schema', () => { - expect(CheckModificationSecretSchema).toBe(EditingRequestSchema) - }) - - it('rejects empty mapId', () => { - const result = v.safeParse(EditingRequestSchema, { - mapId: '', - modificationSecret: 'x', - }) - expect(result.success).toBe(false) - }) - - it('accepts empty modificationSecret', () => { - const result = v.safeParse(EditingRequestSchema, { - mapId: 'abc', - modificationSecret: '', - }) - expect(result.success).toBe(true) - }) -}) - -describe('NodeSelectionSchema', () => { - it('accepts valid input', () => { - const result = v.safeParse(NodeSelectionSchema, { - mapId: 'abc', - nodeId: 'node-1', - selected: true, - }) - expect(result.success).toBe(true) - }) - - it('rejects non-boolean selected', () => { - const result = v.safeParse(NodeSelectionSchema, { - mapId: 'abc', - nodeId: 'node-1', - selected: 'yes', - }) - expect(result.success).toBe(false) - }) - - it('rejects empty nodeId', () => { - const result = v.safeParse(NodeSelectionSchema, { - mapId: 'abc', - nodeId: '', - selected: true, - }) - expect(result.success).toBe(false) - }) -}) - -describe('NodeRequestSchema', () => { - it('accepts valid input with partial node', () => { - const result = v.safeParse(NodeRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - node: { id: 'node-1', name: 'Updated' }, - updatedProperty: 'name', - }) - expect(result.success).toBe(true) - }) - - it('rejects node without id', () => { - const result = v.safeParse(NodeRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - node: { name: 'No ID' }, - updatedProperty: 'name', - }) - expect(result.success).toBe(false) - }) - - it('rejects missing updatedProperty', () => { - const result = v.safeParse(NodeRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - node: { id: 'node-1' }, - }) - expect(result.success).toBe(false) - }) -}) - -describe('NodeRemoveRequestSchema', () => { - it('accepts valid input without updatedProperty', () => { - const result = v.safeParse(NodeRemoveRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - node: { id: 'node-1' }, - }) - expect(result.success).toBe(true) - }) -}) - -describe('NodeAddRequestSchema', () => { - it('accepts nodes with partial fields', () => { - const result = v.safeParse(NodeAddRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - nodes: [ - { id: 'node-1', name: 'Node', coordinates: { x: 1, y: 2 } }, - { name: 'Node 2' }, - ], - }) - expect(result.success).toBe(true) - }) - - it('accepts empty nodes in array', () => { - const result = v.safeParse(NodeAddRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - nodes: [{}], - }) - expect(result.success).toBe(true) - }) - - it('rejects missing nodes array', () => { - const result = v.safeParse(NodeAddRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - }) - expect(result.success).toBe(false) - }) -}) - -describe('UpdateMapOptionsSchema', () => { - it('accepts full options', () => { - const result = v.safeParse(UpdateMapOptionsSchema, { - mapId: 'abc', - modificationSecret: 'secret', - options: { fontMaxSize: 24, fontMinSize: 10, fontIncrement: 2 }, - }) - expect(result.success).toBe(true) - }) - - it('accepts empty options', () => { - const result = v.safeParse(UpdateMapOptionsSchema, { - mapId: 'abc', - modificationSecret: 'secret', - options: {}, - }) - expect(result.success).toBe(true) - }) -}) - -describe('MapRequestSchema', () => { - it('accepts partial map', () => { - const result = v.safeParse(MapRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - map: { uuid: 'map-1' }, - }) - expect(result.success).toBe(true) - }) - - it('accepts empty map', () => { - const result = v.safeParse(MapRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - map: {}, - }) - expect(result.success).toBe(true) - }) -}) - -describe('UndoRedoRequestSchema', () => { - it('accepts valid diff', () => { - const result = v.safeParse(UndoRedoRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - diff: { added: {}, deleted: {}, updated: {} }, - }) - expect(result.success).toBe(true) - }) - - it('accepts diff with partial node entries', () => { - const result = v.safeParse(UndoRedoRequestSchema, { - mapId: 'abc', - modificationSecret: 'secret', - diff: { - added: {}, - deleted: {}, - updated: { 'node-1': { name: 'Updated' } }, - }, - }) - expect(result.success).toBe(true) - }) -}) - -describe('DeleteRequestSchema', () => { - it('accepts valid input', () => { - const result = v.safeParse(DeleteRequestSchema, { - adminId: 'admin', - mapId: 'map-1', - }) - expect(result.success).toBe(true) - }) - - it('accepts null adminId', () => { - const result = v.safeParse(DeleteRequestSchema, { - adminId: null, - mapId: 'map-1', - }) - expect(result.success).toBe(true) - }) - - it('rejects empty mapId', () => { - const result = v.safeParse(DeleteRequestSchema, { - adminId: null, - mapId: '', - }) - expect(result.success).toBe(false) - }) -}) - -describe('MapDiffSchema', () => { - it('accepts empty diff', () => { - const result = v.safeParse(MapDiffSchema, { - added: {}, - deleted: {}, - updated: {}, - }) - expect(result.success).toBe(true) - }) - - it('rejects missing sections', () => { - expect(v.safeParse(MapDiffSchema, {}).success).toBe(false) - }) -}) - -describe('validateWsPayload', () => { - const createMockClient = () => { - const emitted: { event: string; data: unknown }[] = [] - return { - emit: (event: string, data: unknown) => { - emitted.push({ event, data }) - }, - emitted, - } - } - - it('returns parsed output on valid data', () => { - const client = createMockClient() - const result = validateWsPayload(client as never, JoinSchema, { - mapId: 'abc', - color: '#fff', - }) - expect(result).toEqual({ mapId: 'abc', color: '#fff' }) - expect(client.emitted).toHaveLength(0) - }) - - it('emits exception and returns null on invalid data', () => { - const client = createMockClient() - const result = validateWsPayload(client as never, JoinSchema, {}) - expect(result).toBeNull() - expect(client.emitted).toHaveLength(1) - expect(client.emitted[0].event).toBe('exception') - }) - - it('includes issues in the exception payload', () => { - const client = createMockClient() - validateWsPayload(client as never, JoinSchema, { mapId: '' }) - const payload = client.emitted[0].data as { - message: string - issues: unknown[] - } - expect(payload.message).toBe('Invalid payload') - expect(payload.issues.length).toBeGreaterThan(0) - }) -}) diff --git a/teammapper-backend/src/map/schemas/gateway.schema.ts b/teammapper-backend/src/map/schemas/gateway.schema.ts deleted file mode 100644 index 4b32a41e..00000000 --- a/teammapper-backend/src/map/schemas/gateway.schema.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as v from 'valibot' -import { NodeSchema } from './node.schema' -import { sanitizeIssues } from './sanitize-issues' -import type { Socket } from 'socket.io' - -// Base schemas - -export const JoinSchema = v.object({ - mapId: v.pipe(v.string(), v.nonEmpty()), - color: v.pipe(v.string(), v.nonEmpty()), -}) - -export const EditingRequestSchema = v.object({ - modificationSecret: v.string(), - mapId: v.pipe(v.string(), v.nonEmpty()), -}) - -export const CheckModificationSecretSchema = EditingRequestSchema - -export const NodeSelectionSchema = v.object({ - mapId: v.pipe(v.string(), v.nonEmpty()), - nodeId: v.pipe(v.string(), v.nonEmpty()), - selected: v.boolean(), -}) - -// EditGuard-protected schemas - -export const MapOptionsSchema = v.partial( - v.object({ - fontMaxSize: v.number(), - fontMinSize: v.number(), - fontIncrement: v.number(), - }) -) - -// For update/remove operations, node can be partial (only id is required) -const PartialNodeWithIdSchema = v.object({ - ...v.partial(NodeSchema).entries, - id: v.pipe(v.string(), v.nonEmpty()), -}) - -export const NodeRequestSchema = v.object({ - ...EditingRequestSchema.entries, - node: PartialNodeWithIdSchema, - updatedProperty: v.string(), -}) - -export const NodeRemoveRequestSchema = v.object({ - ...EditingRequestSchema.entries, - node: PartialNodeWithIdSchema, -}) - -export const NodeAddRequestSchema = v.object({ - ...EditingRequestSchema.entries, - nodes: v.array(v.partial(NodeSchema)), -}) - -export const UpdateMapOptionsSchema = v.object({ - ...EditingRequestSchema.entries, - options: MapOptionsSchema, -}) - -export const SnapshotChangesSchema = v.record( - v.string(), - v.optional(v.partial(NodeSchema)) -) - -export const MapDiffSchema = v.object({ - added: SnapshotChangesSchema, - deleted: SnapshotChangesSchema, - updated: SnapshotChangesSchema, -}) - -const DateLikeSchema = v.union([v.string(), v.number(), v.date()]) - -// MapSchema validates structure but allows partial data — the service layer -// handles business validation and missing field errors -export const MapSchema = v.partial( - v.object({ - uuid: v.string(), - lastModified: v.nullable(DateLikeSchema), - lastAccessed: v.nullable(DateLikeSchema), - deleteAfterDays: v.number(), - deletedAt: DateLikeSchema, - data: v.array(NodeSchema), - options: MapOptionsSchema, - createdAt: v.nullable(DateLikeSchema), - writable: v.boolean(), - }) -) - -export const MapRequestSchema = v.object({ - ...EditingRequestSchema.entries, - map: MapSchema, -}) - -export const UndoRedoRequestSchema = v.object({ - ...EditingRequestSchema.entries, - diff: MapDiffSchema, -}) - -export const DeleteRequestSchema = v.object({ - adminId: v.nullable(v.string()), - mapId: v.pipe(v.string(), v.nonEmpty()), -}) - -// Inferred types - -export type IMmpClientJoinRequest = v.InferOutput -export type IMmpClientEditingRequest = v.InferOutput< - typeof EditingRequestSchema -> -export type IMmpClientNodeRequest = v.InferOutput -export type IMmpClientNodeAddRequest = v.InferOutput< - typeof NodeAddRequestSchema -> -export type IMmpClientNodeSelectionRequest = v.InferOutput< - typeof NodeSelectionSchema -> -export type IMmpClientUpdateMapOptionsRequest = v.InferOutput< - typeof UpdateMapOptionsSchema -> -export type IMmpClientMapOptions = v.InferOutput -export type IMmpClientMapRequest = v.InferOutput -export type IMmpClientUndoRedoRequest = v.InferOutput< - typeof UndoRedoRequestSchema -> -export type IMmpClientDeleteRequest = v.InferOutput -export type IMmpClientSnapshotChanges = v.InferOutput< - typeof SnapshotChangesSchema -> -export type IMmpClientMapDiff = v.InferOutput - -// Validation helper - -export const validateWsPayload = ( - client: Socket, - schema: v.GenericSchema, - data: unknown -): T | null => { - const result = v.safeParse(schema, data) - if (!result.success) { - client.emit('exception', { - message: 'Invalid payload', - issues: sanitizeIssues(result.issues), - }) - return null - } - return result.output -} diff --git a/teammapper-backend/src/map/schemas/maps.schema.spec.ts b/teammapper-backend/src/map/schemas/maps.schema.spec.ts index 5738f816..2a3b44e8 100644 --- a/teammapper-backend/src/map/schemas/maps.schema.spec.ts +++ b/teammapper-backend/src/map/schemas/maps.schema.spec.ts @@ -12,7 +12,6 @@ const validCreateInput = { const validDeleteInput = { adminId: 'admin-123', - mapId: 'map-456', } describe('MapCreateSchema', () => { @@ -62,32 +61,16 @@ describe('MapDeleteSchema', () => { expect(result.success).toBe(true) }) - it('accepts null adminId', () => { + it('rejects null adminId', () => { const result = v.safeParse(MapDeleteSchema, { ...validDeleteInput, adminId: null, }) - expect(result.success).toBe(true) - }) - - it('rejects missing mapId', () => { - const result = v.safeParse(MapDeleteSchema, { adminId: 'abc' }) - expect(result.success).toBe(false) - }) - - it('rejects empty mapId', () => { - const result = v.safeParse(MapDeleteSchema, { - adminId: null, - mapId: '', - }) expect(result.success).toBe(false) }) - it('rejects non-string mapId', () => { - const result = v.safeParse(MapDeleteSchema, { - adminId: null, - mapId: 123, - }) + it('rejects empty adminId', () => { + const result = v.safeParse(MapDeleteSchema, { adminId: '' }) expect(result.success).toBe(false) }) }) diff --git a/teammapper-backend/src/map/schemas/maps.schema.ts b/teammapper-backend/src/map/schemas/maps.schema.ts index 45acfdde..b6352738 100644 --- a/teammapper-backend/src/map/schemas/maps.schema.ts +++ b/teammapper-backend/src/map/schemas/maps.schema.ts @@ -1,14 +1,22 @@ import * as v from 'valibot' import { NodeBasicsSchema } from './node.schema' +export const MapOptionsSchema = v.partial( + v.object({ + fontMaxSize: v.number(), + fontMinSize: v.number(), + fontIncrement: v.number(), + }) +) + export const MapCreateSchema = v.object({ rootNode: NodeBasicsSchema, }) export const MapDeleteSchema = v.object({ - adminId: v.nullable(v.string()), - mapId: v.pipe(v.string(), v.nonEmpty()), + adminId: v.pipe(v.string(), v.nonEmpty()), }) +export type IMmpClientMapOptions = v.InferOutput export type IMmpClientMapCreateRequest = v.InferOutput export type IMmpClientDeleteRequest = v.InferOutput diff --git a/teammapper-backend/src/map/services/maps.service.spec.ts b/teammapper-backend/src/map/services/maps.service.spec.ts index 5f6388ef..a53dcb8a 100644 --- a/teammapper-backend/src/map/services/maps.service.spec.ts +++ b/teammapper-backend/src/map/services/maps.service.spec.ts @@ -4,14 +4,13 @@ import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm' import { Logger } from '@nestjs/common' import { MmpMap } from '../entities/mmpMap.entity' import { MmpNode } from '../entities/mmpNode.entity' -import { Repository, QueryFailedError } from 'typeorm' +import { Repository } from 'typeorm' import { ConfigModule } from '@nestjs/config' import AppModule from '../../app.module' import { createTestConfiguration, destroyWorkerDatabase, } from '../../../test/db' -import { mapMmpNodeToClient } from '../utils/clientServerMapping' import { truncateDatabase } from 'test/helper' import { jest } from '@jest/globals' @@ -22,8 +21,6 @@ describe('MapsService', () => { let moduleFixture: TestingModule beforeAll(async () => { - // Calling advanceTimers here is very important, as otherwise async ops like await will hang indefinitely - // Ref: https://jestjs.io/docs/jest-object#jestusefaketimersfaketimersconfig jest.useFakeTimers({ advanceTimers: true }) moduleFixture = await Test.createTestingModule({ @@ -45,14 +42,12 @@ describe('MapsService', () => { }) afterAll(async () => { - // close connection: await destroyWorkerDatabase( mapsRepo.manager.connection, process.env.JEST_WORKER_ID || '' ) await moduleFixture.close() - // Make sure we use real timers after these tests so others are not affected jest.useRealTimers() }) @@ -71,10 +66,10 @@ describe('MapsService', () => { } describe('addNodes', () => { - it('adds a node', async () => { + it('bulk-inserts nodes for the duplicate-map flow', async () => { const map = await mapsRepo.save({}) - const node = await nodesRepo.create({ + const node = nodesRepo.create({ id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', nodeMapId: map.id, coordinatesX: 3, @@ -83,192 +78,58 @@ describe('MapsService', () => { detached: true, }) - await mapsService.addNodes(map.id, [node]) + const inserted = await mapsService.addNodes(map.id, [node]) + expect(inserted).toHaveLength(1) const createdNode = await nodesRepo.findOne({ where: { id: node.id }, }) - - expect(createdNode).not.toBeUndefined() - }) - - it('catches an FK error when trying to assign a nodeParentId to a root node', async () => { - const map = await mapsRepo.save({}) - const loggerSpyWarn = jest.spyOn(Logger.prototype, 'warn') - - const node = await nodesRepo.create({ - id: '2177d542-665d-468c-bea5-7520bdc5b481', - nodeMapId: map.id, - root: true, - coordinatesX: 2, - coordinatesY: 1, - nodeParentId: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - }) - - const nodes = await mapsService.addNodes(map.id, [node]) - - // Now returns ValidationErrorResponse instead of empty array - expect(Array.isArray(nodes)).toBe(true) - expect(nodes).toHaveLength(1) - if ('errorType' in nodes[0]) { - expect(nodes[0].success).toBe(false) - expect(nodes[0].errorType).toBe('validation') - expect(nodes[0].code).toBe('CONSTRAINT_VIOLATION') - } - expect(loggerSpyWarn).toHaveBeenCalled() - }) - - it('catches an FK error when trying to assign a nodeParentId to a detached node', async () => { - const map = await mapsRepo.save({}) - const loggerSpyWarn = jest.spyOn(Logger.prototype, 'warn') - - const node = await nodesRepo.create({ - id: '3288e653-776e-579d-cba6-8631cec6c592', - nodeMapId: map.id, - detached: true, - coordinatesX: 2, - coordinatesY: 1, - nodeParentId: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - }) - - const nodes = await mapsService.addNodes(map.id, [node]) - - // Now returns ValidationErrorResponse instead of empty array - expect(Array.isArray(nodes)).toBe(true) - expect(nodes).toHaveLength(1) - if ('errorType' in nodes[0]) { - expect(nodes[0].success).toBe(false) - expect(nodes[0].errorType).toBe('validation') - expect(nodes[0].code).toBe('CONSTRAINT_VIOLATION') - } - expect(loggerSpyWarn).toHaveBeenCalledWith( - expect.stringContaining('Detached node') - ) - }) - - it('catches an FK error when trying to assign a nodeParentId from a different map', async () => { - const map = await mapsRepo.save({}) - const mapTwo = await mapsRepo.save({}) - - // First add parent node to map - const parentNode = await nodesRepo.save({ - id: '2177d542-665d-468c-bea5-7520bdc5b481', - nodeMapId: map.id, - coordinatesX: 2, - coordinatesY: 1, - root: true, - detached: false, - }) - - // Try to add child node to mapTwo that references parent in map - const childNodeFromDifferentMap = await nodesRepo.create({ - id: 'cf65f9cc-0050-4e23-ac4d-effb61cb1731', - nodeMapId: mapTwo.id, - coordinatesX: 1, - coordinatesY: 1, - nodeParentId: parentNode.id, - root: false, - detached: false, - }) - - const nodes = await mapsService.addNodes(mapTwo.id, [ - childNodeFromDifferentMap, - ]) - - // Should fail with INVALID_PARENT because parent is in different map - expect(Array.isArray(nodes)).toBe(true) - expect(nodes.length).toBe(1) - expect('errorType' in nodes[0]).toBe(true) - if ('errorType' in nodes[0]) { - expect(nodes[0].success).toBe(false) - expect(nodes[0].errorType).toBe('validation') - expect(nodes[0].code).toBe('INVALID_PARENT') - } + expect(createdNode).not.toBeNull() }) - }) - describe('updateNode', () => { - it('does update the lastModified value on update', async () => { - const map = await mapsRepo.save({ - lastModified: new Date('2019-01-01'), - }) - - const oldDate = new Date('2019-01-01') - const node = await createNode(map, oldDate) - - const clientNode = mapMmpNodeToClient(node) - clientNode.name = 'new' - - // we save the time before the update to be able to compare the lastModified date and make sure it's newer than this: - const timeBeforeUpdate = new Date() - await mapsService.updateNode(map.id, clientNode) - const updatedNode = await nodesRepo.findOne({ - where: { id: node.id }, - }) - - expect(updatedNode?.lastModified).not.toEqual(oldDate) - expect(updatedNode?.lastModified!.getTime()).toBeGreaterThan( - timeBeforeUpdate.getTime() - ) - }) - - it('returns ValidationErrorResponse when trying to update node with non-existent parent', async () => { + it('skips nodes that already exist in the map', async () => { const map = await mapsRepo.save({}) - const loggerSpyWarn = jest.spyOn(Logger.prototype, 'warn') - const node = await nodesRepo.save({ + const node = nodesRepo.create({ id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', nodeMapId: map.id, coordinatesX: 3, coordinatesY: 1, - root: false, + root: true, detached: false, }) - const clientNode = mapMmpNodeToClient(node) - // Use a valid UUID format but non-existent parent - clientNode.parent = '99999999-9999-9999-9999-999999999999' + const first = await mapsService.addNodes(map.id, [node]) + expect(first).toHaveLength(1) - const result = await mapsService.updateNode(map.id, clientNode) + const second = await mapsService.addNodes(map.id, [node]) + expect(second).toHaveLength(0) - // Now returns ValidationErrorResponse instead of undefined - expect(result).toBeDefined() - if (result && 'errorType' in result) { - expect(result.success).toBe(false) - expect(result.errorType).toBe('validation') - expect(result.code).toBe('CONSTRAINT_VIOLATION') - } - expect(loggerSpyWarn).toHaveBeenCalledWith( - expect.stringContaining('Cannot update node') - ) + const allNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(allNodes.length).toBe(1) }) - it('rejects promise when save operation fails', async () => { + it('throws and rolls back on database errors', async () => { const map = await mapsRepo.save({}) const loggerSpyError = jest.spyOn(Logger.prototype, 'error') - const node = await nodesRepo.save({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', + const invalidNode = nodesRepo.create({ + id: '33333333-3333-4333-8333-333333333333', nodeMapId: map.id, coordinatesX: 3, - coordinatesY: 1, - root: true, + coordinatesY: 3, + root: false, + detached: false, + nodeParentId: '99999999-9999-4999-8999-999999999999', }) - const clientNode = mapMmpNodeToClient(node) - clientNode.name = 'updated' + await expect( + mapsService.addNodes(map.id, [invalidNode]) + ).rejects.toThrow() + expect(loggerSpyError).toHaveBeenCalled() - // Mock save to throw an error - jest - .spyOn(nodesRepo, 'save') - .mockRejectedValueOnce(new Error('Database error')) - - await expect(mapsService.updateNode(map.id, clientNode)).rejects.toThrow( - 'Database error' - ) - expect(loggerSpyError).toHaveBeenCalledWith( - expect.stringContaining('Failed to update node') - ) + const allNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(allNodes.length).toBe(0) }) }) @@ -284,10 +145,8 @@ describe('MapsService', () => { describe('deleteOutdatedMaps', () => { it('deletes a map based off of lastAccessed', async () => { - // Explicitly set system time to lastAccessed + 30 days jest.setSystemTime(new Date('2021-01-31')) - // Last modified is now() by default, so we need to set it here explicitly. const map = await mapsRepo.save({ lastAccessed: new Date('2021-01-01'), lastModified: new Date('2020-01-01'), @@ -301,7 +160,6 @@ describe('MapsService', () => { }) it('does not delete a new map', async () => { - // Explicitly set system time to equal lastAccessed jest.setSystemTime(new Date('2024-09-01')) const map = await mapsRepo.save({ lastAccessed: new Date('2024-09-01'), @@ -316,7 +174,6 @@ describe('MapsService', () => { }) it('deletes a map where lastAccessed is not set and lastModified is too old', async () => { - // Explicitly set system time to lastModified + 30 days jest.setSystemTime(new Date('2021-01-31')) const map = await mapsRepo.save({ @@ -331,7 +188,6 @@ describe('MapsService', () => { }) it('does not delete a map where lastModified is old but lastAccessed is recent', async () => { - // Explicitly set system time to equal lastAccessed jest.setSystemTime(new Date('2024-09-01')) const map = await mapsRepo.save({ @@ -348,7 +204,6 @@ describe('MapsService', () => { }) it('does not delete a map where lastAccessed is old but lastModified is recent', async () => { - // Explicitly set system time to equal lastModified jest.setSystemTime(new Date('2024-09-01')) const map = await mapsRepo.save({ @@ -365,7 +220,6 @@ describe('MapsService', () => { }) it('does delete a map that contains only outdated nodes', async () => { - // Explicitly set system time to node + 30 days jest.setSystemTime(new Date('2021-01-31')) const map = await mapsRepo.save({ @@ -380,10 +234,8 @@ describe('MapsService', () => { }) it('does not delete a map that contains a recent node', async () => { - // Explicitly set system time to equal node jest.setSystemTime(new Date('2024-09-01')) - // map itself is old, but node is not: const map = await mapsRepo.save({ lastModified: new Date('2021-01-01'), }) @@ -396,7 +248,6 @@ describe('MapsService', () => { }) it('deletes a map which has outdated nodes and outdated lastAccessed', async () => { - // Explicitly set system time to lastAccessed + 30 days jest.setSystemTime(new Date('2021-01-31')) const map = await mapsRepo.save({ @@ -412,7 +263,6 @@ describe('MapsService', () => { }) it('does not delete a map which has outdated lastAccessed but some recent nodes', async () => { - // Explcitly set system time to equal recentNode jest.setSystemTime(new Date('2024-09-01')) const map = await mapsRepo.save({ @@ -433,10 +283,8 @@ describe('MapsService', () => { }) it('does not delete a map which has outdated lastModified but some recent nodes', async () => { - // Explicitly set system time to equal recentNode jest.setSystemTime(new Date('2024-09-01')) - // map itself is old, but node is not: const map = await mapsRepo.save({ lastModified: new Date('2021-01-01'), }) @@ -455,7 +303,6 @@ describe('MapsService', () => { }) it('does delete outdated empty maps', async () => { - // Explicitly set system time to lastModified + 30 days jest.setSystemTime(new Date('2021-01-31')) const map = await mapsRepo.save({ @@ -492,35 +339,10 @@ describe('MapsService', () => { }) }) - describe('removeNode', () => { - it('remove all nodes connected together', async () => { - const map = await mapsRepo.save({}) - - const node = await nodesRepo.save({ - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - }) - - const nodeTwo = await nodesRepo.save({ - nodeMapId: map.id, - nodeParent: node, - coordinatesX: 3, - coordinatesY: 1, - }) - - await mapsService.removeNode(mapMmpNodeToClient(node), map.id) - expect(await nodesRepo.findOne({ where: { id: nodeTwo.id } })).toEqual( - null - ) - }) - }) - describe('createEmptyMap', () => { it('rejects promise when root node creation fails', async () => { const loggerSpyError = jest.spyOn(Logger.prototype, 'error') - // Mock the save operation to throw an error jest .spyOn(nodesRepo, 'save') .mockRejectedValueOnce(new Error('Database error')) @@ -539,6 +361,7 @@ describe('MapsService', () => { expect.stringContaining('Failed to create root node') ) }) + it('creates a map with a specified userId as owner', async () => { const testUserId = 'test-person-id' @@ -553,391 +376,6 @@ describe('MapsService', () => { }) }) - describe('addNode - duplicate handling', () => { - it('returns existing node when trying to add duplicate', async () => { - const map = await mapsRepo.save({}) - - const node = await nodesRepo.create({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - root: true, - detached: false, - }) - - const firstAdd = await mapsService.addNode(map.id, node) - expect(firstAdd).not.toBeUndefined() - expect('id' in firstAdd!).toBe(true) - - // Try to add the same node again - const secondAdd = await mapsService.addNode(map.id, node) - expect(secondAdd).not.toBeUndefined() - expect('id' in secondAdd!).toBe(true) - if ('id' in firstAdd! && 'id' in secondAdd!) { - expect(secondAdd.id).toEqual(firstAdd.id) - } - - // Verify only one node exists in database - const allNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) - expect(allNodes.length).toEqual(1) - }) - }) - - describe('updateMapByDiff', () => { - it('rolls back all changes when any operation fails (atomic transaction)', async () => { - const map = await mapsRepo.save({}) - const loggerSpy = jest.spyOn(Logger.prototype, 'error') - - const existingNode = await nodesRepo.save({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - root: true, - name: 'Original Name', - }) - - // Create a diff with valid update but invalid add (invalid UUID will cause error) - const diff = { - added: { - 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa': { - id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - name: 'New Node', - coordinates: { x: 1, y: 1 }, - isRoot: false, - detached: false, - // Invalid parent reference will cause transaction to fail - parent: '99999999-9999-9999-9999-999999999999', - }, - }, - updated: { - [existingNode.id]: { - name: 'Updated Name', - }, - }, - deleted: {}, - } - - // Should throw error and roll back ALL changes (atomic behavior) - await expect(mapsService.updateMapByDiff(map.id, diff)).rejects.toThrow() - - // Verify error was logged - expect(loggerSpy).toHaveBeenCalled() - - // Verify the update was rolled back - name should still be original - const nodeAfterRollback = await nodesRepo.findOne({ - where: { id: existingNode.id }, - }) - expect(nodeAfterRollback?.name).toEqual('Original Name') - - // Verify the new node was not added - const newNode = await nodesRepo.findOne({ - where: { id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - }) - expect(newNode).toBeNull() - }) - - it('successfully applies all changes when all operations succeed (atomic transaction)', async () => { - const map = await mapsRepo.save({}) - - const rootNode = await nodesRepo.save({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - root: true, - name: 'Original Name', - }) - - const nodeToDelete = await nodesRepo.save({ - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - nodeMapId: map.id, - coordinatesX: 4, - coordinatesY: 2, - root: false, - detached: true, - }) - - const diff = { - added: { - 'cccccccc-cccc-4ccc-8ccc-cccccccccccc': { - id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - name: 'New Node', - coordinates: { x: 5, y: 3 }, - isRoot: false, - detached: true, - }, - }, - updated: { - [rootNode.id]: { - name: 'Updated Name', - }, - }, - deleted: { - [nodeToDelete.id]: {}, - }, - } - - // Should succeed atomically - await mapsService.updateMapByDiff(map.id, diff) - - // Verify update was applied - const updatedNode = await nodesRepo.findOne({ - where: { id: rootNode.id }, - }) - expect(updatedNode?.name).toEqual('Updated Name') - - // Verify node was added - const newNode = await nodesRepo.findOne({ - where: { id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc' }, - }) - expect(newNode).not.toBeNull() - expect(newNode?.name).toEqual('New Node') - - // Verify node was deleted - const deletedNode = await nodesRepo.findOne({ - where: { id: nodeToDelete.id }, - }) - expect(deletedNode).toBeNull() - }) - - it('processes updates sequentially to maintain parent-child relationships', async () => { - const map = await mapsRepo.save({}) - - const rootNode = await nodesRepo.save({ - id: '11111111-1111-1111-1111-111111111111', - nodeMapId: map.id, - coordinatesX: 1, - coordinatesY: 1, - root: true, - }) - - const diff = { - added: {}, - updated: { - [rootNode.id]: { - name: 'Updated Root', - }, - }, - deleted: {}, - } - - await mapsService.updateMapByDiff(map.id, diff) - - const updatedNode = await nodesRepo.findOne({ - where: { id: rootNode.id }, - }) - expect(updatedNode?.name).toEqual('Updated Root') - }) - }) - - describe('mapConstraintErrorToValidationResponse', () => { - it('returns ValidationErrorResponse for invalid parent constraint', async () => { - const map = await mapsRepo.save({}) - - // Try to add a node with invalid but valid-UUID-format parent - const invalidNode = nodesRepo.create({ - id: '00000000-0000-4000-8000-000000000002', - nodeMapId: map.id, - nodeParentId: '99999999-9999-9999-9999-999999999999', - coordinatesX: 1, - coordinatesY: 1, - }) - - // This should throw a QueryFailedError - try { - await nodesRepo.save(invalidNode) - fail('Expected QueryFailedError to be thrown') - } catch (error) { - if (error instanceof Error && error.name === 'QueryFailedError') { - const response = - await mapsService.mapConstraintErrorToValidationResponse( - error as QueryFailedError, - invalidNode, - map.id - ) - - expect(response.success).toBe(false) - expect(response.errorType).toBe('validation') - expect(response.code).toBe('INVALID_PARENT') - expect(response.message).toBe('VALIDATION_ERROR.INVALID_PARENT') - } - } - }) - }) - - describe('addNode with ValidationErrorResponse', () => { - it('returns ValidationErrorResponse when addNode fails with constraint violation', async () => { - const map = await mapsRepo.save({}) - - // Create a node with invalid parent reference (valid UUID format but doesn't exist) - const nodeWithInvalidParent = nodesRepo.create({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - nodeMapId: map.id, - nodeParentId: '99999999-9999-4999-8999-999999999999', - coordinatesX: 3, - coordinatesY: 1, - root: false, - detached: false, - }) - - const result = await mapsService.addNode(map.id, nodeWithInvalidParent) - - // Should return ValidationErrorResponse, not throw - expect(result).toBeDefined() - if (result && 'errorType' in result) { - expect(result.success).toBe(false) - expect(result.errorType).toBe('validation') - expect(result.code).toBe('INVALID_PARENT') - } - }) - }) - - describe('addNodes - atomic transaction behavior', () => { - it('rolls back all nodes if one node fails validation (atomic operation)', async () => { - const map = await mapsRepo.save({}) - - // Create a valid root node - const validRootNode = nodesRepo.create({ - id: '11111111-1111-1111-1111-111111111111', - nodeMapId: map.id, - coordinatesX: 1, - coordinatesY: 1, - root: true, - detached: false, - }) - - // Create another valid detached node - const validDetachedNode = nodesRepo.create({ - id: '22222222-2222-2222-2222-222222222222', - nodeMapId: map.id, - coordinatesX: 2, - coordinatesY: 2, - root: false, - detached: true, - }) - - // Create an invalid node with non-existent parent (valid UUID format) - const invalidNode = nodesRepo.create({ - id: '33333333-3333-4333-8333-333333333333', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 3, - root: false, - detached: false, - nodeParentId: '99999999-9999-4999-8999-999999999999', // Valid UUID but doesn't exist - }) - - // Try to add all three nodes at once - const result = await mapsService.addNodes(map.id, [ - validRootNode, - validDetachedNode, - invalidNode, - ]) - - // Should return a single ValidationErrorResponse - expect(Array.isArray(result)).toBe(true) - expect(result.length).toBe(1) - expect('errorType' in result[0]).toBe(true) - if ('errorType' in result[0]) { - expect(result[0].success).toBe(false) - expect(result[0].errorType).toBe('validation') - expect(result[0].code).toBe('INVALID_PARENT') - } - - // Verify NO nodes were created in the database (atomic rollback) - const allNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) - expect(allNodes.length).toBe(0) - }) - - it('successfully adds all nodes when all pass validation (atomic success)', async () => { - const map = await mapsRepo.save({}) - - // Create a valid root node - const rootNode = nodesRepo.create({ - id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - nodeMapId: map.id, - coordinatesX: 1, - coordinatesY: 1, - root: true, - detached: false, - }) - - // Create a valid detached node - const detachedNode = nodesRepo.create({ - id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 3, - root: false, - detached: true, - }) - - // Add root node first - const rootResult = await mapsService.addNodes(map.id, [rootNode]) - expect(rootResult.length).toBe(1) - expect('id' in rootResult[0]).toBe(true) - - // Add detached node - const detachedResult = await mapsService.addNodes(map.id, [detachedNode]) - expect(Array.isArray(detachedResult)).toBe(true) - expect(detachedResult.length).toBe(1) - expect('id' in detachedResult[0]).toBe(true) - - // Create a child node with reference to rootNode (after root is saved) - const childNode = nodesRepo.create({ - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - nodeMapId: map.id, - coordinatesX: 2, - coordinatesY: 2, - root: false, - detached: false, - nodeParentId: rootNode.id, - }) - - // Add child node (has parent reference to rootNode) - const childResult = await mapsService.addNodes(map.id, [childNode]) - expect(Array.isArray(childResult)).toBe(true) - expect(childResult.length).toBe(1) - expect('id' in childResult[0]).toBe(true) - - // Verify all nodes exist in database - const allNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) - expect(allNodes.length).toBe(3) - }) - }) - - describe('updateNode with ValidationErrorResponse', () => { - it('returns ValidationErrorResponse for invalid parent on update', async () => { - const map = await mapsRepo.save({}) - - const node = await nodesRepo.save({ - id: '78a2ae85-1815-46da-a2bc-a41de6bdd5cc', - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - root: true, - }) - - const clientNode = mapMmpNodeToClient(node) - // Set invalid parent (non-existent UUID) - clientNode.parent = '99999999-9999-9999-9999-999999999999' - clientNode.isRoot = false - - const result = await mapsService.updateNode(map.id, clientNode) - - // Should return ValidationErrorResponse - expect(result).toBeDefined() - if (result && 'errorType' in result) { - expect(result.success).toBe(false) - expect(result.errorType).toBe('validation') - // updateNode returns CONSTRAINT_VIOLATION for invalid parent - expect(result.code).toBe('CONSTRAINT_VIOLATION') - } - }) - }) - describe('getMapsOfUser', () => { it('returns [] if no userId is provided', async () => { await mapsRepo.save({ ownerExternalId: undefined }) diff --git a/teammapper-backend/src/map/services/maps.service.ts b/teammapper-backend/src/map/services/maps.service.ts index 606753a7..6875fdc4 100644 --- a/teammapper-backend/src/map/services/maps.service.ts +++ b/teammapper-backend/src/map/services/maps.service.ts @@ -1,28 +1,18 @@ import { Injectable, Logger } from '@nestjs/common' import { InjectRepository } from '@nestjs/typeorm' -import { Repository, QueryFailedError, QueryRunner, In } from 'typeorm' +import { Repository, QueryRunner, In } from 'typeorm' import { MmpMap } from '../entities/mmpMap.entity' import { MmpNode } from '../entities/mmpNode.entity' import { IMmpClientMap, - IMmpClientMapOptions, - IMmpClientNode, IMmpClientNodeBasics, - IMmpClientMapDiff, - IMmpClientSnapshotChanges, IMmpClientMapInfo, - ValidationErrorResponse, } from '../types' import { mapClientBasicNodeToMmpRootNode, mapClientNodeToMmpNode, mapMmpMapToClient, - mergeClientNodeIntoMmpNode, } from '../utils/clientServerMapping' -import { - shouldValidateParent, - createParentNotFoundWarning, -} from '../utils/nodeValidation' import configService from '../../config.service' import { validate as uuidValidate } from 'uuid' import MalformedUUIDError from './uuid.error' @@ -31,11 +21,6 @@ import MalformedUUIDError from './uuid.error' export class MapsService { private readonly logger = new Logger(MapsService.name) - // PostgreSQL error codes for constraint violations - private static readonly PG_FOREIGN_KEY_VIOLATION = '23503' - private static readonly PG_UNIQUE_VIOLATION = '23505' - private static readonly PG_CHECK_VIOLATION = '23514' - constructor( @InjectRepository(MmpNode) private nodesRepository: Repository, @@ -43,29 +28,6 @@ export class MapsService { private mapsRepository: Repository ) {} - /** - * Checks if an error is a database constraint violation - * Detects PostgreSQL constraint errors: foreign key violations, unique violations, and check violations - * @param error The error to classify - * @returns true if the error is a constraint violation, false otherwise - */ - private isConstraintError(error: unknown): boolean { - if (!(error instanceof QueryFailedError)) { - return false - } - - // Check for foreign key constraint violations (PostgreSQL error codes) - const pgError = error.driverError as { code?: string } - return ( - pgError?.code === MapsService.PG_FOREIGN_KEY_VIOLATION || - pgError?.code === MapsService.PG_UNIQUE_VIOLATION || - pgError?.code === MapsService.PG_CHECK_VIOLATION - ) - } - - /** - * Finds the root node for a given map - */ private async findRootNode(mapId: string): Promise { return await this.nodesRepository.findOne({ where: { nodeMapId: mapId, root: true }, @@ -95,35 +57,6 @@ export class MapsService { return mapsInfo.slice(0, 20) } - /** - * Maps database constraint errors to structured validation responses - */ - async mapConstraintErrorToValidationResponse( - error: QueryFailedError, - _node: Partial, - _mapId: string - ): Promise { - const pgError = error.driverError as { code?: string; detail?: string } - - // Determine error code based on constraint type - let code: ValidationErrorResponse['code'] = 'CONSTRAINT_VIOLATION' - if (pgError?.code === MapsService.PG_FOREIGN_KEY_VIOLATION) { - code = 'INVALID_PARENT' - } else if (pgError?.code === MapsService.PG_UNIQUE_VIOLATION) { - code = 'DUPLICATE_NODE' - } - - return { - success: false, - errorType: 'validation', - code, - message: `VALIDATION_ERROR.${code}`, - context: { - detail: pgError?.detail, - }, - } - } - findMap(uuid: string): Promise { if (!uuidValidate(uuid)) return Promise.reject(new MalformedUUIDError('Invalid UUID')) @@ -140,7 +73,7 @@ export class MapsService { return } - this.mapsRepository.update(uuid, { lastAccessed }) + await this.mapsRepository.update(uuid, { lastAccessed }) } async exportMapToClient(uuid: string): Promise { @@ -160,103 +93,12 @@ export class MapsService { } /** - * Validates that node parent constraints are satisfied - * Root and detached nodes cannot have parents - this is a business rule - * @param node The node to validate - * @returns true if constraints are valid, false if violated - */ - private validateNodeParentConstraints(node: MmpNode): boolean { - if (node.detached && node.nodeParentId) { - this.logger.warn( - `addNode(): Detached node ${node.id} is not allowed to have a parent.` - ) - return false - } - - if (node.root && node.nodeParentId) { - this.logger.warn( - `addNode(): Root node ${node.id} is not allowed to have a parent.` - ) - return false - } - - return true - } - - async addNode( - mapId: string, - node: MmpNode - ): Promise { - if (!mapId || !node) { - this.logger.warn( - `addNode(): Required arguments mapId or node not supplied` - ) - return - } - - // Check node parent constraints - if (!this.validateNodeParentConstraints(node)) { - // Return validation error - return await this.mapConstraintErrorToValidationResponse( - new QueryFailedError('', [], new Error('CONSTRAINT_VIOLATION')), - node, - mapId - ) - } - - const existingNode = await this.nodesRepository.findOne({ - where: { id: node.id, nodeMapId: mapId }, - }) - if (existingNode) return existingNode - - const newNode = this.nodesRepository.create({ - ...node, - nodeMapId: mapId, - }) - - try { - return await this.nodesRepository.save(newNode) - } catch (error) { - // Check if it's a constraint error we can handle - if (this.isConstraintError(error) && error instanceof QueryFailedError) { - this.logger.warn( - `addNode(): Constraint violation when adding node ${newNode.id} - likely invalid parent reference` - ) - return await this.mapConstraintErrorToValidationResponse( - error, - newNode, - mapId - ) - } - - // For other errors, log and throw - this.logger.error( - `${error instanceof Error ? error.constructor.name : 'Unknown'} addNode(): Failed to add node ${newNode.id}: ${error instanceof Error ? error.message : String(error)}` - ) - throw error - } - } - - async addNodesFromClient( - mapId: string, - clientNodes: IMmpClientNode[] - ): Promise<(MmpNode | ValidationErrorResponse)[]> { - const mmpNodes = clientNodes.map((x) => mapClientNodeToMmpNode(x, mapId)) - return await this.addNodes(mapId, mmpNodes) - } - - /** - * Adds multiple nodes to a map in a single atomic transaction - * If any node fails validation or save, the entire operation is rolled back - * Relies on database FK constraints for referential integrity - * @param mapId The map ID to add nodes to - * @param nodes Array of nodes to add - * @returns Array of created nodes on success, or array with single ValidationErrorResponse on failure + * Bulk-inserts nodes into a map within a single transaction. Used by the + * REST duplicate-map endpoint, where the source nodes are already valid + * MmpNode entities. Yjs persistence has its own path that does not rely on + * this method. */ - async addNodes( - mapId: string, - nodes: Partial[] - ): Promise<(MmpNode | ValidationErrorResponse)[]> { + async addNodes(mapId: string, nodes: Partial[]): Promise { if (!mapId || nodes.length === 0) { this.logger.warn( `Required arguments mapId or nodes not supplied to addNodes()` @@ -269,12 +111,6 @@ export class MapsService { try { await queryRunner.startTransaction() - const businessRuleValidation = await this.validateBusinessRules(nodes) - if (!businessRuleValidation.valid) { - await queryRunner.rollbackTransaction() - return [businessRuleValidation.error!] - } - const nodesToCreate = await this.filterOutExistingNodes( queryRunner, mapId, @@ -291,81 +127,15 @@ export class MapsService { return createdNodes } catch (error) { await this.rollbackTransactionSafely(queryRunner) - return await this.handleAddNodesError(error, nodes, mapId) + this.logger.error( + `addNodes(): Failed to add nodes to map ${mapId}: ${error instanceof Error ? error.message : String(error)}` + ) + throw error } finally { await this.releaseQueryRunnerSafely(queryRunner) } } - /** - * Creates a validation error response directly - */ - private buildValidationError( - code: ValidationErrorResponse['code'], - message: string - ): ValidationErrorResponse { - return { - success: false, - errorType: 'validation', - code, - message: `VALIDATION_ERROR.${message}`, - } - } - - /** - * Validates business rules that the database cannot enforce - * Does not check FK constraints - those are validated by the database - * @param nodes Array of nodes to validate - * @returns Validation result with error if invalid - */ - private async validateBusinessRules(nodes: Partial[]): Promise<{ - valid: boolean - error?: ValidationErrorResponse - }> { - for (const node of nodes) { - // Validate required properties exist - if (!this.hasRequiredNodeProperties(node)) { - return { - valid: false, - error: this.buildValidationError( - 'MISSING_REQUIRED_FIELD', - 'MISSING_REQUIRED_FIELD' - ), - } - } - - const mmpNode = node as MmpNode - - // Business rule: root and detached nodes cannot have parents - if (!this.validateNodeParentConstraints(mmpNode)) { - return { - valid: false, - error: this.buildValidationError( - 'CONSTRAINT_VIOLATION', - 'CONSTRAINT_VIOLATION' - ), - } - } - } - - return { valid: true } - } - - /** - * Checks if a node has the minimum required property (id) for database save - * Other required properties (root, detached, coordinatesX, coordinatesY, nodeMapId) are either: - * - Set by database defaults (@Column({ default: ... })) - * - Set explicitly before save (nodeMapId) - * - Validated by TypeORM's @IsDefined() decorators during entity creation - */ - private hasRequiredNodeProperties(node: Partial): boolean { - return !!node.id - } - - /** - * Safely rolls back a transaction with error handling - * Only attempts rollback if transaction is active - */ private async rollbackTransactionSafely( queryRunner: QueryRunner ): Promise { @@ -380,10 +150,6 @@ export class MapsService { } } - /** - * Safely releases a query runner with error handling - * Only attempts release if not already released - */ private async releaseQueryRunnerSafely( queryRunner: QueryRunner ): Promise { @@ -398,50 +164,12 @@ export class MapsService { } } - /** - * Handles errors during addNodes operation - * Safely handles constraint violations and re-throws unexpected errors - */ - private async handleAddNodesError( - error: unknown, - nodes: Partial[], - mapId: string - ): Promise<(MmpNode | ValidationErrorResponse)[]> { - // Handle constraint errors from database - if (this.isConstraintError(error) && error instanceof QueryFailedError) { - this.logger.warn( - `addNodes(): Constraint violation when adding nodes to map ${mapId}` - ) - // Use first node if available, otherwise use empty object as fallback - const nodeForError = (nodes[0] ?? {}) as MmpNode - return [ - await this.mapConstraintErrorToValidationResponse( - error, - nodeForError, - mapId - ), - ] - } - - // Re-throw unexpected errors - this.logger.error( - `${error instanceof Error ? error.constructor.name : 'Unknown'} addNodes(): Failed to add nodes to map ${mapId}: ${error instanceof Error ? error.message : String(error)}` - ) - throw error - } - - /** - * Filters out nodes that already exist in the database using a single bulk query - * Optimized to prevent N+1 query problem - */ private async filterOutExistingNodes( queryRunner: QueryRunner, mapId: string, nodes: MmpNode[] ): Promise { - if (nodes.length === 0) { - return [] - } + if (nodes.length === 0) return [] const nodeIds = nodes.map((n) => n.id) const existingNodes = await queryRunner.manager.find(MmpNode, { @@ -456,18 +184,12 @@ export class MapsService { return nodes.filter((node) => !existingNodeIds.has(node.id)) } - /** - * Saves all nodes within a transaction using bulk insert for performance - * All nodes saved in single database operation - */ private async saveAllNodesInTransaction( queryRunner: QueryRunner, mapId: string, nodes: MmpNode[] ): Promise { - if (nodes.length === 0) { - return [] - } + if (nodes.length === 0) return [] const newNodes = nodes.map((node) => queryRunner.manager.create(MmpNode, { @@ -487,143 +209,6 @@ export class MapsService { .getMany() } - async existsNode( - mapId: string, - nodeId: string, - queryRunner?: QueryRunner - ): Promise { - if (!mapId || !nodeId) return false - - // Validate UUID format before querying to avoid database errors - if (!uuidValidate(nodeId)) { - this.logger.warn( - `existsNode(): Invalid UUID format for nodeId: ${nodeId}` - ) - return false - } - - if (queryRunner) { - return await queryRunner.manager.exists(MmpNode, { - where: { id: nodeId, nodeMapId: mapId }, - }) - } else { - return await this.nodesRepository.exists({ - where: { id: nodeId, nodeMapId: mapId }, - }) - } - } - - private async validateNodeParentExists( - mapId: string, - nodeId: string, - updatedNodeData: Partial, - context: string - ): Promise { - if (!shouldValidateParent(updatedNodeData)) { - return true - } - - const parentExists = await this.existsNode( - mapId, - updatedNodeData.nodeParentId! - ) - - if (!parentExists) { - this.logger.warn( - createParentNotFoundWarning( - nodeId, - updatedNodeData.nodeParentId!, - mapId, - context - ) - ) - } - - return parentExists - } - - private async saveUpdatedNode( - existingNode: MmpNode, - updatedNodeData: Partial - ): Promise { - return await this.nodesRepository.save({ - ...existingNode, - ...updatedNodeData, - lastModified: new Date(), - }) - } - - async updateNode( - mapId: string, - clientNode: IMmpClientNode - ): Promise { - const existingNode = await this.nodesRepository.findOne({ - where: { nodeMapId: mapId, id: clientNode.id }, - }) - - if (!existingNode) { - this.logger.warn( - `updateNode(): Existing node on server for given client node ${clientNode.id} has not been found.` - ) - return - } - - const updatedNodeData = mapClientNodeToMmpNode(clientNode, mapId) - - const parentIsValid = await this.validateNodeParentExists( - mapId, - clientNode.id, - updatedNodeData, - 'updateNode()' - ) - - if (!parentIsValid) { - // Return validation error - return await this.mapConstraintErrorToValidationResponse( - new QueryFailedError('', [], new Error('INVALID_PARENT')), - { ...existingNode, ...updatedNodeData }, - mapId - ) - } - - try { - return await this.saveUpdatedNode(existingNode, updatedNodeData) - } catch (error) { - // Check if it's a constraint error we can handle - if (this.isConstraintError(error) && error instanceof QueryFailedError) { - this.logger.warn( - `updateNode(): Constraint violation when updating node ${existingNode.id}` - ) - return await this.mapConstraintErrorToValidationResponse( - error, - { ...existingNode, ...updatedNodeData }, - mapId - ) - } - - this.logger.error( - `${error instanceof Error ? error.constructor.name : 'Unknown'} updateNode(): Failed to update node ${existingNode.id}: ${error instanceof Error ? error.message : String(error)}` - ) - throw error - } - } - - async removeNode( - clientNode: IMmpClientNode, - mapId: string - ): Promise { - const existingNode = await this.nodesRepository.findOneBy({ - id: clientNode.id, - nodeMapId: mapId, - }) - - if (!existingNode) { - return - } - - return this.nodesRepository.remove(existingNode) - } - private async createRootNodeForMap( rootNode: IMmpClientNodeBasics, mapId: string @@ -659,29 +244,28 @@ export class MapsService { } /** - * Updates map by replacing all nodes in a transaction - * Ensures atomicity - either all changes succeed or none do + * Replaces all nodes in a map atomically. Used by REST import flows. */ async updateMap(clientMap: IMmpClientMap): Promise { const queryRunner = await this.createQueryRunner() try { - await this.startMapUpdateTransaction(queryRunner) - await this.deleteExistingNodes(queryRunner, clientMap.uuid) - await this.addValidatedNodes(queryRunner, clientMap) - await this.commitMapTransaction(queryRunner) + await queryRunner.startTransaction() + await queryRunner.manager.delete(MmpNode, { nodeMapId: clientMap.uuid }) + await this.saveValidNodes(queryRunner, clientMap) + await queryRunner.commitTransaction() return this.findMap(clientMap.uuid) } catch (error) { - await this.rollbackMapTransaction(queryRunner, clientMap.uuid, error) + await this.rollbackTransactionSafely(queryRunner) + this.logger.error( + `updateMap(): Failed to update map ${clientMap.uuid}: ${error instanceof Error ? error.message : String(error)}` + ) throw error } finally { - await queryRunner.release() + await this.releaseQueryRunnerSafely(queryRunner) } } - /** - * Creates and connects a query runner for transaction management - */ private async createQueryRunner() { const queryRunner = this.nodesRepository.manager.connection.createQueryRunner() @@ -689,271 +273,22 @@ export class MapsService { return queryRunner } - /** - * Starts a database transaction for map updates - */ - private async startMapUpdateTransaction( - queryRunner: QueryRunner - ): Promise { - await queryRunner.startTransaction() - } - - /** - * Deletes all existing nodes for a map - * Prevents multiple root nodes in the updated map - */ - private async deleteExistingNodes( - queryRunner: QueryRunner, - mapId: string - ): Promise { - await queryRunner.manager.delete(MmpNode, { nodeMapId: mapId }) - } - - /** - * Adds new nodes from client map with validation - * Validates parent relationships and order before adding each node - */ - private async addValidatedNodes( - queryRunner: QueryRunner, - clientMap: IMmpClientMap - ): Promise { - const mmpNodes = this.convertClientNodesToMmpNodes(clientMap) - await this.saveValidNodes(queryRunner, mmpNodes, clientMap.uuid) - } - - /** - * Converts client nodes to MmpNode format - */ - private convertClientNodesToMmpNodes( - clientMap: IMmpClientMap - ): Partial[] { - return clientMap.data.map((x) => mapClientNodeToMmpNode(x, clientMap.uuid)) - } - - /** - * Saves valid nodes sequentially to avoid race conditions - */ private async saveValidNodes( queryRunner: QueryRunner, - mmpNodes: Partial[], - mapId: string - ): Promise { - for (const node of mmpNodes) { - await this.saveNodeIfValid(queryRunner, node as MmpNode, mapId) - } - } - - /** - * Saves a single node if it passes validation within the transaction context - */ - private async saveNodeIfValid( - queryRunner: QueryRunner, - node: MmpNode, - mapId: string - ): Promise { - const isValid = await this.validatesNodeParentForNode( - mapId, - node, - queryRunner - ) - if (!isValid) return - - const newNode = queryRunner.manager.create(MmpNode, { - ...node, - nodeMapId: mapId, - }) - await queryRunner.manager.save(newNode) - } - - /** - * Commits the map update transaction - */ - private async commitMapTransaction(queryRunner: QueryRunner): Promise { - await queryRunner.commitTransaction() - } - - /** - * Rolls back transaction and logs error - */ - private async rollbackMapTransaction( - queryRunner: QueryRunner, - mapId: string, - error: unknown - ): Promise { - await queryRunner.rollbackTransaction() - this.logger.error( - `updateMap(): Failed to update map ${mapId}: ${error instanceof Error ? error.message : String(error)}` - ) - } - - /** - * Updates map by applying incremental changes (diff) in a single atomic transaction - * If any change (add/update/delete) fails, the entire operation is rolled back - * Ensures atomicity - either all changes succeed or none do - * @param mapId The map ID to update - * @param diff The diff containing added, updated, and deleted nodes - */ - async updateMapByDiff(mapId: string, diff: IMmpClientMapDiff): Promise { - const queryRunner = await this.createQueryRunner() - - try { - await queryRunner.startTransaction() - - // Process deleted nodes first to avoid FK constraint issues - if (diff.deleted && Object.keys(diff.deleted).length > 0) { - await this.applyDeletedChangesInTransaction( - queryRunner, - mapId, - diff.deleted - ) - } - - // Process added nodes - if (diff.added && Object.keys(diff.added).length > 0) { - await this.applyAddedChangesInTransaction( - queryRunner, - mapId, - diff.added - ) - } - - // Process updated nodes - if (diff.updated && Object.keys(diff.updated).length > 0) { - await this.applyUpdatedChangesInTransaction( - queryRunner, - mapId, - diff.updated - ) - } - - await queryRunner.commitTransaction() - } catch (error) { - await this.rollbackTransactionSafely(queryRunner) - this.logger.error( - `updateMapByDiff(): Failed to apply changes to map ${mapId}: ${error instanceof Error ? error.message : String(error)}` - ) - throw error - } finally { - await this.releaseQueryRunnerSafely(queryRunner) - } - } - - /** - * Applies deleted changes within a transaction - */ - private async applyDeletedChangesInTransaction( - queryRunner: QueryRunner, - mapId: string, - deletedNodes: IMmpClientSnapshotChanges - ): Promise { - const nodeIds = Object.keys(deletedNodes) - - // Delete all nodes in a single query - if (nodeIds.length > 0) { - await queryRunner.manager.delete(MmpNode, { - id: In(nodeIds), - nodeMapId: mapId, - }) - } - } - - /** - * Applies added changes within a transaction - */ - private async applyAddedChangesInTransaction( - queryRunner: QueryRunner, - mapId: string, - addedNodes: IMmpClientSnapshotChanges + clientMap: IMmpClientMap ): Promise { - const clientNodes = Object.values(addedNodes) as IMmpClientNode[] - const mmpNodes = clientNodes.map((x) => mapClientNodeToMmpNode(x, mapId)) - - // Validate business rules for all nodes - const businessRuleValidation = await this.validateBusinessRules(mmpNodes) - if (!businessRuleValidation.valid) { - throw new Error( - `Business rule validation failed: ${businessRuleValidation.error?.message}` - ) - } - - // Filter out existing nodes and save new ones - const nodesToCreate = await this.filterOutExistingNodes( - queryRunner, - mapId, - mmpNodes as MmpNode[] + const mmpNodes = clientMap.data.map((x) => + mapClientNodeToMmpNode(x, clientMap.uuid) ) - - if (nodesToCreate.length > 0) { - await this.saveAllNodesInTransaction(queryRunner, mapId, nodesToCreate) - } - } - - /** - * Applies updated changes within a transaction - * - * Error Handling Strategy: - * - Missing nodes (not found in database) are skipped with a warning (lenient) - * Rationale: Node may have been deleted by concurrent operation, acceptable for optimistic updates - * - Invalid parent references throw errors and roll back transaction (strict) - * Rationale: Invalid parents indicate data integrity violation or client state inconsistency - * that should fail the entire atomic operation - * - * This differential handling balances robustness (handling race conditions gracefully) - * with data integrity (preventing orphaned or corrupted node relationships) - */ - private async applyUpdatedChangesInTransaction( - queryRunner: QueryRunner, - mapId: string, - updatedNodes: IMmpClientSnapshotChanges - ): Promise { - // Process updates sequentially to maintain parent-child relationship order - for (const [nodeId, clientNodeData] of Object.entries(updatedNodes)) { - if (!clientNodeData) continue - - const serverNode = await queryRunner.manager.findOne(MmpNode, { - where: { nodeMapId: mapId, id: nodeId }, + for (const node of mmpNodes) { + const newNode = queryRunner.manager.create(MmpNode, { + ...(node as MmpNode), + nodeMapId: clientMap.uuid, }) - - if (!serverNode) { - this.logger.warn( - `updateMapByDiff(): Node ${nodeId} not found for update, skipping` - ) - continue - } - - const mergedNode = mergeClientNodeIntoMmpNode(clientNodeData, serverNode) - - // Validate parent exists if specified - if (shouldValidateParent(mergedNode)) { - const parentExists = await queryRunner.manager.exists(MmpNode, { - where: { - id: mergedNode.nodeParentId!, - nodeMapId: mapId, - }, - }) - - if (!parentExists) { - throw new Error( - `Invalid parent reference: Parent node ${mergedNode.nodeParentId} does not exist for node ${nodeId}` - ) - } - } - - // Apply changes and save - Object.assign(serverNode, mergedNode, { lastModified: new Date() }) - await queryRunner.manager.save(serverNode) + await queryRunner.manager.save(newNode) } } - async updateMapOptions( - mapId: string, - clientOptions: IMmpClientMapOptions - ): Promise { - await this.mapsRepository.update(mapId, { options: clientOptions }) - - return await this.mapsRepository.findOne({ where: { id: mapId } }) - } - async getDeletedAt( map: MmpMap, afterDays: number @@ -965,7 +300,6 @@ export class MapsService { return } - // get newest node of this map: const newestNodeQuery = this.nodesRepository .createQueryBuilder('node') .select('max(node.lastModified) AS lastModified') @@ -986,7 +320,6 @@ export class MapsService { } calculcateDeletedAt(lastModified: Date, afterDays: number): Date { - // dont modify original input as this might be used somewhere else const copyDate: Date = new Date(lastModified.getTime()) copyDate.setDate(copyDate.getDate() + afterDays) return copyDate @@ -1002,7 +335,6 @@ export class MapsService { .select('map.id') .leftJoin( (qb) => - // subquery to get the newest node and its lastModified date of this map: qb .select([ 'node.nodeMapId AS nodeMapId', @@ -1032,24 +364,10 @@ export class MapsService { ).affected } - // no maps found to be deleted: return 0 } async deleteMap(uuid: string): Promise { await this.mapsRepository.delete({ id: uuid }) } - - async validatesNodeParentForNode( - mapId: string, - node: MmpNode, - queryRunner?: QueryRunner - ): Promise { - return ( - node.root || - node.detached || - (!!node.nodeParentId && - (await this.existsNode(mapId, node.nodeParentId, queryRunner))) - ) - } } diff --git a/teammapper-backend/src/map/types.ts b/teammapper-backend/src/map/types.ts index 55c799a0..a356cc84 100644 --- a/teammapper-backend/src/map/types.ts +++ b/teammapper-backend/src/map/types.ts @@ -1,5 +1,5 @@ import type { IMmpClientNode as _IMmpClientNode } from './schemas/node.schema' -import type { IMmpClientMapOptions as _IMmpClientMapOptions } from './schemas/gateway.schema' +import type { IMmpClientMapOptions as _IMmpClientMapOptions } from './schemas/maps.schema' // Entity/node types — derived from valibot schemas export type { @@ -10,28 +10,14 @@ export type { IMmpClientNode, } from './schemas/node.schema' -// Gateway/request types — derived from valibot schemas +// Maps controller types — derived from valibot schemas export type { - IMmpClientJoinRequest, - IMmpClientEditingRequest, - IMmpClientNodeRequest, - IMmpClientNodeAddRequest, - IMmpClientNodeSelectionRequest, - IMmpClientUpdateMapOptionsRequest, IMmpClientMapOptions, - IMmpClientMapRequest, - IMmpClientUndoRedoRequest, + IMmpClientMapCreateRequest, IMmpClientDeleteRequest, - IMmpClientSnapshotChanges, - IMmpClientMapDiff, -} from './schemas/gateway.schema' - -// Maps controller types — derived from valibot schemas -export type { IMmpClientMapCreateRequest } from './schemas/maps.schema' +} from './schemas/maps.schema' -// IMmpClientMap is a domain type used across services/mappers with required fields. -// The WebSocket MapSchema is intentionally permissive for validation; this interface -// is the canonical type for internal use. +// IMmpClientMap is the canonical domain type used across services/mappers. export interface IMmpClientMap { uuid: string lastModified: Date | null @@ -66,80 +52,6 @@ export interface IMmpClientPrivateMap { modificationSecret: string | null } -export interface IClientCache { - [clientId: string]: string -} - -// Operation tracking types for optimistic updates -export type OperationType = 'create' | 'update' | 'delete' | 'updateProperty' - -export type OperationStatus = 'pending' | 'confirmed' | 'rejected' | 'timedout' - -// Error response types for structured error handling -export interface BaseErrorResponse { - /** Success indicator (always false for errors) */ - success: false - - /** Type of error for classification */ - errorType: 'validation' | 'critical' - - /** Machine-readable error code */ - code: string - - /** i18n key for user-facing message */ - message: string - - /** Optional additional context (not shown to user) */ - context?: Record -} - -export interface ValidationErrorResponse extends BaseErrorResponse { - errorType: 'validation' - - /** Specific validation error codes */ - code: - | 'INVALID_PARENT' - | 'CONSTRAINT_VIOLATION' - | 'MISSING_REQUIRED_FIELD' - | 'CIRCULAR_REFERENCE' - | 'DUPLICATE_NODE' - - /** Full map state for client synchronization after errors */ - fullMapState?: IMmpClientMap -} - -export interface CriticalErrorResponse extends BaseErrorResponse { - errorType: 'critical' - - /** Specific critical error codes */ - code: - | 'SERVER_ERROR' - | 'NETWORK_TIMEOUT' - | 'AUTH_FAILED' - | 'MALFORMED_REQUEST' - | 'RATE_LIMIT_EXCEEDED' - - /** Optional retry-after value for rate limiting */ - retryAfter?: number - - /** Full map state for client synchronization after errors */ - fullMapState?: IMmpClientMap -} - -export interface SuccessResponse { - /** Success indicator */ - success: true - - /** Result data from the operation */ - data: T - - /** Optional metadata */ - meta?: { - timestamp: number - operationId?: string - } -} - export interface Request { cookies: { access_token?: string @@ -147,8 +59,3 @@ export interface Request { } pid: string | undefined } - -export type OperationResponse = - | SuccessResponse - | ValidationErrorResponse - | CriticalErrorResponse diff --git a/teammapper-backend/src/map/utils/clientServerMapping.spec.ts b/teammapper-backend/src/map/utils/clientServerMapping.spec.ts index 6c0d82b0..40d6bded 100644 --- a/teammapper-backend/src/map/utils/clientServerMapping.spec.ts +++ b/teammapper-backend/src/map/utils/clientServerMapping.spec.ts @@ -1,9 +1,7 @@ import { mapClientNodeToMmpNode, - mergeClientNodeIntoMmpNode, mapClientBasicNodeToMmpRootNode, } from './clientServerMapping' -import { MmpNode } from '../entities/mmpNode.entity' import { IMmpClientNode } from '../types' const buildClientNode = ( @@ -24,33 +22,6 @@ const buildClientNode = ( ...overrides, }) -const buildServerNode = (overrides: Partial = {}): MmpNode => { - const node = new MmpNode() - Object.assign(node, { - id: 'server-uuid', - name: 'Server Node', - colorsName: '#000000', - colorsBackground: '#ffffff', - colorsBranch: '#333333', - coordinatesX: 0, - coordinatesY: 0, - fontSize: 20, - fontStyle: 'normal', - fontWeight: 'normal', - imageSrc: '', - imageSize: 0, - linkHref: '', - k: 1, - locked: false, - detached: false, - root: false, - nodeMapId: 'map-uuid', - nodeParentId: 'parent-uuid', - ...overrides, - }) - return node -} - describe('mapClientNodeToMmpNode sanitization', () => { it('should strip HTML from name', () => { const client = buildClientNode({ @@ -105,37 +76,6 @@ describe('mapClientNodeToMmpNode sanitization', () => { }) }) -describe('mergeClientNodeIntoMmpNode sanitization', () => { - it('should sanitize name when client provides it', () => { - const client = { name: 'Clean' } - const server = buildServerNode() - const result = mergeClientNodeIntoMmpNode(client, server) - expect(result.name).toBe('Clean') - }) - - it('should keep server name when client does not provide it', () => { - const server = buildServerNode({ name: 'Server Name' }) - const result = mergeClientNodeIntoMmpNode({}, server) - expect(result.name).toBe('Server Name') - }) - - it('should sanitize image src when client provides it', () => { - const client = { image: { src: 'javascript:alert(1)', size: 60 } } - const server = buildServerNode() - const result = mergeClientNodeIntoMmpNode(client, server) - expect(result.imageSrc).toBe('') - }) - - it('should sanitize link href when client provides it', () => { - const client = { - link: { href: 'data:text/html,' }, - } - const server = buildServerNode() - const result = mergeClientNodeIntoMmpNode(client, server) - expect(result.linkHref).toBe('') - }) -}) - describe('mapClientBasicNodeToMmpRootNode sanitization', () => { it('should sanitize name with HTML', () => { const basics = { diff --git a/teammapper-backend/src/map/utils/clientServerMapping.ts b/teammapper-backend/src/map/utils/clientServerMapping.ts index fead917d..63bbc153 100644 --- a/teammapper-backend/src/map/utils/clientServerMapping.ts +++ b/teammapper-backend/src/map/utils/clientServerMapping.ts @@ -56,33 +56,6 @@ const mapMmpMapToClient = ( } } -const mergeClientNodeIntoMmpNode = ( - clientNode: Partial, - serverNode: MmpNode -): Partial => - sanitizeNodeFields({ - id: clientNode?.id ?? serverNode.id, - colorsBackground: - clientNode?.colors?.background ?? serverNode.colorsBackground, - colorsBranch: clientNode?.colors?.branch ?? serverNode.colorsBranch, - colorsName: clientNode?.colors?.name ?? serverNode.colorsName, - coordinatesX: clientNode?.coordinates?.x ?? serverNode.coordinatesX, - coordinatesY: clientNode?.coordinates?.y ?? serverNode.coordinatesY, - fontSize: clientNode?.font?.size ?? serverNode.fontSize, - fontStyle: clientNode?.font?.style ?? serverNode.fontStyle, - fontWeight: clientNode?.font?.weight ?? serverNode.fontWeight, - imageSrc: clientNode?.image?.src ?? serverNode.imageSrc, - imageSize: clientNode?.image?.size ?? serverNode.imageSize, - k: clientNode?.k ?? serverNode.k, - linkHref: clientNode?.link?.href ?? serverNode.linkHref, - locked: clientNode?.locked ?? serverNode.locked, - detached: clientNode?.detached ?? serverNode.detached, - name: clientNode?.name !== undefined ? clientNode.name : serverNode.name, - nodeParentId: clientNode?.parent || serverNode.nodeParentId || undefined, - root: clientNode?.isRoot ?? serverNode.root, - nodeMapId: serverNode.nodeMapId, - }) - const mapClientNodeToMmpNode = ( clientNode: IMmpClientNode, mapId: string @@ -146,5 +119,4 @@ export { mapClientNodeToMmpNode, mapClientBasicNodeToMmpRootNode, mapMmpMapToClient, - mergeClientNodeIntoMmpNode, } diff --git a/teammapper-backend/src/map/utils/nodeValidation.spec.ts b/teammapper-backend/src/map/utils/nodeValidation.spec.ts deleted file mode 100644 index 992d9f79..00000000 --- a/teammapper-backend/src/map/utils/nodeValidation.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { MmpNode } from '../entities/mmpNode.entity' -import { - shouldValidateParent, - createParentNotFoundWarning, -} from './nodeValidation' - -describe('nodeValidation', () => { - describe('shouldValidateParent', () => { - it('returns true when nodeParentId is set and node is not root or detached', () => { - const node: Partial = { - nodeParentId: 'parent-id', - root: false, - detached: false, - } - - expect(shouldValidateParent(node)).toBe(true) - }) - - it('returns false when nodeParentId is not set', () => { - const node: Partial = { - nodeParentId: undefined, - root: false, - detached: false, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - - it('returns false when node is root', () => { - const node: Partial = { - nodeParentId: 'parent-id', - root: true, - detached: false, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - - it('returns false when node is detached', () => { - const node: Partial = { - nodeParentId: 'parent-id', - root: false, - detached: true, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - - it('returns false when node is both root and detached', () => { - const node: Partial = { - nodeParentId: 'parent-id', - root: true, - detached: true, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - - it('returns false when nodeParentId is null', () => { - const node: Partial = { - nodeParentId: null as unknown as string, - root: false, - detached: false, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - - it('returns false when nodeParentId is empty string', () => { - const node: Partial = { - nodeParentId: '', - root: false, - detached: false, - } - - expect(shouldValidateParent(node)).toBe(false) - }) - }) - - describe('createParentNotFoundWarning', () => { - it('creates a properly formatted warning message', () => { - const warning = createParentNotFoundWarning( - 'node-123', - 'parent-456', - 'map-789', - 'updateNode()' - ) - - expect(warning).toBe( - 'updateNode(): Cannot update node node-123 - parent parent-456 does not exist in map map-789' - ) - }) - - it('includes the context at the beginning of the message', () => { - const warning = createParentNotFoundWarning( - 'node-abc', - 'parent-def', - 'map-ghi', - 'diffUpdatedCallback()' - ) - - expect(warning).toContain('diffUpdatedCallback():') - }) - - it('includes all provided IDs in the message', () => { - const nodeId = 'node-test-1' - const parentId = 'parent-test-2' - const mapId = 'map-test-3' - - const warning = createParentNotFoundWarning( - nodeId, - parentId, - mapId, - 'testContext()' - ) - - expect(warning).toContain(nodeId) - expect(warning).toContain(parentId) - expect(warning).toContain(mapId) - }) - }) -}) diff --git a/teammapper-backend/src/map/utils/nodeValidation.ts b/teammapper-backend/src/map/utils/nodeValidation.ts deleted file mode 100644 index d1df81d1..00000000 --- a/teammapper-backend/src/map/utils/nodeValidation.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { MmpNode } from '../entities/mmpNode.entity' - -export const shouldValidateParent = (node: Partial): boolean => - !!node.nodeParentId && !node.root && !node.detached - -export const createParentNotFoundWarning = ( - nodeId: string, - parentId: string, - mapId: string, - context: string -): string => - `${context}: Cannot update node ${nodeId} - parent ${parentId} does not exist in map ${mapId}` diff --git a/teammapper-backend/src/settings/settings.service.spec.ts b/teammapper-backend/src/settings/settings.service.spec.ts index adfe936d..73565788 100644 --- a/teammapper-backend/src/settings/settings.service.spec.ts +++ b/teammapper-backend/src/settings/settings.service.spec.ts @@ -16,7 +16,7 @@ const defaultSettings: Settings = { pictogramApiUrl: 'https://api.example.com', pictogramStaticUrl: 'https://static.example.com', }, - featureFlags: { pictograms: true, ai: false, yjs: false }, + featureFlags: { pictograms: true, ai: false }, }, userSettings: { general: { language: 'en' }, @@ -51,7 +51,6 @@ describe('SettingsService', () => { beforeEach(() => { jest.clearAllMocks() - ;(configService.isYjsEnabled as jest.Mock).mockReturnValue(false) ;(configService.isAiEnabled as jest.Mock).mockReturnValue(false) mockedFs.readFileSync.mockReturnValue(JSON.stringify(defaultSettings)) mockedFs.existsSync.mockReturnValue(false) @@ -132,40 +131,22 @@ describe('SettingsService', () => { expect(settings.systemSettings.featureFlags.ai).toBe(false) }) - it('should set yjs flag to true when YJS_ENABLED is true', () => { - ;(configService.isYjsEnabled as jest.Mock).mockReturnValue(true) - - const settings = service.getSettings() - - expect(settings.systemSettings.featureFlags.yjs).toBe(true) - }) - - it('should set yjs flag to false when YJS_ENABLED is false', () => { - ;(configService.isYjsEnabled as jest.Mock).mockReturnValue(false) - - const settings = service.getSettings() - - expect(settings.systemSettings.featureFlags.yjs).toBe(false) - }) - it('should override file-based feature flags with config service values', () => { const settingsWithFlags = { ...defaultSettings, systemSettings: { ...defaultSettings.systemSettings, - featureFlags: { pictograms: true, ai: true, yjs: true }, + featureFlags: { pictograms: true, ai: true }, }, } mockedFs.readFileSync.mockReturnValue(JSON.stringify(settingsWithFlags)) ;(configService.isAiEnabled as jest.Mock).mockReturnValue(false) - ;(configService.isYjsEnabled as jest.Mock).mockReturnValue(false) const settings = service.getSettings() expect(settings.systemSettings.featureFlags).toEqual({ pictograms: true, ai: false, - yjs: false, }) }) }) diff --git a/teammapper-backend/src/settings/settings.service.ts b/teammapper-backend/src/settings/settings.service.ts index e5f12d3d..445275d3 100644 --- a/teammapper-backend/src/settings/settings.service.ts +++ b/teammapper-backend/src/settings/settings.service.ts @@ -38,7 +38,6 @@ export class SettingsService { } const settings = deepmerge(defaultSettings, overrideSettings) as Settings - settings.systemSettings.featureFlags.yjs = configService.isYjsEnabled() settings.systemSettings.featureFlags.ai = configService.isAiEnabled() return settings } diff --git a/teammapper-backend/src/settings/settings.types.ts b/teammapper-backend/src/settings/settings.types.ts index 16df5b83..43113f97 100644 --- a/teammapper-backend/src/settings/settings.types.ts +++ b/teammapper-backend/src/settings/settings.types.ts @@ -42,7 +42,6 @@ export interface MapOptions { export interface FeatureFlags { pictograms: boolean ai: boolean - yjs: boolean } export interface Settings { diff --git a/teammapper-backend/test/app.e2e-spec.ts b/teammapper-backend/test/app.e2e-spec.ts deleted file mode 100644 index cd3c7c52..00000000 --- a/teammapper-backend/test/app.e2e-spec.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing' -import { INestApplication } from '@nestjs/common' -import request from 'supertest' -import { MmpMap } from 'src/map/entities/mmpMap.entity' -import { MmpNode } from 'src/map/entities/mmpNode.entity' -import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm' -import { Repository } from 'typeorm' -import { ConfigModule } from '@nestjs/config' -import { io, Socket } from 'socket.io-client' -import { IMmpClientMap, OperationResponse, IMmpClientNode } from 'src/map/types' -import { createTestConfiguration, destroyWorkerDatabase } from './db' -import AppModule from '../src/app.module' - -describe('AppController (e2e)', () => { - let app: INestApplication - let server: ReturnType - let nodesRepo: Repository - let mapRepo: Repository - - beforeAll(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigModule, - TypeOrmModule.forRoot( - await createTestConfiguration(process.env.JEST_WORKER_ID || '') - ), - AppModule, - ], - }).compile() - - nodesRepo = moduleFixture.get>( - getRepositoryToken(MmpNode) - ) - mapRepo = moduleFixture.get>(getRepositoryToken(MmpMap)) - app = moduleFixture.createNestApplication() - server = app.getHttpServer() - await app.init() - await app.listen(3001) - }) - - afterAll(async () => { - // close connection: - await destroyWorkerDatabase( - mapRepo.manager.connection, - process.env.JEST_WORKER_ID || '' - ) - await app.close() - }) - - it('/api/maps/:id(GET)', async () => { - const map: MmpMap = await mapRepo.save({ name: 'test' }) - await nodesRepo.save({ - nodeMapId: map.id, - coordinatesX: 3, - coordinatesY: 1, - }) - const response: request.Response = await request(server).get( - `/api/maps/${map.id}` - ) - expect(response.body.uuid).toEqual(map.id) - }) - - describe('WebSocketGateway', () => { - let socket: Socket - - beforeEach(async () => { - socket = io('http://localhost:3001') - }) - - afterEach(async () => { - socket.close() - }) - - it('lets a user join a map session', (done) => { - mapRepo.save({}).then((map) => { - socket.emit( - 'join', - { mapId: map.id, color: '#FFFFFF' }, - (result: IMmpClientMap) => { - expect(result).toBeInstanceOf(Object) - done() - } - ) - }) - }) - - it('lets a user update a map', async () => { - const modificationSecret = crypto.randomUUID() - - const oldMap = await mapRepo.save({ - modificationSecret: modificationSecret, - }) - - const map: IMmpClientMap = { - uuid: oldMap.id, - lastModified: new Date(), - lastAccessed: new Date(), - deleteAfterDays: 30, - deletedAt: new Date(), - options: { fontMaxSize: 10, fontMinSize: 15, fontIncrement: 2 }, - createdAt: new Date(), - data: [ - { - name: 'test', - coordinates: { x: 1.1, y: 2.2 }, - detached: false, - font: { style: null, size: 5, weight: null }, - colors: { branch: null, background: null, name: null }, - image: { size: 60, src: null }, - parent: null, - k: -15.361675447001142, - id: modificationSecret, - link: { href: null }, - locked: false, - isRoot: true, - }, - ], - } - socket.emit( - 'updateMap', - { - map, - mapId: map.uuid, - modificationSecret: modificationSecret, - }, - async () => { - const mapInDb = await mapRepo.findOne({ - where: { id: map.uuid }, - }) - expect(mapInDb?.id).toEqual(map.uuid) - } - ) - }) - - it('responds with success when adding a new node', (done) => { - const mapId = crypto.randomUUID() - const nodeId = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: mapId, - }) - .then((map) => { - socket.emit('join', { mapId: map.id, color: '#FFFFFF' }, () => { - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: nodeId, - name: 'test', - coordinates: { x: 1, y: 2 }, - font: {}, - colors: {}, - link: {}, - isRoot: true, - detached: false, - }, - ], - }, - (result: OperationResponse) => { - // Now we check the acknowledgment response - expect(result.success).toBe(true) - if ('data' in result) { - expect(result.data).toBeDefined() - expect(result.data[0].name).toEqual('test') - } - done() - } - ) - }) - }) - }) - - it('responds with success when updating a node', (done) => { - const mapId = crypto.randomUUID() - const nodeId = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: mapId, - nodes: [ - nodesRepo.create({ - id: nodeId, - coordinatesX: 1, - coordinatesY: 2, - nodeMapId: mapId, - detached: false, - root: true, - }), - ], - }) - .then((map) => { - socket.emit('join', { mapId: map.id, color: '#FFFFFF' }, () => { - socket.emit( - 'updateNode', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - updatedProperty: 'nodeName', - node: { - id: nodeId, - name: 'test', - coordinates: { x: 3, y: 4 }, - font: {}, - colors: {}, - link: {}, - detached: false, - root: true, - }, - }, - (result: OperationResponse) => { - // Now we check the acknowledgment response - expect(result.success).toBe(true) - if ('data' in result) { - expect(result.data).toBeDefined() - expect(result.data.id).toEqual(nodeId) - expect(result.data.coordinates.x).toEqual(3) - } - done() - } - ) - }) - }) - }) - }) -}) diff --git a/teammapper-backend/test/jest-e2e.json b/teammapper-backend/test/jest-e2e.json index 3d56d82e..48fb5a49 100644 --- a/teammapper-backend/test/jest-e2e.json +++ b/teammapper-backend/test/jest-e2e.json @@ -6,6 +6,5 @@ "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "modulePaths": [""], - "setupFiles": ["./test/jest-setup.ts"] + "modulePaths": [""] } diff --git a/teammapper-backend/test/jest-setup.ts b/teammapper-backend/test/jest-setup.ts deleted file mode 100644 index c36664bd..00000000 --- a/teammapper-backend/test/jest-setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Disable Yjs feature flag so that MapModule includes the legacy Socket.io -// MapsGateway and excludes all Yjs providers (YjsGateway, YjsDocManagerService, -// YjsPersistenceService, WsConnectionLimiterService). The e2e tests -// (app.e2e-spec, map-operations-error.e2e-spec) rely on the Socket.io gateway -// for join/addNodes/updateNode events. Unit tests that need Yjs enabled mock -// configService directly. -process.env.YJS_ENABLED = 'false' diff --git a/teammapper-backend/test/map-operations-error.e2e-spec.ts b/teammapper-backend/test/map-operations-error.e2e-spec.ts deleted file mode 100644 index 5366cf7b..00000000 --- a/teammapper-backend/test/map-operations-error.e2e-spec.ts +++ /dev/null @@ -1,278 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing' -import { INestApplication } from '@nestjs/common' -import { MmpMap } from '../src/map/entities/mmpMap.entity' -import { MmpNode } from '../src/map/entities/mmpNode.entity' -import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm' -import { Repository } from 'typeorm' -import { ConfigModule } from '@nestjs/config' -import { io, Socket } from 'socket.io-client' -import { - OperationResponse, - ValidationErrorResponse, - IMmpClientNode, -} from '../src/map/types' -import { createTestConfiguration, destroyWorkerDatabase } from './db' -import AppModule from '../src/app.module' - -// Using IMmpClientNode as the return type for node operations -type ExportNodeProperties = IMmpClientNode - -const crypto = require('crypto') // eslint-disable-line @typescript-eslint/no-require-imports - -describe('Map Operations Error Handling (e2e)', () => { - let app: INestApplication - let nodesRepo: Repository - let mapRepo: Repository - - beforeAll(async () => { - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigModule, - TypeOrmModule.forRoot( - await createTestConfiguration(process.env.JEST_WORKER_ID || '') - ), - AppModule, - ], - }).compile() - - nodesRepo = moduleFixture.get>( - getRepositoryToken(MmpNode) - ) - mapRepo = moduleFixture.get>(getRepositoryToken(MmpMap)) - app = moduleFixture.createNestApplication() - await app.init() - await app.listen(3002) - }) - - afterAll(async () => { - await destroyWorkerDatabase( - mapRepo.manager.connection, - process.env.JEST_WORKER_ID || '' - ) - await app.close() - }) - - describe('addNodes - ValidationErrorResponse structure', () => { - let socket: Socket - - beforeEach(() => { - socket = io('http://localhost:3002') - }) - - afterEach(() => { - socket.close() - }) - - it('returns ValidationErrorResponse for invalid parent', (done) => { - const mapId = crypto.randomUUID() - const rootNodeId = crypto.randomUUID() - const modificationSecret = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: modificationSecret, - }) - .then(() => { - return nodesRepo.save({ - id: rootNodeId, - nodeMapId: mapId, - root: true, - detached: false, - coordinatesX: 0, - coordinatesY: 0, - }) - }) - .then(() => { - socket.emit('join', { mapId, color: '#FFFFFF' }, () => { - socket.emit( - 'addNodes', - { - mapId: mapId, - modificationSecret: modificationSecret, - nodes: [ - { - id: crypto.randomUUID(), - name: 'Invalid Node', - coordinates: { x: 1, y: 2 }, - parent: '99999999-9999-9999-9999-999999999999', - isRoot: false, - detached: false, - }, - ], - }, - (response: OperationResponse) => { - // Socket.io acknowledgment contains ValidationErrorResponse structure - if (response.success === false) { - const validationError = response as ValidationErrorResponse - expect(validationError.errorType).toBe('validation') - expect(validationError.code).toBeDefined() - expect(validationError.message).toBeDefined() - done() - } else { - done.fail('Expected validation error response') - } - } - ) - }) - }) - }) - - it('returns SuccessResponse structure on successful operation', (done) => { - const mapId = crypto.randomUUID() - const modificationSecret = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: modificationSecret, - }) - .then((map) => { - socket.emit('join', { mapId: map.id, color: '#FFFFFF' }, () => { - socket.emit( - 'addNodes', - { - mapId: map.id, - modificationSecret: map.modificationSecret, - nodes: [ - { - id: crypto.randomUUID(), - name: 'Valid Node', - coordinates: { x: 1, y: 2 }, - isRoot: true, - detached: false, - }, - ], - }, - (response: OperationResponse) => { - // Successful operations return SuccessResponse structure - expect(response.success).toBe(true) - if (response.success) { - expect(response.data).toBeDefined() - expect(Array.isArray(response.data)).toBe(true) - } - done() - } - ) - }) - }) - }) - }) - - describe('updateNode - ValidationErrorResponse structure', () => { - let socket: Socket - - beforeEach(() => { - socket = io('http://localhost:3002') - }) - - afterEach(() => { - socket.close() - }) - - it('returns ValidationErrorResponse for invalid parent update', (done) => { - const mapId = crypto.randomUUID() - const nodeId = crypto.randomUUID() - const modificationSecret = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: modificationSecret, - }) - .then(() => { - return nodesRepo.save({ - id: nodeId, - nodeMapId: mapId, - root: true, - detached: false, - coordinatesX: 1, - coordinatesY: 2, - }) - }) - .then(() => { - socket.emit('join', { mapId, color: '#FFFFFF' }, () => { - socket.emit( - 'updateNode', - { - mapId: mapId, - modificationSecret: modificationSecret, - updatedProperty: 'parent', - node: { - id: nodeId, - name: 'Updated Node', - coordinates: { x: 3, y: 4 }, - parent: '99999999-9999-9999-9999-999999999999', - isRoot: false, - detached: false, - }, - }, - (response: OperationResponse) => { - // Socket.io acknowledgment contains ValidationErrorResponse structure - if (response.success === false) { - const validationError = response as ValidationErrorResponse - expect(validationError.errorType).toBe('validation') - // updateNode returns CONSTRAINT_VIOLATION for invalid parent - expect(validationError.code).toBe('CONSTRAINT_VIOLATION') - done() - } else { - done.fail('Expected validation error response') - } - } - ) - }) - }) - }) - - it('returns SuccessResponse structure on successful update', (done) => { - const mapId = crypto.randomUUID() - const nodeId = crypto.randomUUID() - const modificationSecret = crypto.randomUUID() - - mapRepo - .save({ - id: mapId, - modificationSecret: modificationSecret, - }) - .then(() => { - return nodesRepo.save({ - id: nodeId, - nodeMapId: mapId, - root: true, - detached: false, - coordinatesX: 1, - coordinatesY: 2, - }) - }) - .then(() => { - socket.emit('join', { mapId, color: '#FFFFFF' }, () => { - socket.emit( - 'updateNode', - { - mapId: mapId, - modificationSecret: modificationSecret, - updatedProperty: 'name', - node: { - id: nodeId, - name: 'Successfully Updated', - coordinates: { x: 1, y: 2 }, - root: true, - detached: false, - }, - }, - (response: OperationResponse) => { - // Successful operations return SuccessResponse structure - expect(response.success).toBe(true) - if (response.success) { - expect(response.data).toBeDefined() - expect(response.data.id).toBe(nodeId) - expect(response.data.name).toBe('Successfully Updated') - } - done() - } - ) - }) - }) - }) - }) -}) diff --git a/teammapper-backend/tsconfig.json b/teammapper-backend/tsconfig.json index 7a85f7e3..0b5666db 100644 --- a/teammapper-backend/tsconfig.json +++ b/teammapper-backend/tsconfig.json @@ -15,6 +15,6 @@ "strictNullChecks": true, "esModuleInterop": true, "skipLibCheck": true, - "types": ["node", "jest", "express", "cache-manager", "uuid", "supertest", "express-serve-static-core"] + "types": ["node", "jest", "express", "uuid", "supertest", "express-serve-static-core"] } } diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index 0a52177a..fdbc6a57 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -72,7 +72,6 @@ "ngx-toastr": "^20.0.5", "qr-code-styling": "1.9.2", "rxjs": "~7.8.2", - "socket.io-client": "~4.8.3", "tslib": "^2.8.1", "uuid": "13.0.1", "y-websocket": "^3.0.0", diff --git a/teammapper-frontend/src/app/core/services/dialog/dialog.service.ts b/teammapper-frontend/src/app/core/services/dialog/dialog.service.ts index 8741ea2a..b635584e 100644 --- a/teammapper-frontend/src/app/core/services/dialog/dialog.service.ts +++ b/teammapper-frontend/src/app/core/services/dialog/dialog.service.ts @@ -6,10 +6,6 @@ import { DialogImportMermaidComponent } from 'src/app/modules/application/compon import { DialogImportAiComponent } from 'src/app/modules/application/components/dialog-import-ai/dialog-import-ai.component'; import { DialogPictogramsComponent } from 'src/app/modules/application/components/dialog-pictograms/dialog-pictograms.component'; import { DialogShareComponent } from 'src/app/modules/application/components/dialog-share/dialog-share.component'; -import { - DialogCriticalErrorComponent, - CriticalErrorData, -} from 'src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component'; @Injectable({ providedIn: 'root', @@ -23,7 +19,6 @@ export class DialogService { private pictogramsModalRef: MatDialogRef; private importMermaidModalRef: MatDialogRef; private importAiModalRef: MatDialogRef; - private criticalErrorModalRef: MatDialogRef; openPictogramDialog() { this.pictogramsModalRef = this.dialog.open(DialogPictogramsComponent); @@ -89,32 +84,4 @@ export class DialogService { this.shareModalRef.close(); } - - /** - * Open critical error dialog - * This modal is blocking and cannot be dismissed except by reloading the page - * @param errorData Critical error data to display - * @returns Dialog reference - */ - openCriticalErrorDialog( - errorData: CriticalErrorData - ): MatDialogRef { - // Only open one critical error dialog at a time - if (this.criticalErrorModalRef) { - return this.criticalErrorModalRef; - } - - this.criticalErrorModalRef = this.dialog.open( - DialogCriticalErrorComponent, - { - data: errorData, - disableClose: true, // Prevent closing by clicking outside or pressing escape - hasBackdrop: true, - backdropClass: 'critical-error-backdrop', - panelClass: 'critical-error-dialog', - } - ); - - return this.criticalErrorModalRef; - } } diff --git a/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.spec.ts b/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.spec.ts deleted file mode 100644 index f7c959f4..00000000 --- a/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.spec.ts +++ /dev/null @@ -1,210 +0,0 @@ -import { MmpService } from '../mmp/mmp.service'; -import { UtilsService } from '../utils/utils.service'; -import { ToastService } from '../toast/toast.service'; -import { DialogService } from '../dialog/dialog.service'; -import { - ValidationErrorResponse, - CriticalErrorResponse, - SuccessResponse, - OperationResponse, -} from './server-types'; -import { ExportNodeProperties } from '@mmp/map/types'; -import { createMockUtilsService } from '../../../../test/mocks/utils-service.mock'; -import { MapSyncErrorHandler } from './map-sync-error-handler'; - -function createMockNode( - overrides?: Partial -): ExportNodeProperties { - return { - id: 'mock-id', - name: 'Mock Node', - parent: 'root', - k: 1, - colors: { branch: '#000000' }, - font: { size: 14, style: 'normal', weight: 'normal' }, - locked: false, - hidden: false, - coordinates: undefined, - image: undefined, - link: undefined, - isRoot: false, - detached: false, - ...overrides, - }; -} - -describe('MapSyncErrorHandler', () => { - let handler: MapSyncErrorHandler; - let mmpService: jest.Mocked; - let utilsService: jest.Mocked; - let toastService: jest.Mocked; - let dialogService: jest.Mocked; - - const mockNode = createMockNode({ id: 'node-1', name: 'Test Node' }); - const mockMapSnapshot: ExportNodeProperties[] = [mockNode]; - - const mockServerMap = { - uuid: 'test-uuid', - lastModified: new Date().toISOString(), - deletedAt: new Date(Date.now() + 86400000).toISOString(), - deleteAfterDays: 30, - data: mockMapSnapshot, - options: { fontMaxSize: 18, fontMinSize: 10, fontIncrement: 2 }, - createdAt: new Date().toISOString(), - }; - - beforeEach(() => { - mmpService = { - new: jest.fn(), - } as unknown as jest.Mocked; - - utilsService = createMockUtilsService(); - - toastService = { - showValidationCorrection: jest.fn(), - } as unknown as jest.Mocked; - - dialogService = { - openCriticalErrorDialog: jest.fn(), - } as unknown as jest.Mocked; - - handler = new MapSyncErrorHandler( - mmpService, - utilsService, - toastService, - dialogService - ); - }); - - it('success response triggers no side effects', async () => { - const response: SuccessResponse = { - success: true, - data: [mockNode], - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect({ - mapReloaded: mmpService.new.mock.calls.length, - toastShown: toastService.showValidationCorrection.mock.calls.length, - dialogOpened: dialogService.openCriticalErrorDialog.mock.calls.length, - }).toEqual({ mapReloaded: 0, toastShown: 0, dialogOpened: 0 }); - }); - - it('error with fullMapState reloads map', async () => { - const response: ValidationErrorResponse = { - success: false, - errorType: 'validation', - code: 'INVALID_PARENT', - message: 'Invalid parent', - fullMapState: mockServerMap, - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect(mmpService.new).toHaveBeenCalledWith(mockMapSnapshot, false); - }); - - it('error with fullMapState shows toast', async () => { - const response: CriticalErrorResponse = { - success: false, - errorType: 'critical', - code: 'SERVER_ERROR', - message: 'Server error', - fullMapState: mockServerMap, - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect(toastService.showValidationCorrection).toHaveBeenCalledWith( - 'add node', - 'Operation failed - map reloaded from server' - ); - }); - - it('error without fullMapState shows critical dialog', async () => { - const response: CriticalErrorResponse = { - success: false, - errorType: 'critical', - code: 'SERVER_ERROR', - message: 'Server error', - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ - code: 'SERVER_ERROR', - message: expect.stringContaining('server encountered an error'), - }); - }); - - it('malformed response shows critical dialog', async () => { - const response = { - invalid: 'response', - } as unknown as OperationResponse; - - await handler.handleOperationResponse(response, 'add node'); - - expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ - code: 'MALFORMED_RESPONSE', - message: expect.stringContaining('invalid response'), - }); - }); - - it('malformed response with translation failure uses fallback', async () => { - utilsService.translate.mockImplementation(async () => { - throw new Error('Translation failed'); - }); - - const response = { - invalid: 'response', - } as unknown as OperationResponse; - - await handler.handleOperationResponse(response, 'add node'); - - expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ - code: 'MALFORMED_RESPONSE', - message: 'Invalid server response. Please try again.', - }); - }); - - it('error with malformed fullMapState shows critical dialog', async () => { - const response: ValidationErrorResponse = { - success: false, - errorType: 'validation', - code: 'INVALID_PARENT', - message: 'Invalid parent', - fullMapState: { - uuid: 'test-uuid', - data: [], - } as unknown as typeof mockServerMap, - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ - code: 'MALFORMED_RESPONSE', - message: expect.stringContaining('invalid response'), - }); - }); - - it('error without fullMapState with translation failure uses fallback', async () => { - utilsService.translate.mockImplementation(async () => { - throw new Error('Translation failed'); - }); - - const response: CriticalErrorResponse = { - success: false, - errorType: 'critical', - code: 'SERVER_ERROR', - message: 'Server error', - }; - - await handler.handleOperationResponse(response, 'add node'); - - expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ - code: 'SERVER_ERROR', - message: 'An error occurred. Please try again.', - }); - }); -}); diff --git a/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.ts b/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.ts deleted file mode 100644 index ba8145b0..00000000 --- a/teammapper-frontend/src/app/core/services/map-sync/map-sync-error-handler.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { MmpService } from '../mmp/mmp.service'; -import { UtilsService } from '../utils/utils.service'; -import { ToastService } from '../toast/toast.service'; -import { DialogService } from '../dialog/dialog.service'; -import { - OperationResponse, - ValidationErrorResponse, - CriticalErrorResponse, - ServerMap, -} from './server-types'; - -// Validate ServerMap structure at runtime -export function isValidServerMap(map: unknown): map is ServerMap { - if (!map || typeof map !== 'object') return false; - - const serverMap = map as ServerMap; - - return ( - typeof serverMap.uuid === 'string' && - serverMap.uuid.length > 0 && - Array.isArray(serverMap.data) && - serverMap.data.length > 0 && - typeof serverMap.lastModified === 'string' && - typeof serverMap.createdAt === 'string' && - typeof serverMap.deletedAt === 'string' && - typeof serverMap.deleteAfterDays === 'number' && - typeof serverMap.options === 'object' - ); -} - -// Type guard to validate error response structure at runtime -export function isValidErrorResponse( - response: OperationResponse -): response is ValidationErrorResponse | CriticalErrorResponse { - if (response.success !== false) return false; - - const errorResponse = response as - | ValidationErrorResponse - | CriticalErrorResponse; - - const isBasicStructureValid = - typeof errorResponse.errorType === 'string' && - (errorResponse.errorType === 'validation' || - errorResponse.errorType === 'critical') && - typeof errorResponse.code === 'string' && - errorResponse.code.trim() !== '' && - typeof errorResponse.message === 'string'; - - if (!isBasicStructureValid) return false; - - if (errorResponse.fullMapState) { - return isValidServerMap(errorResponse.fullMapState); - } - - return true; -} - -export class MapSyncErrorHandler { - constructor( - private mmpService: MmpService, - private utilsService: UtilsService, - private toastService: ToastService, - private dialogService: DialogService - ) {} - - // Simplified handler for all operation responses - async handleOperationResponse( - response: OperationResponse, - operationName: string - ): Promise { - if (response.success) { - return; - } - - if (!isValidErrorResponse(response)) { - await this.showMalformedResponseError(); - return; - } - - if (response.fullMapState) { - await this.handleRecoverableError(response, operationName); - } else { - await this.handleCriticalError(response); - } - } - - private async showMalformedResponseError(): Promise { - let message: string; - try { - message = await this.utilsService.translate( - 'TOASTS.ERRORS.MALFORMED_RESPONSE' - ); - } catch { - message = 'Invalid server response. Please try again.'; - } - this.dialogService.openCriticalErrorDialog({ - code: 'MALFORMED_RESPONSE', - message, - }); - } - - private async handleRecoverableError( - response: ValidationErrorResponse | CriticalErrorResponse, - operationName: string - ): Promise { - this.mmpService.new(response.fullMapState.data, false); - - let message: string; - try { - message = await this.utilsService.translate( - 'TOASTS.ERRORS.OPERATION_FAILED_MAP_RELOADED' - ); - } catch { - message = 'Operation failed - map reloaded'; - } - this.toastService.showValidationCorrection(operationName, message); - } - - private async handleCriticalError( - response: ValidationErrorResponse | CriticalErrorResponse - ): Promise { - const userMessage = await this.getUserFriendlyErrorMessage( - response.code || 'SERVER_ERROR', - response.message || 'Unknown error' - ); - - this.dialogService.openCriticalErrorDialog({ - code: response.code || 'SERVER_ERROR', - message: userMessage, - }); - } - - // Convert error code to user-friendly translated message - private async getUserFriendlyErrorMessage( - code: string, - _messageKey: string - ): Promise { - const errorKeyMapping: Record = { - NETWORK_TIMEOUT: 'TOASTS.ERRORS.NETWORK_TIMEOUT', - SERVER_ERROR: 'TOASTS.ERRORS.SERVER_ERROR', - AUTH_FAILED: 'TOASTS.ERRORS.AUTH_FAILED', - MALFORMED_REQUEST: 'TOASTS.ERRORS.MALFORMED_REQUEST', - RATE_LIMIT_EXCEEDED: 'TOASTS.ERRORS.RATE_LIMIT_EXCEEDED', - }; - - const translationKey = - errorKeyMapping[code] || 'TOASTS.ERRORS.UNEXPECTED_ERROR'; - - try { - return await this.utilsService.translate(translationKey); - } catch { - return 'An error occurred. Please try again.'; - } - } -} diff --git a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.spec.ts b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.spec.ts index fd00c037..27e29e64 100644 --- a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.spec.ts +++ b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.spec.ts @@ -6,17 +6,15 @@ import { StorageService } from '../storage/storage.service'; import { SettingsService } from '../settings/settings.service'; import { UtilsService } from '../utils/utils.service'; import { ToastrService } from 'ngx-toastr'; -import { ToastService } from '../toast/toast.service'; -import { DialogService } from '../dialog/dialog.service'; import { ExportNodeProperties } from '@mmp/map/types'; import { createMockUtilsService } from '../../../../test/mocks/utils-service.mock'; import { Observable } from 'rxjs'; import { UserSettings } from '../../../shared/models/settings.model'; -import { SyncStrategy } from './sync-strategy'; +import { YjsSyncService } from './yjs-sync.service'; -// Narrow accessor: only exposes the SyncStrategy interface, not sub-service internals -function getStrategy(service: MapSyncService): SyncStrategy { - return (service as unknown as { syncStrategy: SyncStrategy }).syncStrategy; +// Narrow accessor: only exposes the sync service handle, not its internals +function getSync(service: MapSyncService): YjsSyncService { + return (service as unknown as { syncService: YjsSyncService }).syncService; } function createMockNode( @@ -69,7 +67,7 @@ describe('MapSyncService', () => { settingsService = { getCachedUserSettings: jest.fn(), getCachedSystemSettings: jest.fn().mockReturnValue({ - featureFlags: { yjs: false, pictograms: false, ai: false }, + featureFlags: { pictograms: false, ai: false }, }), setEditMode: jest.fn(), } as unknown as jest.Mocked; @@ -101,14 +99,6 @@ describe('MapSyncService', () => { }, { provide: SettingsService, useValue: settingsService }, { provide: UtilsService, useValue: createMockUtilsService() }, - { - provide: ToastService, - useValue: { showValidationCorrection: jest.fn() }, - }, - { - provide: DialogService, - useValue: { openCriticalErrorDialog: jest.fn() }, - }, { provide: ToastrService, useValue: { @@ -142,9 +132,8 @@ describe('MapSyncService', () => { }, }); - // Prevent strategy from performing real I/O jest - .spyOn(getStrategy(service), 'initMap') + .spyOn(getSync(service), 'initMap') .mockImplementation(() => undefined); }); @@ -170,16 +159,20 @@ describe('MapSyncService', () => { }); describe('undo and redo', () => { - it('undo delegates through to mmpService', () => { + it('undo delegates through to sync service', () => { + const undoSpy = jest.spyOn(getSync(service), 'undo'); + service.undo(); - expect(mmpService.undo).toHaveBeenCalled(); + expect(undoSpy).toHaveBeenCalled(); }); - it('redo delegates through to mmpService', () => { + it('redo delegates through to sync service', () => { + const redoSpy = jest.spyOn(getSync(service), 'redo'); + service.redo(); - expect(mmpService.redo).toHaveBeenCalled(); + expect(redoSpy).toHaveBeenCalled(); }); }); @@ -228,24 +221,24 @@ describe('MapSyncService', () => { ); }); - it('sets writable true on strategy when response writable is true', async () => { + it('sets writable true on sync service when response writable is true', async () => { httpService.get.mockResolvedValue({ ok: true, json: () => Promise.resolve({ ...mockServerMap, writable: true }), } as unknown as Response); - const setWritableSpy = jest.spyOn(getStrategy(service), 'setWritable'); + const setWritableSpy = jest.spyOn(getSync(service), 'setWritable'); await service.prepareExistingMap('test-uuid', 'secret'); expect(setWritableSpy).toHaveBeenCalledWith(true); }); - it('sets writable false on strategy when response writable is false', async () => { + it('sets writable false on sync service when response writable is false', async () => { httpService.get.mockResolvedValue({ ok: true, json: () => Promise.resolve({ ...mockServerMap, writable: false }), } as unknown as Response); - const setWritableSpy = jest.spyOn(getStrategy(service), 'setWritable'); + const setWritableSpy = jest.spyOn(getSync(service), 'setWritable'); await service.prepareExistingMap('test-uuid', 'wrong'); @@ -257,7 +250,7 @@ describe('MapSyncService', () => { ok: true, json: () => Promise.resolve(mockServerMap), } as unknown as Response); - const setWritableSpy = jest.spyOn(getStrategy(service), 'setWritable'); + const setWritableSpy = jest.spyOn(getSync(service), 'setWritable'); await service.prepareExistingMap('test-uuid', ''); @@ -266,16 +259,16 @@ describe('MapSyncService', () => { }); describe('lifecycle', () => { - it('ngOnDestroy calls destroy on strategy', () => { - const destroySpy = jest.spyOn(getStrategy(service), 'destroy'); + it('ngOnDestroy calls destroy on sync service', () => { + const destroySpy = jest.spyOn(getSync(service), 'destroy'); service.ngOnDestroy(); expect(destroySpy).toHaveBeenCalled(); }); - it('reset calls destroy on strategy', () => { - const destroySpy = jest.spyOn(getStrategy(service), 'destroy'); + it('reset calls destroy on sync service', () => { + const destroySpy = jest.spyOn(getSync(service), 'destroy'); service.reset(); diff --git a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts index f768e369..0dd71efa 100644 --- a/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts +++ b/teammapper-frontend/src/app/core/services/map-sync/map-sync.service.ts @@ -16,14 +16,9 @@ import { UtilsService } from '../utils/utils.service'; import { StorageService } from '../storage/storage.service'; import { SettingsService } from '../settings/settings.service'; import { ToastrService } from 'ngx-toastr'; -import { ToastService } from '../toast/toast.service'; -import { DialogService } from '../dialog/dialog.service'; import { ClientColorMapping, ClientColorMappingValue } from './yjs-utils'; -import { MapSyncErrorHandler } from './map-sync-error-handler'; import { MapSyncContext, ConnectionStatus } from './map-sync-context'; -import { SocketIoSyncService } from './socket-io-sync.service'; import { YjsSyncService } from './yjs-sync.service'; -import { SyncStrategy } from './sync-strategy'; export { ConnectionStatus } from './map-sync-context'; @@ -37,8 +32,6 @@ export class MapSyncService implements OnDestroy { private settingsService = inject(SettingsService); private utilsService = inject(UtilsService); private toastrService = inject(ToastrService); - private toastService = inject(ToastService); - private dialogService = inject(DialogService); // needed in color panel to show all clients private readonly clientListSubject: BehaviorSubject; @@ -56,8 +49,7 @@ export class MapSyncService implements OnDestroy { public readonly canRedo$: Observable = this.canRedoSubject.asObservable(); - // Sync strategy (only one instantiated based on feature flag) - private readonly syncStrategy: SyncStrategy; + private readonly syncService: YjsSyncService; // Common fields private colorMapping: ClientColorMapping; @@ -80,43 +72,19 @@ export class MapSyncService implements OnDestroy { ]; this.modificationSecret = ''; this.colorMapping = {}; - const yjsEnabled = - this.settingsService.getCachedSystemSettings()?.featureFlags?.yjs ?? - false; - - const ctx = this.createContext(); - - if (yjsEnabled) { - this.syncStrategy = new YjsSyncService( - ctx, - this.mmpService, - this.settingsService, - this.utilsService, - this.toastrService, - this.httpService - ); - } else { - const errorHandler = new MapSyncErrorHandler( - this.mmpService, - this.utilsService, - this.toastService, - this.dialogService - ); - this.syncStrategy = new SocketIoSyncService( - ctx, - this.mmpService, - this.settingsService, - this.utilsService, - this.toastrService, - errorHandler - ); - } - this.syncStrategy.connect(); + this.syncService = new YjsSyncService( + this.createContext(), + this.mmpService, + this.settingsService, + this.utilsService, + this.toastrService, + this.httpService + ); } ngOnDestroy() { - this.syncStrategy.destroy(); + this.syncService.destroy(); } // ─── Public API ────────────────────────────────────────────── @@ -139,14 +107,14 @@ export class MapSyncService implements OnDestroy { return; } - this.syncStrategy.setWritable(serverMap.writable !== false); + this.syncService.setWritable(serverMap.writable !== false); this.updateCachedMapForAdmin(serverMap); this.prepareMap(serverMap); return serverMap; } public reset() { - this.syncStrategy.destroy(); + this.syncService.destroy(); this.colorMapping = {}; } @@ -155,7 +123,7 @@ export class MapSyncService implements OnDestroy { this.attachedNodeSubject.next( this.mmpService.selectNode(this.mmpService.getRootNode().id) ); - this.syncStrategy.initMap(this.getAttachedMap().cachedMap.uuid); + this.syncService.initMap(this.getAttachedMap().cachedMap.uuid); } public attachMap(cachedMapEntry: CachedMapEntry): void { @@ -203,19 +171,19 @@ export class MapSyncService implements OnDestroy { } public undo(): void { - this.syncStrategy.undo(); + this.syncService.undo(); } public redo(): void { - this.syncStrategy.redo(); + this.syncService.redo(); } public updateMapOptions(options?: CachedMapOptions) { - this.syncStrategy.updateMapOptions(options); + this.syncService.updateMapOptions(options); } public async deleteMap(adminId: string): Promise { - await this.syncStrategy.deleteMap(adminId); + await this.syncService.deleteMap(adminId); } public async fetchUserMapsFromServer(): Promise { diff --git a/teammapper-frontend/src/app/core/services/map-sync/server-types.ts b/teammapper-frontend/src/app/core/services/map-sync/server-types.ts index 356f1e41..439fef4d 100644 --- a/teammapper-frontend/src/app/core/services/map-sync/server-types.ts +++ b/teammapper-frontend/src/app/core/services/map-sync/server-types.ts @@ -1,121 +1,6 @@ -import { MapSnapshot, ExportNodeProperties } from '@mmp/map/types'; +import { MapSnapshot } from '@mmp/map/types'; import { CachedMapOptions } from 'src/app/shared/models/cached-map.model'; -// Re-export operation types from backend -export type OperationType = - | 'create' - | 'update' - | 'delete' - | 'updateProperty' - | 'undo' - | 'redo'; -export type OperationStatus = 'pending' | 'confirmed' | 'rejected'; - -// Error response types (matching backend types.ts) -export interface BaseErrorResponse { - success: false; - errorType: 'validation' | 'critical'; - code: string; - message: string; - context?: Record; -} - -export interface ValidationErrorResponse extends BaseErrorResponse { - errorType: 'validation'; - code: - | 'INVALID_PARENT' - | 'CONSTRAINT_VIOLATION' - | 'MISSING_REQUIRED_FIELD' - | 'CIRCULAR_REFERENCE' - | 'DUPLICATE_NODE'; - fullMapState?: ServerMap; -} - -export interface CriticalErrorResponse extends BaseErrorResponse { - errorType: 'critical'; - code: - | 'SERVER_ERROR' - | 'NETWORK_TIMEOUT' - | 'AUTH_FAILED' - | 'MALFORMED_REQUEST' - | 'RATE_LIMIT_EXCEEDED'; - retryAfter?: number; - fullMapState?: ServerMap; -} - -export interface SuccessResponse { - success: true; - data: T; - meta?: { - timestamp: number; - operationId?: string; - }; -} - -export type OperationResponse = - | SuccessResponse - | ValidationErrorResponse - | CriticalErrorResponse; - -// Extended node properties with optimistic state -export interface ExportNodePropertiesWithState extends ExportNodeProperties { - __optimistic?: boolean; - __operationId?: string; - __localModifiedAt?: number; -} - -interface ResponseServer { - // socket id of the triggering client, to prevent endless update loops - clientId: string; -} - -interface ResponseMapUpdated extends ResponseServer { - map: ServerMap; -} - -type ResponseSnapshotChanges = Record< - string, - Partial | undefined ->; - -interface ResponseMapDiff { - added: ResponseSnapshotChanges; - deleted: ResponseSnapshotChanges; - updated: ResponseSnapshotChanges; -} - -interface ResponseUndoRedoChanges extends ResponseServer { - diff: ResponseMapDiff; -} - -interface ResponseMapOptionsUpdated extends ResponseServer { - options: CachedMapOptions; -} - -interface ResponseNodeUpdated extends ResponseServer { - node: ExportNodeProperties; - property: string; -} - -interface ResponseNodesAdded extends ResponseServer { - nodes: ExportNodeProperties[]; -} - -interface ResponseClientNotification { - clientId: string; - message: string; - type: 'error' | 'warning' | 'success'; -} - -interface ResponseNodeRemoved extends ResponseServer { - nodeId: string; -} - -interface ResponseSelectionUpdated extends ResponseServer { - nodeId: string; - selected: boolean; -} - interface ServerMap { uuid: string; lastModified: string; @@ -165,17 +50,4 @@ const ReversePropertyMapping = { hidden: 'hidden', } as const; -export { - ResponseMapUpdated, - ResponseUndoRedoChanges, - ResponseMapOptionsUpdated, - ResponseNodesAdded, - ResponseNodeRemoved, - ResponseNodeUpdated, - ResponseSelectionUpdated, - ResponseClientNotification, - ServerMap, - ServerMapInfo, - PrivateServerMap, - ReversePropertyMapping, -}; +export { ServerMap, ServerMapInfo, PrivateServerMap, ReversePropertyMapping }; diff --git a/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.spec.ts b/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.spec.ts deleted file mode 100644 index 28b1962a..00000000 --- a/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.spec.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { Subject } from 'rxjs'; -import { SocketIoSyncService } from './socket-io-sync.service'; -import { MapSyncContext } from './map-sync-context'; -import { MmpService } from '../mmp/mmp.service'; -import { SettingsService } from '../settings/settings.service'; -import { UtilsService } from '../utils/utils.service'; -import { ToastrService } from 'ngx-toastr'; -import { MapSyncErrorHandler } from './map-sync-error-handler'; -import { ExportNodeProperties, MapCreateEvent } from '@mmp/map/types'; -import { MapSnapshot } from '@mmp/map/handlers/history'; - -describe('SocketIoSyncService', () => { - let service: SocketIoSyncService; - let ctx: jest.Mocked; - let mmpService: jest.Mocked; - let settingsService: jest.Mocked; - let utilsService: jest.Mocked; - let toastrService: jest.Mocked; - let errorHandler: jest.Mocked; - - // Subjects to simulate MMP events - let createSubject: Subject; - let nodeUpdateSubject: Subject; - let nodeCreateSubject: Subject; - let nodePasteSubject: Subject; - let nodeRemoveSubject: Subject; - let undoSubject: Subject; - let redoSubject: Subject; - let nodeSelectSubject: Subject; - let nodeDeselectSubject: Subject; - - const mockNode: ExportNodeProperties = { - id: 'node-1', - name: 'Test Node', - parent: 'root', - k: 1, - colors: { branch: '#000000' }, - font: { size: 14, style: 'normal', weight: 'normal' }, - locked: false, - hidden: false, - coordinates: undefined, - image: undefined, - link: undefined, - isRoot: false, - detached: false, - }; - - beforeEach(() => { - createSubject = new Subject(); - nodeUpdateSubject = new Subject(); - nodeCreateSubject = new Subject(); - nodePasteSubject = new Subject(); - nodeRemoveSubject = new Subject(); - undoSubject = new Subject(); - redoSubject = new Subject(); - nodeSelectSubject = new Subject(); - nodeDeselectSubject = new Subject(); - - const subjectMap: Record> = { - create: createSubject, - nodeUpdate: nodeUpdateSubject, - nodeCreate: nodeCreateSubject, - nodePaste: nodePasteSubject, - nodeRemove: nodeRemoveSubject, - undo: undoSubject, - redo: redoSubject, - nodeSelect: nodeSelectSubject, - nodeDeselect: nodeDeselectSubject, - }; - - mmpService = { - on: jest.fn((event: string) => subjectMap[event] ?? new Subject()), - selectNode: jest.fn().mockReturnValue(mockNode), - editNode: jest.fn(), - undo: jest.fn(), - redo: jest.fn(), - history: jest.fn().mockReturnValue({ - snapshots: [[], [], []] as MapSnapshot[], - index: 2, - }), - highlightNode: jest.fn(), - exportAsJSON: jest.fn().mockReturnValue([]), - } as unknown as jest.Mocked; - - ctx = { - getAttachedMap: jest.fn().mockReturnValue({ - key: 'map-test', - cachedMap: { uuid: 'test-uuid', data: [] }, - }), - getModificationSecret: jest.fn().mockReturnValue('secret'), - getColorMapping: jest.fn().mockReturnValue({}), - getClientColor: jest.fn().mockReturnValue('#ff0000'), - colorForNode: jest.fn().mockReturnValue(''), - setConnectionStatus: jest.fn(), - setColorMapping: jest.fn(), - setAttachedNode: jest.fn(), - setClientColor: jest.fn(), - setCanUndo: jest.fn(), - setCanRedo: jest.fn(), - updateAttachedMap: jest.fn().mockResolvedValue(undefined), - emitClientList: jest.fn(), - } as jest.Mocked; - - settingsService = { - setEditMode: jest.fn(), - } as unknown as jest.Mocked; - - utilsService = {} as unknown as jest.Mocked; - - toastrService = { - error: jest.fn(), - success: jest.fn(), - warning: jest.fn(), - } as unknown as jest.Mocked; - - errorHandler = { - handleOperationResponse: jest.fn(), - } as unknown as jest.Mocked; - - service = new SocketIoSyncService( - ctx, - mmpService, - settingsService, - utilsService, - toastrService, - errorHandler - ); - - // initMap calls createListeners which sets up all the subscriptions - // We need to mock the socket operations, so we call initMap indirectly - // by invoking the private createListeners through initMap - // But initMap also calls listenServerEvents which needs a socket. - // Let's just call createListeners directly via the public initMap path. - // We need to stub socket first. - - // Since connect() creates the socket and initMap needs it, - // we'll test at the unit level by calling initMap after mocking connect. - }); - - describe('updateCanUndoRedo after node mutations', () => { - beforeEach(() => { - // Inject mock socket before setting up listeners - const mockSocket = { - emit: jest.fn(), - on: jest.fn(), - io: { on: jest.fn() }, - id: 'mock-socket-id', - }; - (service as unknown as { socket: unknown }).socket = mockSocket; - (service as unknown as { createListeners: () => void }).createListeners(); - }); - - it('calls setCanUndo and setCanRedo after create event', () => { - createSubject.next({} as MapCreateEvent); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(true); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - - it('calls setCanUndo and setCanRedo after nodeUpdate event', () => { - nodeUpdateSubject.next({ - nodeProperties: mockNode, - changedProperty: 'name', - }); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(true); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - - it('calls setCanUndo and setCanRedo after nodeCreate event', () => { - nodeCreateSubject.next(mockNode); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(true); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - - it('calls setCanUndo and setCanRedo after nodePaste event', () => { - nodePasteSubject.next([mockNode]); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(true); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - - it('calls setCanUndo and setCanRedo after nodeRemove event', () => { - nodeRemoveSubject.next(mockNode); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(true); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - }); - - describe('undo and redo', () => { - it('calls mmpService.undo and updates canUndoRedo state', () => { - service.undo(); - - expect(mmpService.undo).toHaveBeenCalled(); - expect(ctx.setCanUndo).toHaveBeenCalled(); - expect(ctx.setCanRedo).toHaveBeenCalled(); - }); - - it('calls mmpService.redo and updates canUndoRedo state', () => { - service.redo(); - - expect(mmpService.redo).toHaveBeenCalled(); - expect(ctx.setCanUndo).toHaveBeenCalled(); - expect(ctx.setCanRedo).toHaveBeenCalled(); - }); - }); - - describe('canUndo/canRedo values from history state', () => { - beforeEach(() => { - const mockSocket = { - emit: jest.fn(), - on: jest.fn(), - io: { on: jest.fn() }, - id: 'mock-socket-id', - }; - (service as unknown as { socket: unknown }).socket = mockSocket; - (service as unknown as { createListeners: () => void }).createListeners(); - }); - - it('sets canUndo=false when history index is at start', () => { - mmpService.history.mockReturnValue({ - snapshots: [[]] as MapSnapshot[], - index: 0, - }); - createSubject.next({} as MapCreateEvent); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(false); - expect(ctx.setCanRedo).toHaveBeenCalledWith(false); - }); - - it('sets canRedo=true when history index is before end', () => { - mmpService.history.mockReturnValue({ - snapshots: [[], [], []] as MapSnapshot[], - index: 1, - }); - createSubject.next({} as MapCreateEvent); - - expect(ctx.setCanUndo).toHaveBeenCalledWith(false); - expect(ctx.setCanRedo).toHaveBeenCalledWith(true); - }); - }); -}); diff --git a/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.ts b/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.ts deleted file mode 100644 index fcb6f5f7..00000000 --- a/teammapper-frontend/src/app/core/services/map-sync/socket-io-sync.service.ts +++ /dev/null @@ -1,664 +0,0 @@ -import { io, Socket } from 'socket.io-client'; -import { NodePropertyMapping } from '@mmp/index'; -import { - ExportNodeProperties, - MapCreateEvent, - MapProperties, - NodeUpdateEvent, -} from '@mmp/map/types'; -import { - ResponseMapUpdated, - ResponseUndoRedoChanges, - ResponseMapOptionsUpdated, - ResponseNodesAdded, - ResponseNodeRemoved, - ResponseNodeUpdated, - ResponseSelectionUpdated, - ResponseClientNotification, - OperationResponse, - ReversePropertyMapping, -} from './server-types'; -import { MmpService } from '../mmp/mmp.service'; -import { UtilsService } from '../utils/utils.service'; -import { SettingsService } from '../settings/settings.service'; -import { ToastrService } from 'ngx-toastr'; -import { MapDiff, SnapshotChanges } from '@mmp/map/handlers/history'; -import { CachedMapOptions } from '../../../shared/models/cached-map.model'; -import { - MapSyncContext, - DEFAULT_COLOR, - DEFAULT_SELF_COLOR, -} from './map-sync-context'; -import { MapSyncErrorHandler } from './map-sync-error-handler'; -import { SyncStrategy } from './sync-strategy'; - -type ServerClientList = Record; - -export class SocketIoSyncService implements SyncStrategy { - private socket: Socket; - - constructor( - private ctx: MapSyncContext, - private mmpService: MmpService, - private settingsService: SettingsService, - private utilsService: UtilsService, - private toastrService: ToastrService, - private errorHandler: MapSyncErrorHandler - ) {} - - // ─── Connection ───────────────────────────────────────────── - - connect(): void { - const reconnectOptions = { - reconnection: true, - reconnectionDelay: 1000, - reconnectionDelayMax: 5000, - reconnectionAttempts: 60, - randomizationFactor: 0.5, - }; - - const baseHref = - document.querySelector('base')?.getAttribute('href') ?? '/'; - this.socket = - baseHref !== '/' - ? io('', { - path: `${baseHref}socket.io`, - ...reconnectOptions, - }) - : io({ - ...reconnectOptions, - }); - } - - private detach(): void { - if (this.socket) { - this.socket.removeAllListeners(); - this.leaveMap(); - } - } - - destroy(): void { - this.detach(); - } - - initMap(uuid: string): void { - this.createListeners(); - this.listenServerEvents(uuid); - } - - setWritable(): void { - // No-op: Socket.io strategy handles write access differently - } - - undo(): void { - this.mmpService.undo(); - this.updateCanUndoRedo(); - } - - redo(): void { - this.mmpService.redo(); - this.updateCanUndoRedo(); - } - - updateMapOptions(options?: CachedMapOptions): void { - const cachedMapEntry = this.ctx.getAttachedMap(); - this.socket.emit('updateMapOptions', { - mapId: cachedMapEntry.cachedMap.uuid, - options, - modificationSecret: this.ctx.getModificationSecret(), - }); - } - - async deleteMap(adminId: string): Promise { - const cachedMapEntry = this.ctx.getAttachedMap(); - this.socket.emit('deleteMap', { - adminId, - mapId: cachedMapEntry.cachedMap.uuid, - }); - } - - // ─── MMP event listeners (MMP → Socket.io) ───────────────── - - private createListeners(): void { - this.setupCreateHandler(); - this.setupSelectionHandlers(); - this.setupNodeUpdateHandler(); - this.setupUndoRedoHandlers(); - this.setupNodeCreateHandler(); - this.setupPasteHandler(); - this.setupNodeRemoveHandler(); - } - - private setupCreateHandler(): void { - this.mmpService.on('create').subscribe((_result: MapCreateEvent) => { - this.ctx.setAttachedNode(this.mmpService.selectNode()); - this.ctx.updateAttachedMap(); - this.updateMap(); - this.updateCanUndoRedo(); - }); - } - - private setupSelectionHandlers(): void { - this.mmpService - .on('nodeSelect') - .subscribe((nodeProps: ExportNodeProperties) => { - this.updateNodeSelection(nodeProps.id, true); - this.ctx.setAttachedNode(nodeProps); - }); - - this.mmpService - .on('nodeDeselect') - .subscribe((nodeProps: ExportNodeProperties) => { - this.updateNodeSelection(nodeProps.id, false); - this.ctx.setAttachedNode(nodeProps); - }); - } - - private setupNodeUpdateHandler(): void { - this.mmpService.on('nodeUpdate').subscribe((result: NodeUpdateEvent) => { - this.ctx.setAttachedNode(result.nodeProperties); - this.emitUpdateNode(result); - this.ctx.updateAttachedMap(); - this.updateCanUndoRedo(); - }); - } - - private setupUndoRedoHandlers(): void { - this.mmpService.on('undo').subscribe((diff?: MapDiff) => { - this.ctx.setAttachedNode(this.mmpService.selectNode()); - this.ctx.updateAttachedMap(); - this.emitApplyMapChangesByDiff(diff, 'undo'); - this.updateCanUndoRedo(); - }); - - this.mmpService.on('redo').subscribe((diff?: MapDiff) => { - this.ctx.setAttachedNode(this.mmpService.selectNode()); - this.ctx.updateAttachedMap(); - this.emitApplyMapChangesByDiff(diff, 'redo'); - this.updateCanUndoRedo(); - }); - } - - private updateCanUndoRedo(): void { - if (typeof this.mmpService.history !== 'function') return; - const history = this.mmpService.history(); - if (!history?.snapshots) return; - this.ctx.setCanUndo(history.index > 1); - this.ctx.setCanRedo(history.index < history.snapshots.length - 1); - } - - private setupNodeCreateHandler(): void { - this.mmpService - .on('nodeCreate') - .subscribe((newNode: ExportNodeProperties) => { - this.emitAddNode(newNode); - this.ctx.updateAttachedMap(); - this.mmpService.selectNode(newNode.id); - this.mmpService.editNode(); - this.updateCanUndoRedo(); - }); - } - - private setupPasteHandler(): void { - this.mmpService - .on('nodePaste') - .subscribe((newNodes: ExportNodeProperties[]) => { - this.emitAddNodes(newNodes); - this.ctx.updateAttachedMap(); - this.updateCanUndoRedo(); - }); - } - - private setupNodeRemoveHandler(): void { - this.mmpService - .on('nodeRemove') - .subscribe((removedNode: ExportNodeProperties) => { - this.emitRemoveNode(removedNode); - this.ctx.updateAttachedMap(); - this.updateCanUndoRedo(); - }); - } - - // ─── Socket.io emit methods ───────────────────────────────── - - private joinMap(mmpUuid: string, color: string): Promise { - return new Promise((resolve, reject) => { - this.socket.emit( - 'join', - { mapId: mmpUuid, color }, - (serverMap: MapProperties) => { - if (!serverMap) { - reject('Server Map not available'); - return; - } - resolve(serverMap); - } - ); - }); - } - - private leaveMap(): void { - this.socket.emit('leave'); - } - - private emitAddNode(newNode: ExportNodeProperties): void { - this.socket.emit( - 'addNodes', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - nodes: [newNode], - modificationSecret: this.ctx.getModificationSecret(), - }, - async (response: OperationResponse) => { - await this.handleOperationResponse(response, 'add node'); - } - ); - } - - private emitAddNodes(newNodes: ExportNodeProperties[]): void { - this.socket.emit( - 'addNodes', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - nodes: newNodes, - modificationSecret: this.ctx.getModificationSecret(), - }, - async (response: OperationResponse) => { - await this.handleOperationResponse(response, 'add nodes'); - } - ); - } - - private emitUpdateNode(nodeUpdate: NodeUpdateEvent): void { - this.socket.emit( - 'updateNode', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - node: nodeUpdate.nodeProperties, - updatedProperty: nodeUpdate.changedProperty, - modificationSecret: this.ctx.getModificationSecret(), - }, - async (response: OperationResponse) => { - await this.handleOperationResponse(response, 'update node'); - } - ); - } - - private emitRemoveNode(removedNode: ExportNodeProperties): void { - this.socket.emit( - 'removeNode', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - node: removedNode, - modificationSecret: this.ctx.getModificationSecret(), - }, - async (response: OperationResponse) => { - await this.handleOperationResponse(response, 'remove node'); - } - ); - } - - private emitApplyMapChangesByDiff( - diff: MapDiff, - operationType: 'undo' | 'redo' - ): void { - this.socket.emit( - 'applyMapChangesByDiff', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - diff, - modificationSecret: this.ctx.getModificationSecret(), - }, - async (response: OperationResponse) => { - await this.handleOperationResponse( - response, - `${operationType} operation` - ); - } - ); - } - - private updateMap(): void { - const cachedMapEntry = this.ctx.getAttachedMap(); - this.socket.emit('updateMap', { - mapId: cachedMapEntry.cachedMap.uuid, - map: cachedMapEntry.cachedMap, - modificationSecret: this.ctx.getModificationSecret(), - }); - } - - private updateNodeSelection(id: string, selected: boolean): void { - const mapping = this.ctx.getColorMapping(); - this.ctx.setColorMapping({ - ...mapping, - [this.socket.id]: { - color: DEFAULT_SELF_COLOR, - nodeId: selected ? id : '', - }, - }); - - if (!selected) { - const colorForNode = this.ctx.colorForNode(id); - if (colorForNode !== '') - this.mmpService.highlightNode(id, colorForNode, false); - } - - this.socket.emit('updateNodeSelection', { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - nodeId: id, - selected, - }); - } - - private checkModificationSecret(): void { - this.socket.emit( - 'checkModificationSecret', - { - mapId: this.ctx.getAttachedMap().cachedMap.uuid, - modificationSecret: this.ctx.getModificationSecret(), - }, - (result: boolean) => this.settingsService.setEditMode(result) - ); - } - - // ─── Server event handlers ────────────────────────────────── - - private listenServerEvents(uuid: string): Promise { - this.checkModificationSecret(); - this.setupReconnectionHandler(uuid); - this.setupNotificationHandlers(); - this.setupNodeEventHandlers(); - this.setupMapEventHandlers(); - this.setupClientEventHandlers(); - this.setupConnectionEventHandlers(); - - return this.joinMap(uuid, this.ctx.getClientColor()); - } - - private setupReconnectionHandler(uuid: string): void { - this.socket.io.on('reconnect', async () => { - const serverMap = await this.joinMap(uuid, this.ctx.getClientColor()); - this.ctx.setConnectionStatus('connected'); - this.mmpService.new(serverMap.data, false); - }); - } - - private setupNotificationHandlers(): void { - this.socket.on( - 'clientNotification', - async (notification: ResponseClientNotification) => { - if (notification.clientId === this.socket.id) return; - await this.handleClientNotification(notification); - } - ); - } - - private async handleClientNotification( - notification: ResponseClientNotification - ): Promise { - const msg = await this.utilsService.translate(notification.message); - if (!msg) return; - - const toastHandlers: Record void> = { - error: () => this.toastrService.error(msg), - success: () => this.toastrService.success(msg), - warning: () => this.toastrService.warning(msg), - }; - toastHandlers[notification.type]?.(); - } - - private setupNodeEventHandlers(): void { - this.setupNodesAddedHandler(); - this.setupNodeUpdatedHandler(); - this.setupNodeRemovedHandler(); - } - - private setupNodesAddedHandler(): void { - this.socket.on('nodesAdded', (result: ResponseNodesAdded) => { - if (result.clientId === this.socket.id) return; - this.mmpService.addNodesFromServer(result.nodes); - }); - } - - private setupNodeUpdatedHandler(): void { - this.socket.on('nodeUpdated', (result: ResponseNodeUpdated) => { - if (result.clientId === this.socket.id) return; - this.handleNodeUpdate(result); - }); - } - - private handleNodeUpdate(result: ResponseNodeUpdated): void { - const newNode = result.node; - const existingNode = this.mmpService.getNode(newNode.id); - const propertyPath = NodePropertyMapping[result.property]; - const changedValue = UtilsService.get(newNode, propertyPath); - - this.mmpService.updateNode( - result.property, - changedValue, - false, - true, - existingNode.id - ); - } - - private setupNodeRemovedHandler(): void { - this.socket.on('nodeRemoved', (result: ResponseNodeRemoved) => { - if (result.clientId === this.socket.id) return; - if (this.mmpService.existNode(result.nodeId)) { - this.mmpService.removeNode(result.nodeId, false); - } - }); - } - - private setupMapEventHandlers(): void { - this.setupMapUpdatedHandler(); - this.setupMapChangesHandler(); - this.setupMapOptionsHandler(); - this.setupMapDeletedHandler(); - } - - private setupMapUpdatedHandler(): void { - this.socket.on('mapUpdated', (result: ResponseMapUpdated) => { - if (result.clientId === this.socket.id) return; - this.mmpService.new(result.map.data, false); - }); - } - - private setupMapChangesHandler(): void { - this.socket.on('mapChangesUndoRedo', (result: ResponseUndoRedoChanges) => { - if (result.clientId === this.socket.id) return; - this.applyMapDiff(result.diff); - }); - } - - private applyMapDiff(diff: MapDiff): void { - this.applyAddedNodes(diff.added); - this.applyUpdatedNodes(diff.updated); - this.applyDeletedNodes(diff.deleted); - } - - private applyAddedNodes(added: SnapshotChanges): void { - if (!added) return; - for (const nodeId in added) { - this.mmpService.addNode(added[nodeId], false); - } - } - - private applyUpdatedNodes(updated: SnapshotChanges): void { - if (!updated) return; - for (const nodeId in updated) { - if (this.mmpService.existNode(nodeId)) { - this.applyNodePropertyUpdates(nodeId, updated[nodeId]); - } - } - } - - private applyNodePropertyUpdates( - nodeId: string, - nodeUpdates: Partial - ): void { - if (!nodeUpdates) return; - - for (const property in nodeUpdates) { - const updatedProperty = this.getClientProperty( - property, - (nodeUpdates as Record)[property] - ); - if (updatedProperty) { - this.mmpService.updateNode( - updatedProperty.clientProperty, - updatedProperty.directValue, - false, - true, - nodeId - ); - } - } - } - - private getClientProperty( - serverProperty: string, - value: unknown - ): { clientProperty: string; directValue: unknown } | undefined { - const mapping = - ReversePropertyMapping[ - serverProperty as keyof typeof ReversePropertyMapping - ]; - - if (typeof mapping === 'string') { - return { clientProperty: mapping, directValue: value }; - } - - if (mapping && typeof value === 'object') { - const subProperty = Object.keys(value)[0]; - const nestedMapping = mapping[ - subProperty as keyof typeof mapping - ] as string; - return { clientProperty: nestedMapping, directValue: value[subProperty] }; - } - - return; - } - - private applyDeletedNodes(deleted: SnapshotChanges): void { - if (!deleted) return; - for (const nodeId in deleted) { - if (this.mmpService.existNode(nodeId)) { - this.mmpService.removeNode(nodeId, false); - } - } - } - - private setupMapOptionsHandler(): void { - this.socket.on('mapOptionsUpdated', (result: ResponseMapOptionsUpdated) => { - if (result.clientId === this.socket.id) return; - this.mmpService.updateAdditionalMapOptions(result.options); - }); - } - - private setupMapDeletedHandler(): void { - this.socket.on('mapDeleted', () => { - window.location.reload(); - }); - } - - private setupClientEventHandlers(): void { - this.setupSelectionHandler(); - this.setupClientListHandler(); - this.setupClientDisconnectHandler(); - } - - private setupSelectionHandler(): void { - this.socket.on('selectionUpdated', (result: ResponseSelectionUpdated) => { - if (result.clientId === this.socket.id) return; - if (!this.mmpService.existNode(result.nodeId)) return; - this.handleSelectionUpdate(result); - }); - } - - private handleSelectionUpdate(result: ResponseSelectionUpdated): void { - this.ensureClientInMapping(result.clientId); - this.updateClientNodeSelection( - result.clientId, - result.nodeId, - result.selected - ); - const colorForNode = this.ctx.colorForNode(result.nodeId); - this.mmpService.highlightNode(result.nodeId, colorForNode, false); - } - - private ensureClientInMapping(clientId: string): void { - const mapping = this.ctx.getColorMapping(); - if (!mapping[clientId]) { - this.ctx.setColorMapping({ - ...mapping, - [clientId]: { color: DEFAULT_COLOR, nodeId: '' }, - }); - this.ctx.emitClientList(); - } - } - - private updateClientNodeSelection( - clientId: string, - nodeId: string, - selected: boolean - ): void { - const mapping = this.ctx.getColorMapping(); - const client = mapping[clientId]; - if (!client) return; - this.ctx.setColorMapping({ - ...mapping, - [clientId]: { ...client, nodeId: selected ? nodeId : '' }, - }); - } - - private setupClientListHandler(): void { - this.socket.on('clientListUpdated', (clients: ServerClientList) => { - this.updateColorMapping(clients); - this.ctx.emitClientList(); - }); - } - - private updateColorMapping(clients: ServerClientList): void { - const currentMapping = this.ctx.getColorMapping(); - this.ctx.setColorMapping( - Object.fromEntries( - Object.keys(clients).map(key => [ - key, - { - nodeId: currentMapping[key]?.nodeId || '', - color: key === this.socket.id ? DEFAULT_SELF_COLOR : clients[key], - }, - ]) - ) - ); - } - - private setupClientDisconnectHandler(): void { - this.socket.on('clientDisconnect', (clientId: string) => { - const mapping = this.ctx.getColorMapping(); - this.ctx.setColorMapping( - Object.fromEntries( - Object.entries(mapping).filter(([key]) => key !== clientId) - ) - ); - this.ctx.emitClientList(); - }); - } - - private setupConnectionEventHandlers(): void { - this.socket.on('disconnect', () => { - this.ctx.setConnectionStatus('disconnected'); - }); - } - - // ─── Error handling (delegated) ───────────────────────────── - - private async handleOperationResponse( - response: OperationResponse, - operationName: string - ): Promise { - return this.errorHandler.handleOperationResponse(response, operationName); - } -} diff --git a/teammapper-frontend/src/app/core/services/map-sync/sync-strategy.ts b/teammapper-frontend/src/app/core/services/map-sync/sync-strategy.ts deleted file mode 100644 index 4d7b4dce..00000000 --- a/teammapper-frontend/src/app/core/services/map-sync/sync-strategy.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { CachedMapOptions } from '../../../shared/models/cached-map.model'; - -export interface SyncStrategy { - /** One-time connection setup. Socket.io: opens socket. Yjs: no-op. */ - connect(): void; - - /** - * Initialize syncing for the given map. - * Caller must call destroy() before initMap() when switching maps. - * Caller must call setWritable() before initMap() to set permissions. - */ - initMap(uuid: string): void; - - /** Full cleanup: destroy resources, disconnect, reset all state. Idempotent. */ - destroy(): void; - - /** Undo the last operation. */ - undo(): void; - - /** Redo the last undone operation. */ - redo(): void; - - /** Push map option changes to the server. */ - updateMapOptions(options?: CachedMapOptions): void; - - /** Delete the map by admin ID. */ - deleteMap(adminId: string): Promise; - - /** Set write-access status from HTTP response. */ - setWritable(writable: boolean): void; -} diff --git a/teammapper-frontend/src/app/core/services/map-sync/yjs-sync.service.ts b/teammapper-frontend/src/app/core/services/map-sync/yjs-sync.service.ts index 83b4e492..b3109e63 100644 --- a/teammapper-frontend/src/app/core/services/map-sync/yjs-sync.service.ts +++ b/teammapper-frontend/src/app/core/services/map-sync/yjs-sync.service.ts @@ -29,11 +29,10 @@ import { DEFAULT_COLOR, DEFAULT_SELF_COLOR, } from './map-sync-context'; -import { SyncStrategy } from './sync-strategy'; const WS_CLOSE_MAP_DELETED = 4001; -export class YjsSyncService implements SyncStrategy { +export class YjsSyncService { private yDoc: Y.Doc | null = null; private wsProvider: WebsocketProvider | null = null; private yjsSynced = false; @@ -79,11 +78,7 @@ export class YjsSyncService implements SyncStrategy { await this.deleteMapViaHttp(adminId); } - // ─── SyncStrategy lifecycle ───────────────────────────────── - - connect(): void { - // Yjs connects lazily in initMap — no-op here - } + // ─── Connection lifecycle ─────────────────────────────────── initMap(uuid: string): void { if (this.hasActiveConnection(uuid)) { diff --git a/teammapper-frontend/src/app/core/services/toast/toast.service.spec.ts b/teammapper-frontend/src/app/core/services/toast/toast.service.spec.ts deleted file mode 100644 index 1e88e8fc..00000000 --- a/teammapper-frontend/src/app/core/services/toast/toast.service.spec.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { ToastrService } from 'ngx-toastr'; -import { ToastService } from './toast.service'; - -describe('ToastService', () => { - let service: ToastService; - let toastrSpy: jest.Mocked; - - beforeEach(() => { - jest.useFakeTimers(); - const spy = { - error: jest.fn(), - warning: jest.fn(), - info: jest.fn(), - success: jest.fn(), - } as unknown as jest.Mocked; - - TestBed.configureTestingModule({ - providers: [ToastService, { provide: ToastrService, useValue: spy }], - }); - - service = TestBed.inject(ToastService); - toastrSpy = TestBed.inject( - ToastrService - ) as unknown as jest.Mocked; - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - describe('showValidationCorrection()', () => { - it('should display warning toast with correct message', () => { - service.showValidationCorrection('Test Node', 'parent was reset to root'); - - // Wait for throttle time (500ms) - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalledWith( - 'Node "Test Node" was auto-corrected: parent was reset to root', - '', - expect.objectContaining({ - timeOut: 4000, - }) - ); - }); - - it('should display message without node name if not provided', () => { - service.showValidationCorrection('', 'parent was reset to root'); - - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalledWith( - 'Node was auto-corrected: parent was reset to root', - '', - expect.anything() - ); - }); - - it('should include correction details in message', () => { - service.showValidationCorrection( - 'Meeting Notes', - 'invalid reference removed' - ); - - jest.advanceTimersByTime(500); - - const call = - toastrSpy.warning.mock.calls[toastrSpy.warning.mock.calls.length - 1]; - expect(call[0]).toContain('invalid reference removed'); - }); - - it('should use 4 second duration', () => { - service.showValidationCorrection('Test', 'corrected'); - - jest.advanceTimersByTime(500); - - const call = - toastrSpy.warning.mock.calls[toastrSpy.warning.mock.calls.length - 1]; - expect(call[2]).toEqual({ timeOut: 4000 }); - }); - }); - - describe('throttling', () => { - it('should prevent toast spam by showing first message only', () => { - // Rapid fire toasts - service.showValidationCorrection('Node 1', 'correction 1'); - jest.advanceTimersByTime(100); - service.showValidationCorrection('Node 2', 'correction 2'); - jest.advanceTimersByTime(100); - service.showValidationCorrection('Node 3', 'correction 3'); - - // Wait for throttle time - jest.advanceTimersByTime(500); - - // Only the FIRST toast should be shown (throttle behavior, not debounce) - expect(toastrSpy.warning).toHaveBeenCalledTimes(1); - expect(toastrSpy.warning).toHaveBeenCalledWith( - expect.stringContaining('Node 1'), - expect.anything(), - expect.anything() - ); - }); - - it('should show multiple toasts if separated by throttle time', () => { - service.showValidationCorrection('Node 1', 'correction 1'); - jest.advanceTimersByTime(500); - - service.showValidationCorrection('Node 2', 'correction 2'); - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalledTimes(2); - }); - - it('should throttle all toast types', () => { - service.showInfo('Info message'); - jest.advanceTimersByTime(100); - service.showWarning('Warning message'); - jest.advanceTimersByTime(100); - service.showError('Error message'); - - jest.advanceTimersByTime(500); - - // Only first one should be shown (throttle shows first, not last) - expect(toastrSpy.info).toHaveBeenCalledTimes(1); - expect(toastrSpy.warning).toHaveBeenCalledTimes(0); - expect(toastrSpy.error).toHaveBeenCalledTimes(0); - expect(toastrSpy.info).toHaveBeenCalledWith( - 'Info message', - expect.anything(), - expect.anything() - ); - }); - }); - - describe('showInfo()', () => { - it('should display info toast with default duration', () => { - service.showInfo('Information message'); - - jest.advanceTimersByTime(500); - - expect(toastrSpy.info).toHaveBeenCalledWith( - 'Information message', - '', - expect.objectContaining({ - timeOut: 4000, - }) - ); - }); - }); - - describe('showWarning()', () => { - it('should display warning toast with default duration', () => { - service.showWarning('Warning message'); - - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalledWith( - 'Warning message', - '', - expect.objectContaining({ - timeOut: 4000, - }) - ); - }); - }); - - describe('showError()', () => { - it('should display error toast with default duration', () => { - service.showError('Error message'); - - jest.advanceTimersByTime(500); - - expect(toastrSpy.error).toHaveBeenCalledWith( - 'Error message', - '', - expect.objectContaining({ - timeOut: 4000, - }) - ); - }); - }); - - describe('toast queueing', () => { - it('should display toast immediately with throttle leading', () => { - service.showValidationCorrection('Node 1', 'correction'); - - // With leading: true, toast is displayed immediately - jest.advanceTimersByTime(0); - expect(toastrSpy.warning).toHaveBeenCalledTimes(1); - - // After throttle time passes, can show another toast - jest.advanceTimersByTime(500); - service.showValidationCorrection('Node 2', 'correction 2'); - jest.advanceTimersByTime(0); - expect(toastrSpy.warning).toHaveBeenCalledTimes(2); - }); - - it('should handle multiple messages in quick succession', () => { - // Simulate rapid operations triggering toasts - for (let i = 0; i < 10; i++) { - service.showValidationCorrection(`Node ${i}`, 'correction'); - jest.advanceTimersByTime(50); - } - - jest.advanceTimersByTime(500); - - // Only one toast should be shown (the first one due to throttling) - expect(toastrSpy.warning).toHaveBeenCalledTimes(1); - expect(toastrSpy.warning).toHaveBeenCalledWith( - expect.stringContaining('Node 0'), - expect.anything(), - expect.anything() - ); - }); - }); - - describe('toast types', () => { - it('should call toastr.warning for validation corrections', () => { - service.showValidationCorrection('Test', 'correction'); - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalled(); - expect(toastrSpy.error).not.toHaveBeenCalled(); - expect(toastrSpy.info).not.toHaveBeenCalled(); - expect(toastrSpy.success).not.toHaveBeenCalled(); - }); - - it('should call toastr.info for info messages', () => { - service.showInfo('Test info'); - jest.advanceTimersByTime(500); - - expect(toastrSpy.info).toHaveBeenCalled(); - expect(toastrSpy.error).not.toHaveBeenCalled(); - expect(toastrSpy.warning).not.toHaveBeenCalled(); - expect(toastrSpy.success).not.toHaveBeenCalled(); - }); - - it('should call toastr.warning for warnings', () => { - service.showWarning('Test warning'); - jest.advanceTimersByTime(500); - - expect(toastrSpy.warning).toHaveBeenCalled(); - expect(toastrSpy.error).not.toHaveBeenCalled(); - expect(toastrSpy.info).not.toHaveBeenCalled(); - expect(toastrSpy.success).not.toHaveBeenCalled(); - }); - - it('should call toastr.error for errors', () => { - service.showError('Test error'); - jest.advanceTimersByTime(500); - - expect(toastrSpy.error).toHaveBeenCalled(); - expect(toastrSpy.warning).not.toHaveBeenCalled(); - expect(toastrSpy.info).not.toHaveBeenCalled(); - expect(toastrSpy.success).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/teammapper-frontend/src/app/core/services/toast/toast.service.ts b/teammapper-frontend/src/app/core/services/toast/toast.service.ts deleted file mode 100644 index d11c7cce..00000000 --- a/teammapper-frontend/src/app/core/services/toast/toast.service.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Injectable, inject } from '@angular/core'; -import { ToastrService } from 'ngx-toastr'; -import { Subject } from 'rxjs'; -import { throttleTime } from 'rxjs/operators'; - -/** - * Service for displaying toast notifications using ngx-toastr - * Includes throttling to prevent toast spam - */ -@Injectable({ providedIn: 'root' }) -export class ToastService { - private readonly THROTTLE_TIME_MS = 500; - private readonly TOAST_DURATION_MS = 4000; - - private toastr = inject(ToastrService); - private toastQueue = new Subject(); - - constructor() { - // Setup throttled toast display - shows first message, ignores subsequent ones - this.toastQueue - .pipe( - throttleTime(this.THROTTLE_TIME_MS, undefined, { - leading: true, - trailing: false, - }) - ) - .subscribe(message => { - this.displayToast(message); - }); - } - - /** - * Display a validation correction notification - * @param nodeName Name of the node that was corrected - * @param correctionDetails Details about what was corrected - */ - showValidationCorrection(nodeName: string, correctionDetails: string): void { - const message = nodeName - ? `Node "${nodeName}" was auto-corrected: ${correctionDetails}` - : `Node was auto-corrected: ${correctionDetails}`; - - this.queueToast({ message, type: 'warning' }); - } - - /** - * Display a general info toast - * @param message Message to display - */ - showInfo(message: string): void { - this.queueToast({ message, type: 'info' }); - } - - /** - * Display a warning toast - * @param message Message to display - */ - showWarning(message: string): void { - this.queueToast({ message, type: 'warning' }); - } - - /** - * Display an error toast - * @param message Message to display - */ - showError(message: string): void { - this.queueToast({ message, type: 'error' }); - } - - /** - * Queue a toast message (will be throttled) - * @param toast Toast message to queue - */ - private queueToast(toast: ToastMessage): void { - this.toastQueue.next(toast); - } - - /** - * Display a toast immediately (after throttle) - * @param toast Toast message to display - */ - private displayToast(toast: ToastMessage): void { - const options = { timeOut: this.TOAST_DURATION_MS }; - - switch (toast.type) { - case 'error': - this.toastr.error(toast.message, '', options); - break; - case 'warning': - this.toastr.warning(toast.message, '', options); - break; - case 'info': - this.toastr.info(toast.message, '', options); - break; - case 'success': - this.toastr.success(toast.message, '', options); - break; - } - } -} - -interface ToastMessage { - message: string; - type: 'error' | 'warning' | 'info' | 'success'; -} diff --git a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.html b/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.html deleted file mode 100644 index 74e34ddb..00000000 --- a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.html +++ /dev/null @@ -1,19 +0,0 @@ -

- warning - {{ 'MODALS.CRITICAL_ERROR.TITLE' | translate }} -

- - -

{{ data.message }}

-

{{ 'MODALS.CRITICAL_ERROR.WARNING' | translate }}

-
- - - - diff --git a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.scss b/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.scss deleted file mode 100644 index e6369483..00000000 --- a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.scss +++ /dev/null @@ -1,39 +0,0 @@ -.error-title { - display: flex; - align-items: center; - gap: 0.5rem; - color: var(--mat-dialog-headline-color, rgba(0, 0, 0, 0.87)); -} - -.error-icon { - color: #f44336; // Material red-500 - font-size: 28px; - width: 28px; - height: 28px; -} - -.error-message { - font-size: 16px; - line-height: 1.5; - margin-bottom: 1rem; - color: var(--mat-dialog-content-color, rgba(0, 0, 0, 0.87)); -} - -.warning-text { - font-size: 14px; - line-height: 1.5; - color: var(--mat-dialog-content-color, rgba(0, 0, 0, 0.6)); - font-style: italic; -} - -mat-dialog-content { - padding: 1rem 1.5rem; -} - -mat-dialog-actions { - padding: 0.5rem 1.5rem 1rem; -} - -.reload-button { - min-width: 120px; -} diff --git a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.spec.ts b/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.spec.ts deleted file mode 100644 index dd06a002..00000000 --- a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { - MatDialogRef, - MAT_DIALOG_DATA, - MatDialogModule, -} from '@angular/material/dialog'; -import { MatIconModule } from '@angular/material/icon'; -import { MatButtonModule } from '@angular/material/button'; -import { - DialogCriticalErrorComponent, - CriticalErrorData, -} from './dialog-critical-error.component'; -import { NoopAnimationsModule } from '@angular/platform-browser/animations'; -import { TranslateModule } from '@ngx-translate/core'; - -describe('DialogCriticalErrorComponent', () => { - let component: DialogCriticalErrorComponent; - let fixture: ComponentFixture; - let mockDialogRef: jest.Mocked>; - let mockErrorData: CriticalErrorData; - - beforeEach(async () => { - mockDialogRef = { - disableClose: false, - close: jest.fn(), - } as unknown as jest.Mocked>; - - mockErrorData = { - code: 'SERVER_ERROR', - message: 'We lost connection to the server. Please reload the page.', - }; - - await TestBed.configureTestingModule({ - imports: [ - DialogCriticalErrorComponent, - MatDialogModule, - MatIconModule, - MatButtonModule, - NoopAnimationsModule, - TranslateModule.forRoot(), - ], - providers: [ - { provide: MatDialogRef, useValue: mockDialogRef }, - { provide: MAT_DIALOG_DATA, useValue: mockErrorData }, - ], - }).compileComponents(); - - fixture = TestBed.createComponent(DialogCriticalErrorComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create and display error message', () => { - expect(component).toBeTruthy(); - - const contentElement = - fixture.nativeElement.querySelector('mat-dialog-content'); - expect(contentElement.textContent).toContain(mockErrorData.message); - }); - - it('should be a blocking dialog that cannot be dismissed', () => { - expect(component.dialogRef.disableClose).toBe(true); - }); -}); diff --git a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.ts b/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.ts deleted file mode 100644 index 97da1e55..00000000 --- a/teammapper-frontend/src/app/modules/application/components/dialog-critical-error/dialog-critical-error.component.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Component, inject } from '@angular/core'; -import { - MAT_DIALOG_DATA, - MatDialogRef, - MatDialogTitle, - MatDialogContent, - MatDialogActions, -} from '@angular/material/dialog'; -import { MatButton } from '@angular/material/button'; -import { MatIcon } from '@angular/material/icon'; -import { TranslatePipe } from '@ngx-translate/core'; - -/** - * Data passed to critical error dialog component - */ -export interface CriticalErrorData { - code: string; - message: string; -} - -/** - * Critical Error Dialog Component - * Displays a blocking modal for critical errors that require page reload - * Cannot be dismissed by clicking outside or pressing escape - */ -@Component({ - selector: 'teammapper-dialog-critical-error', - templateUrl: './dialog-critical-error.component.html', - styleUrls: ['./dialog-critical-error.component.scss'], - imports: [ - MatDialogTitle, - MatDialogContent, - MatDialogActions, - MatButton, - MatIcon, - TranslatePipe, - ], -}) -export class DialogCriticalErrorComponent { - public dialogRef = - inject>(MatDialogRef); - public data = inject(MAT_DIALOG_DATA); - - constructor() { - // Prevent closing dialog by clicking outside or pressing escape - this.dialogRef.disableClose = true; - } - - /** - * Reload the page to recover from critical error - */ - reloadPage(): void { - window.location.reload(); - } -} diff --git a/teammapper-frontend/src/app/modules/application/components/toolbar/toolbar.component.spec.ts b/teammapper-frontend/src/app/modules/application/components/toolbar/toolbar.component.spec.ts index a176cb4a..c27471de 100644 --- a/teammapper-frontend/src/app/modules/application/components/toolbar/toolbar.component.spec.ts +++ b/teammapper-frontend/src/app/modules/application/components/toolbar/toolbar.component.spec.ts @@ -81,7 +81,7 @@ async function setupTestBed(): Promise { provide: SettingsService, useValue: { getCachedSystemSettings: jest.fn().mockReturnValue({ - featureFlags: { yjs: false, pictograms: false, ai: false }, + featureFlags: { pictograms: false, ai: false }, }), }, }, diff --git a/teammapper-frontend/src/app/shared/models/settings.model.ts b/teammapper-frontend/src/app/shared/models/settings.model.ts index 0f49616b..174841fb 100644 --- a/teammapper-frontend/src/app/shared/models/settings.model.ts +++ b/teammapper-frontend/src/app/shared/models/settings.model.ts @@ -44,5 +44,4 @@ interface Urls { interface FeatureFlags { pictograms: boolean; ai: boolean; - yjs: boolean; } diff --git a/teammapper-frontend/src/assets/i18n/de.json b/teammapper-frontend/src/assets/i18n/de.json index e5e23049..1a27967b 100644 --- a/teammapper-frontend/src/assets/i18n/de.json +++ b/teammapper-frontend/src/assets/i18n/de.json @@ -231,15 +231,7 @@ "EXPORT_IMAGE_ERROR": "Beim Bild-Export ist ein Fehler aufgetreten. Womöglich ist die Mindmap zu groß.", "MAP_COULD_NOT_BE_FOUND": "Mindmap konnte nicht gefunden werden!", "IMPORT_ERROR": "Mindmap konnte nicht importiert werden!", - "AI_MERMAID_ERROR": "Mermaid konnte nicht generiert werden.", - "OPERATION_FAILED_MAP_RELOADED": "Operation fehlgeschlagen - Karte vom Server neu geladen", - "NETWORK_TIMEOUT": "Die Verbindung zum Server wurde unterbrochen. Ihre Änderungen wurden möglicherweise nicht gespeichert.", - "SERVER_ERROR": "Der Server hat einen Fehler festgestellt. Ihre letzten Änderungen wurden möglicherweise nicht gespeichert.", - "AUTH_FAILED": "Authentifizierung fehlgeschlagen. Möglicherweise müssen Sie die Seite neu laden und sich erneut anmelden.", - "MALFORMED_REQUEST": "Es gab ein Problem mit Ihrer Anfrage. Bitte versuchen Sie es erneut.", - "RATE_LIMIT_EXCEEDED": "Zu viele Anfragen. Bitte warten Sie einen Moment, bevor Sie es erneut versuchen.", - "MALFORMED_RESPONSE": "Ungültige Antwort vom Server erhalten. Bitte laden Sie die Seite neu.", - "UNEXPECTED_ERROR": "Ein unerwarteter Fehler ist aufgetreten. Bitte laden Sie die Seite neu." + "AI_MERMAID_ERROR": "Mermaid konnte nicht generiert werden." }, "DELETE_MAP_SUCCESS": "Mindmap erfolgreich gelöscht!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid erfolgreich generiert", diff --git a/teammapper-frontend/src/assets/i18n/en.json b/teammapper-frontend/src/assets/i18n/en.json index 897c142a..7b2c8ed7 100644 --- a/teammapper-frontend/src/assets/i18n/en.json +++ b/teammapper-frontend/src/assets/i18n/en.json @@ -230,15 +230,7 @@ "EXPORT_IMAGE_ERROR": "An error has occurred during image export. The mind map may be too large.", "MAP_COULD_NOT_BE_FOUND": "Mindmap could not be found!", "IMPORT_ERROR": "Could not import mindmap", - "AI_MERMAID_ERROR": "Mermaid could not be generated", - "OPERATION_FAILED_MAP_RELOADED": "Operation failed - map reloaded from server", - "NETWORK_TIMEOUT": "We lost connection to the server. Your changes may not have been saved.", - "SERVER_ERROR": "The server encountered an error. Your recent changes may not have been saved.", - "AUTH_FAILED": "Authentication failed. You may need to reload and sign in again.", - "MALFORMED_REQUEST": "There was a problem with your request. Please try again.", - "RATE_LIMIT_EXCEEDED": "Too many requests. Please wait a moment before trying again.", - "MALFORMED_RESPONSE": "Received invalid response from server. Please reload the page.", - "UNEXPECTED_ERROR": "An unexpected error occurred. Please reload the page." + "AI_MERMAID_ERROR": "Mermaid could not be generated" }, "DELETE_MAP_SUCCESS": "Mindmap successfully deleted!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid successfully generated!", diff --git a/teammapper-frontend/src/assets/i18n/es.json b/teammapper-frontend/src/assets/i18n/es.json index e94429d9..b081e3a1 100644 --- a/teammapper-frontend/src/assets/i18n/es.json +++ b/teammapper-frontend/src/assets/i18n/es.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "Se ha producido un error durante la exportación de la imagen. El mapa mental puede ser demasiado grande.", "MAP_COULD_NOT_BE_FOUND": "No se ha encontrado el mapa mental.", "IMPORT_ERROR": "No se ha podido importar el mapa mental.", - "AI_MERMAID_ERROR": "No se pudo generar Mermaid", - "OPERATION_FAILED_MAP_RELOADED": "Operación fallida - mapa recargado desde el servidor", - "NETWORK_TIMEOUT": "Perdimos la conexión con el servidor. Es posible que sus cambios no se hayan guardado.", - "SERVER_ERROR": "El servidor encontró un error. Es posible que sus cambios recientes no se hayan guardado.", - "AUTH_FAILED": "Autenticación fallida. Es posible que deba recargar la página e iniciar sesión nuevamente.", - "MALFORMED_REQUEST": "Hubo un problema con su solicitud. Por favor, inténtelo de nuevo.", - "RATE_LIMIT_EXCEEDED": "Demasiadas solicitudes. Espere un momento antes de intentarlo de nuevo.", - "MALFORMED_RESPONSE": "Se recibió una respuesta inválida del servidor. Por favor, recargue la página.", - "UNEXPECTED_ERROR": "Ocurrió un error inesperado. Por favor, recargue la página." + "AI_MERMAID_ERROR": "No se pudo generar Mermaid" }, "DELETE_MAP_SUCCESS": "¡Mapa mental borrado con éxito!", "AI_MERMAID_GENERATED_SUCCESS": "¡Mermaid generado con éxito!", diff --git a/teammapper-frontend/src/assets/i18n/fr.json b/teammapper-frontend/src/assets/i18n/fr.json index 89b2121e..3bb4b1e8 100644 --- a/teammapper-frontend/src/assets/i18n/fr.json +++ b/teammapper-frontend/src/assets/i18n/fr.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "Une erreur s'est produite lors de l'exportation de l'image. Il se peut que la mind map soit trop grande.", "MAP_COULD_NOT_BE_FOUND": "La carte heuristique est introuvable !", "IMPORT_ERROR": "La Mindmap n'a pas pu être importée !", - "AI_MERMAID_ERROR": "Mermaid n'a pas pu être généré", - "OPERATION_FAILED_MAP_RELOADED": "Opération échouée - carte rechargée depuis le serveur", - "NETWORK_TIMEOUT": "Nous avons perdu la connexion au serveur. Vos modifications n'ont peut-être pas été enregistrées.", - "SERVER_ERROR": "Le serveur a rencontré une erreur. Vos modifications récentes n'ont peut-être pas été enregistrées.", - "AUTH_FAILED": "Échec de l'authentification. Vous devrez peut-être recharger la page et vous reconnecter.", - "MALFORMED_REQUEST": "Il y a eu un problème avec votre demande. Veuillez réessayer.", - "RATE_LIMIT_EXCEEDED": "Trop de demandes. Veuillez patienter un instant avant de réessayer.", - "MALFORMED_RESPONSE": "Réponse invalide reçue du serveur. Veuillez recharger la page.", - "UNEXPECTED_ERROR": "Une erreur inattendue s'est produite. Veuillez recharger la page." + "AI_MERMAID_ERROR": "Mermaid n'a pas pu être généré" }, "DELETE_MAP_SUCCESS": "Mindmap supprimée avec succès !", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid généré avec succès !", diff --git a/teammapper-frontend/src/assets/i18n/it.json b/teammapper-frontend/src/assets/i18n/it.json index 51820c06..9dbd0ca9 100644 --- a/teammapper-frontend/src/assets/i18n/it.json +++ b/teammapper-frontend/src/assets/i18n/it.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "Si è verificato un errore durante l'esportazione dell'immagine. La mappa mentale potrebbe essere troppo grande.", "MAP_COULD_NOT_BE_FOUND": "Impossibile trovare la mappa mentale!", "IMPORT_ERROR": "Non è stato possibile importare la mappa mentale!", - "AI_MERMAID_ERROR": "Mermaid non può essere generato", - "OPERATION_FAILED_MAP_RELOADED": "Operazione fallita - mappa ricaricata dal server", - "NETWORK_TIMEOUT": "Abbiamo perso la connessione al server. Le tue modifiche potrebbero non essere state salvate.", - "SERVER_ERROR": "Il server ha riscontrato un errore. Le tue modifiche recenti potrebbero non essere state salvate.", - "AUTH_FAILED": "Autenticazione fallita. Potrebbe essere necessario ricaricare la pagina e accedere nuovamente.", - "MALFORMED_REQUEST": "Si è verificato un problema con la tua richiesta. Riprova.", - "RATE_LIMIT_EXCEEDED": "Troppe richieste. Attendi un momento prima di riprovare.", - "MALFORMED_RESPONSE": "Ricevuta risposta non valida dal server. Ricarica la pagina.", - "UNEXPECTED_ERROR": "Si è verificato un errore imprevisto. Ricarica la pagina." + "AI_MERMAID_ERROR": "Mermaid non può essere generato" }, "DELETE_MAP_SUCCESS": "Mappa mentale cancellata con successo!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid generato con successo!", diff --git a/teammapper-frontend/src/assets/i18n/ja.json b/teammapper-frontend/src/assets/i18n/ja.json index b9667afb..d88a02b7 100644 --- a/teammapper-frontend/src/assets/i18n/ja.json +++ b/teammapper-frontend/src/assets/i18n/ja.json @@ -230,15 +230,7 @@ "EXPORT_IMAGE_ERROR": "画像のエクスポート中にエラーが発生しました。マインドマップが大きすぎる可能性があります。", "MAP_COULD_NOT_BE_FOUND": "マインドマップが見つかりませんでした!", "IMPORT_ERROR": "マインドマップをインポートできませんでした", - "AI_MERMAID_ERROR": "Mermaidを生成できませんでした", - "OPERATION_FAILED_MAP_RELOADED": "操作に失敗しました - サーバーからマップを再読み込みしました", - "NETWORK_TIMEOUT": "サーバーへの接続が切断されました。変更が保存されていない可能性があります。", - "SERVER_ERROR": "サーバーでエラーが発生しました。最近の変更が保存されていない可能性があります。", - "AUTH_FAILED": "認証に失敗しました。ページを再読み込みしてサインインし直してください。", - "MALFORMED_REQUEST": "リクエストに問題が発生しました。もう一度お試しください。", - "RATE_LIMIT_EXCEEDED": "リクエストが多すぎます。しばらく待ってから再試行してください。", - "MALFORMED_RESPONSE": "サーバーから無効なレスポンスを受信しました。ページを再読み込みしてください。", - "UNEXPECTED_ERROR": "予期しないエラーが発生しました。ページを再読み込みしてください。" + "AI_MERMAID_ERROR": "Mermaidを生成できませんでした" }, "DELETE_MAP_SUCCESS": "マインドマップを削除しました!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaidを生成しました!", diff --git a/teammapper-frontend/src/assets/i18n/pt-br.json b/teammapper-frontend/src/assets/i18n/pt-br.json index 67ac6a06..6cf44ff8 100644 --- a/teammapper-frontend/src/assets/i18n/pt-br.json +++ b/teammapper-frontend/src/assets/i18n/pt-br.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "Ocorreu um erro durante a exportação da imagem. O mapa mental pode ser muito grande.", "MAP_COULD_NOT_BE_FOUND": "Não foi possível encontrar o mapa mental!", "IMPORT_ERROR": "O mapa mental não pôde ser importado!", - "AI_MERMAID_ERROR": "Mermaid não pôde ser gerado", - "OPERATION_FAILED_MAP_RELOADED": "Operação falhou - mapa recarregado do servidor", - "NETWORK_TIMEOUT": "Perdemos a conexão com o servidor. Suas alterações podem não ter sido salvas.", - "SERVER_ERROR": "O servidor encontrou um erro. Suas alterações recentes podem não ter sido salvas.", - "AUTH_FAILED": "Autenticação falhou. Você pode precisar recarregar a página e fazer login novamente.", - "MALFORMED_REQUEST": "Houve um problema com sua solicitação. Por favor, tente novamente.", - "RATE_LIMIT_EXCEEDED": "Muitas solicitações. Aguarde um momento antes de tentar novamente.", - "MALFORMED_RESPONSE": "Resposta inválida recebida do servidor. Por favor, recarregue a página.", - "UNEXPECTED_ERROR": "Ocorreu um erro inesperado. Por favor, recarregue a página." + "AI_MERMAID_ERROR": "Mermaid não pôde ser gerado" }, "DELETE_MAP_SUCCESS": "Mapa mental excluído com sucesso!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid gerado com sucesso!", diff --git a/teammapper-frontend/src/assets/i18n/zh-cn.json b/teammapper-frontend/src/assets/i18n/zh-cn.json index cfdfb338..183a5023 100644 --- a/teammapper-frontend/src/assets/i18n/zh-cn.json +++ b/teammapper-frontend/src/assets/i18n/zh-cn.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "导出图像时发生错误。思维导图可能过大。", "MAP_COULD_NOT_BE_FOUND": "找不到思维导图!", "IMPORT_ERROR": "无法导入思维导图!", - "AI_MERMAID_ERROR": "无法生成Mermaid", - "OPERATION_FAILED_MAP_RELOADED": "操作失败 - 已从服务器重新加载地图", - "NETWORK_TIMEOUT": "我们与服务器失去了连接。您的更改可能尚未保存。", - "SERVER_ERROR": "服务器遇到错误。您最近的更改可能尚未保存。", - "AUTH_FAILED": "身份验证失败。您可能需要重新加载页面并重新登录。", - "MALFORMED_REQUEST": "您的请求出现问题。请重试。", - "RATE_LIMIT_EXCEEDED": "请求过多。请稍等片刻再试。", - "MALFORMED_RESPONSE": "从服务器收到无效响应。请重新加载页面。", - "UNEXPECTED_ERROR": "发生意外错误。请重新加载页面。" + "AI_MERMAID_ERROR": "无法生成Mermaid" }, "DELETE_MAP_SUCCESS": "思维导图已成功删除!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid生成成功!", diff --git a/teammapper-frontend/src/assets/i18n/zh-tw.json b/teammapper-frontend/src/assets/i18n/zh-tw.json index 564daed1..2e8e6c00 100644 --- a/teammapper-frontend/src/assets/i18n/zh-tw.json +++ b/teammapper-frontend/src/assets/i18n/zh-tw.json @@ -228,15 +228,7 @@ "EXPORT_IMAGE_ERROR": "An error has occurred during image export. The mind map may be too large.", "MAP_COULD_NOT_BE_FOUND": "Mindmap could not be found!", "IMPORT_ERROR": "Mindmap could not be imported!", - "AI_MERMAID_ERROR": "無法生成Mermaid", - "OPERATION_FAILED_MAP_RELOADED": "操作失敗 - 已從伺服器重新載入地圖", - "NETWORK_TIMEOUT": "我們與伺服器失去連接。您的變更可能尚未儲存。", - "SERVER_ERROR": "伺服器遇到錯誤。您最近的變更可能尚未儲存。", - "AUTH_FAILED": "身份驗證失敗。您可能需要重新載入頁面並重新登入。", - "MALFORMED_REQUEST": "您的請求出現問題。請重試。", - "RATE_LIMIT_EXCEEDED": "請求過多。請稍等片刻再試。", - "MALFORMED_RESPONSE": "從伺服器收到無效回應。請重新載入頁面。", - "UNEXPECTED_ERROR": "發生意外錯誤。請重新載入頁面。" + "AI_MERMAID_ERROR": "無法生成Mermaid" }, "DELETE_MAP_SUCCESS": "Mindmap successfully deleted!", "AI_MERMAID_GENERATED_SUCCESS": "Mermaid生成成功!", diff --git a/teammapper-frontend/src/proxy.conf.json b/teammapper-frontend/src/proxy.conf.json index e6cf7fd1..93e77963 100644 --- a/teammapper-frontend/src/proxy.conf.json +++ b/teammapper-frontend/src/proxy.conf.json @@ -1,10 +1,4 @@ { - "/socket.io": { - "target": "http://localhost:3000", - "secure": false, - "changeOrigin": true, - "ws": true - }, "/yjs": { "target": "http://localhost:3000", "secure": false, diff --git a/teammapper-frontend/src/test/mocks/utils-service.mock.ts b/teammapper-frontend/src/test/mocks/utils-service.mock.ts index eee48b55..250bdef3 100644 --- a/teammapper-frontend/src/test/mocks/utils-service.mock.ts +++ b/teammapper-frontend/src/test/mocks/utils-service.mock.ts @@ -1,28 +1,5 @@ import { UtilsService } from '../../app/core/services/utils/utils.service'; -/** - * Centralized translation keys for tests - * Keeps test translations consistent with production keys - */ -export const MOCK_TRANSLATIONS: Record = { - 'TOASTS.ERRORS.OPERATION_FAILED_MAP_RELOADED': - 'Operation failed - map reloaded from server', - 'TOASTS.ERRORS.MALFORMED_RESPONSE': - 'Received invalid response from server. Please reload the page.', - 'TOASTS.ERRORS.SERVER_ERROR': - 'The server encountered an error. Your recent changes may not have been saved.', - 'TOASTS.ERRORS.NETWORK_TIMEOUT': - 'We lost connection to the server. Your changes may not have been saved.', - 'TOASTS.ERRORS.AUTH_FAILED': - 'Authentication failed. You may need to reload and sign in again.', - 'TOASTS.ERRORS.MALFORMED_REQUEST': - 'There was a problem with your request. Please try again.', - 'TOASTS.ERRORS.RATE_LIMIT_EXCEEDED': - 'Too many requests. Please wait a moment before trying again.', - 'TOASTS.ERRORS.UNEXPECTED_ERROR': - 'An unexpected error occurred. Please reload the page.', -}; - /** * Creates a properly configured mock UtilsService for testing * Includes all instance methods with sensible defaults @@ -36,10 +13,8 @@ export function createMockUtilsService(): jest.Mocked { blobToBase64: jest.fn(), } as unknown as jest.Mocked; - // Configure translate spy with realistic return values - mock.translate.mockImplementation(async (key: string) => { - return MOCK_TRANSLATIONS[key] || key; - }); + // Configure translate spy to echo the key back + mock.translate.mockImplementation(async (key: string) => key); // Configure confirmDialog with default behavior (returns true) mock.confirmDialog.mockResolvedValue(true); From cd8dd79284ddd5821fcc02507b32d4c479dc6133 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 24 May 2026 17:49:21 +0200 Subject: [PATCH 10/14] archive old specs (#1301) --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/ai-mindmap-generation/spec.md | 0 .../specs/import-export/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/yjs-write-access/spec.md | 0 .../tasks.md | 0 .../.openspec.yaml | 0 .../2026-05-24-yjs-introduction}/design.md | 0 .../2026-05-24-yjs-introduction}/proposal.md | 0 .../specs/yjs-awareness/spec.md | 0 .../specs/yjs-bridge/spec.md | 0 .../specs/yjs-persistence/spec.md | 0 .../specs/yjs-sync/spec.md | 0 .../yjs-ws-connection-resilience/spec.md | 0 .../specs/yjs-ws-lifecycle-integrity/spec.md | 0 .../specs/yjs-ws-resource-protection/spec.md | 0 .../2026-05-24-yjs-introduction}/tasks.md | 0 .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/yjs-bridge/spec.md | 0 .../specs/yjs-undo/spec.md | 0 .../tasks.md | 0 openspec/specs/ai-mindmap-generation/spec.md | 7 +- openspec/specs/import-export/spec.md | 17 +-- openspec/specs/yjs-awareness/spec.md | 64 ++++++++ openspec/specs/yjs-bridge/spec.md | 128 ++++++++++++++++ openspec/specs/yjs-persistence/spec.md | 56 +++++++ openspec/specs/yjs-sync/spec.md | 116 ++++++++++++++ openspec/specs/yjs-undo/spec.md | 143 ++++++++++++++++++ openspec/specs/yjs-write-access/spec.md | 69 +++++++++ .../yjs-ws-connection-resilience/spec.md | 63 ++++++++ .../specs/yjs-ws-lifecycle-integrity/spec.md | 86 +++++++++++ .../specs/yjs-ws-resource-protection/spec.md | 72 +++++++++ 39 files changed, 804 insertions(+), 17 deletions(-) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/.openspec.yaml (100%) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/design.md (100%) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/proposal.md (100%) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/specs/ai-mindmap-generation/spec.md (100%) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/specs/import-export/spec.md (100%) rename openspec/changes/{remove-ai-from-mermaid-import => archive/2026-05-24-remove-ai-from-mermaid-import}/tasks.md (100%) rename openspec/changes/{yjs-auth-read-write-access => archive/2026-05-24-yjs-auth-read-write-access}/.openspec.yaml (100%) rename openspec/changes/{yjs-auth-read-write-access => archive/2026-05-24-yjs-auth-read-write-access}/design.md (100%) rename openspec/changes/{yjs-auth-read-write-access => archive/2026-05-24-yjs-auth-read-write-access}/proposal.md (100%) rename openspec/changes/{yjs-auth-read-write-access => archive/2026-05-24-yjs-auth-read-write-access}/specs/yjs-write-access/spec.md (100%) rename openspec/changes/{yjs-auth-read-write-access => archive/2026-05-24-yjs-auth-read-write-access}/tasks.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/.openspec.yaml (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/design.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/proposal.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-awareness/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-bridge/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-persistence/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-sync/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-ws-connection-resilience/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-ws-lifecycle-integrity/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/specs/yjs-ws-resource-protection/spec.md (100%) rename openspec/changes/{yjs-introduction => archive/2026-05-24-yjs-introduction}/tasks.md (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/.openspec.yaml (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/design.md (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/proposal.md (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/specs/yjs-bridge/spec.md (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/specs/yjs-undo/spec.md (100%) rename openspec/changes/{yjs-undo-redo-manager => archive/2026-05-24-yjs-undo-redo-manager}/tasks.md (100%) create mode 100644 openspec/specs/yjs-awareness/spec.md create mode 100644 openspec/specs/yjs-bridge/spec.md create mode 100644 openspec/specs/yjs-persistence/spec.md create mode 100644 openspec/specs/yjs-sync/spec.md create mode 100644 openspec/specs/yjs-undo/spec.md create mode 100644 openspec/specs/yjs-write-access/spec.md create mode 100644 openspec/specs/yjs-ws-connection-resilience/spec.md create mode 100644 openspec/specs/yjs-ws-lifecycle-integrity/spec.md create mode 100644 openspec/specs/yjs-ws-resource-protection/spec.md diff --git a/openspec/changes/remove-ai-from-mermaid-import/.openspec.yaml b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/.openspec.yaml similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/.openspec.yaml rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/.openspec.yaml diff --git a/openspec/changes/remove-ai-from-mermaid-import/design.md b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/design.md similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/design.md rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/design.md diff --git a/openspec/changes/remove-ai-from-mermaid-import/proposal.md b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/proposal.md similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/proposal.md rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/proposal.md diff --git a/openspec/changes/remove-ai-from-mermaid-import/specs/ai-mindmap-generation/spec.md b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/specs/ai-mindmap-generation/spec.md similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/specs/ai-mindmap-generation/spec.md rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/specs/ai-mindmap-generation/spec.md diff --git a/openspec/changes/remove-ai-from-mermaid-import/specs/import-export/spec.md b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/specs/import-export/spec.md similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/specs/import-export/spec.md rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/specs/import-export/spec.md diff --git a/openspec/changes/remove-ai-from-mermaid-import/tasks.md b/openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/tasks.md similarity index 100% rename from openspec/changes/remove-ai-from-mermaid-import/tasks.md rename to openspec/changes/archive/2026-05-24-remove-ai-from-mermaid-import/tasks.md diff --git a/openspec/changes/yjs-auth-read-write-access/.openspec.yaml b/openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/.openspec.yaml similarity index 100% rename from openspec/changes/yjs-auth-read-write-access/.openspec.yaml rename to openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/.openspec.yaml diff --git a/openspec/changes/yjs-auth-read-write-access/design.md b/openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/design.md similarity index 100% rename from openspec/changes/yjs-auth-read-write-access/design.md rename to openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/design.md diff --git a/openspec/changes/yjs-auth-read-write-access/proposal.md b/openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/proposal.md similarity index 100% rename from openspec/changes/yjs-auth-read-write-access/proposal.md rename to openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/proposal.md diff --git a/openspec/changes/yjs-auth-read-write-access/specs/yjs-write-access/spec.md b/openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/specs/yjs-write-access/spec.md similarity index 100% rename from openspec/changes/yjs-auth-read-write-access/specs/yjs-write-access/spec.md rename to openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/specs/yjs-write-access/spec.md diff --git a/openspec/changes/yjs-auth-read-write-access/tasks.md b/openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/tasks.md similarity index 100% rename from openspec/changes/yjs-auth-read-write-access/tasks.md rename to openspec/changes/archive/2026-05-24-yjs-auth-read-write-access/tasks.md diff --git a/openspec/changes/yjs-introduction/.openspec.yaml b/openspec/changes/archive/2026-05-24-yjs-introduction/.openspec.yaml similarity index 100% rename from openspec/changes/yjs-introduction/.openspec.yaml rename to openspec/changes/archive/2026-05-24-yjs-introduction/.openspec.yaml diff --git a/openspec/changes/yjs-introduction/design.md b/openspec/changes/archive/2026-05-24-yjs-introduction/design.md similarity index 100% rename from openspec/changes/yjs-introduction/design.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/design.md diff --git a/openspec/changes/yjs-introduction/proposal.md b/openspec/changes/archive/2026-05-24-yjs-introduction/proposal.md similarity index 100% rename from openspec/changes/yjs-introduction/proposal.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/proposal.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-awareness/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-awareness/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-awareness/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-awareness/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-bridge/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-bridge/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-bridge/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-bridge/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-persistence/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-persistence/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-persistence/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-persistence/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-sync/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-sync/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-sync/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-sync/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-ws-connection-resilience/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-connection-resilience/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-ws-connection-resilience/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-connection-resilience/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md diff --git a/openspec/changes/yjs-introduction/specs/yjs-ws-resource-protection/spec.md b/openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-resource-protection/spec.md similarity index 100% rename from openspec/changes/yjs-introduction/specs/yjs-ws-resource-protection/spec.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/specs/yjs-ws-resource-protection/spec.md diff --git a/openspec/changes/yjs-introduction/tasks.md b/openspec/changes/archive/2026-05-24-yjs-introduction/tasks.md similarity index 100% rename from openspec/changes/yjs-introduction/tasks.md rename to openspec/changes/archive/2026-05-24-yjs-introduction/tasks.md diff --git a/openspec/changes/yjs-undo-redo-manager/.openspec.yaml b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/.openspec.yaml similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/.openspec.yaml rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/.openspec.yaml diff --git a/openspec/changes/yjs-undo-redo-manager/design.md b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/design.md similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/design.md rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/design.md diff --git a/openspec/changes/yjs-undo-redo-manager/proposal.md b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/proposal.md similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/proposal.md rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/proposal.md diff --git a/openspec/changes/yjs-undo-redo-manager/specs/yjs-bridge/spec.md b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/specs/yjs-bridge/spec.md similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/specs/yjs-bridge/spec.md rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/specs/yjs-bridge/spec.md diff --git a/openspec/changes/yjs-undo-redo-manager/specs/yjs-undo/spec.md b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/specs/yjs-undo/spec.md similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/specs/yjs-undo/spec.md rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/specs/yjs-undo/spec.md diff --git a/openspec/changes/yjs-undo-redo-manager/tasks.md b/openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/tasks.md similarity index 100% rename from openspec/changes/yjs-undo-redo-manager/tasks.md rename to openspec/changes/archive/2026-05-24-yjs-undo-redo-manager/tasks.md diff --git a/openspec/specs/ai-mindmap-generation/spec.md b/openspec/specs/ai-mindmap-generation/spec.md index 4686bda9..487e4fb9 100644 --- a/openspec/specs/ai-mindmap-generation/spec.md +++ b/openspec/specs/ai-mindmap-generation/spec.md @@ -99,12 +99,7 @@ The system SHALL instruct the AI to generate mindmap syntax in the user's reques ### Requirement: AI generation dialog SHALL reflect generation state immediately -The AI generation dialogs SHALL update their visible state immediately when async operations complete, without requiring additional user interaction. This applies to both the standalone AI import dialog and the mermaid dialog's AI section. - -#### Scenario: AI generation result appears immediately in mermaid dialog -- **WHEN** the user triggers AI generation in the mermaid import dialog -- **AND** the server returns a successful response -- **THEN** the generated mermaid syntax SHALL appear in the text area immediately without the user needing to click or interact with any other element +The AI generation dialog SHALL update its visible state immediately when async operations complete, without requiring additional user interaction. This applies to the standalone AI import dialog only. #### Scenario: AI generation loading state updates immediately - **WHEN** the user triggers AI generation in the standalone AI import dialog diff --git a/openspec/specs/import-export/spec.md b/openspec/specs/import-export/spec.md index 2ed98ee7..22fc1c94 100644 --- a/openspec/specs/import-export/spec.md +++ b/openspec/specs/import-export/spec.md @@ -26,13 +26,18 @@ The system SHALL allow users to import a mind map by uploading a JSON file. The - **AND** each expected node SHALL appear exactly once ### Requirement: User can import a mind map from Mermaid syntax -The system SHALL open a dialog with a text area when the user selects the Mermaid import option. Users SHALL enter Mermaid mindmap syntax and trigger import. On success, the dialog SHALL close and the nodes from the Mermaid syntax SHALL appear on the map. +The system SHALL open a dialog with a text area when the user selects the Mermaid import option. Users SHALL enter Mermaid mindmap syntax and trigger import. On success, the dialog SHALL close and the nodes from the Mermaid syntax SHALL appear on the map. The dialog SHALL NOT include any AI generation functionality. #### Scenario: Import Mermaid mindmap via dialog - **WHEN** the user enters valid Mermaid mindmap syntax and clicks import - **THEN** the dialog SHALL close - **AND** all nodes defined in the Mermaid syntax SHALL be visible on the map +#### Scenario: Mermaid import dialog has no AI generation section +- **WHEN** the user opens the Mermaid import dialog +- **THEN** the dialog SHALL NOT display any AI description input field +- **AND** the dialog SHALL NOT display any AI generation button + ### Requirement: Mermaid import preserves branch color assignment The system SHALL assign distinct branch colors when importing a Mermaid mindmap with multiple first-level branches. Child branches SHALL share their parent's color, resulting in as many unique colors as there are first-level branches. @@ -66,13 +71,3 @@ The system SHALL allow exporting mind maps as SVG, PNG, JPG images and PDF docum #### Scenario: Export PDF - **WHEN** the user clicks "Document (.pdf)" in the export menu - **THEN** the map SHALL be downloaded as a PDF document - -### Requirement: Mermaid import dialog SHALL reflect AI-generated content immediately - -When AI generation populates the mermaid text area, the content SHALL be visible to the user immediately upon generation completing, without requiring any additional user interaction such as clicking or tabbing. - -#### Scenario: AI-generated mermaid content visible without interaction -- **WHEN** the user enters a description and clicks the AI generation button in the mermaid import dialog -- **AND** the server returns generated mermaid syntax -- **THEN** the mermaid text area SHALL display the generated content immediately -- **AND** the user SHALL be able to click "Import" without any additional interaction to reveal the content diff --git a/openspec/specs/yjs-awareness/spec.md b/openspec/specs/yjs-awareness/spec.md new file mode 100644 index 00000000..d9d0d960 --- /dev/null +++ b/openspec/specs/yjs-awareness/spec.md @@ -0,0 +1,64 @@ +## ADDED Requirements + +### Requirement: Client presence via Yjs Awareness +Each connected client SHALL announce its presence using the Yjs Awareness protocol. The awareness state SHALL include the client's assigned color and current node selection. The awareness state SHALL be set when the client connects and updated when the selection changes. + +#### Scenario: Client sets initial awareness state on connect +- **WHEN** a client connects to a map via the WebsocketProvider +- **THEN** the client SHALL set its awareness state with `{ color: , selectedNodeId: null }` + +#### Scenario: Client updates awareness on node selection +- **WHEN** a user selects a node in the map +- **THEN** the client SHALL update its awareness state to `{ color: , selectedNodeId: }` + +#### Scenario: Client updates awareness on node deselection +- **WHEN** a user deselects a node +- **THEN** the client SHALL update its awareness state to `{ color: , selectedNodeId: null }` + +### Requirement: Client color assignment +Each client SHALL be assigned a random color from the existing `COLORS` palette on connection. If the chosen color collides with another connected client's color (detected via awareness states), a random fallback color SHALL be generated. + +#### Scenario: Client picks a unique color +- **WHEN** a client connects and its randomly chosen color is not used by any other client +- **THEN** the client SHALL use that color in its awareness state + +#### Scenario: Client color collision +- **WHEN** a client connects and its randomly chosen color matches another client's awareness color +- **THEN** the client SHALL generate a random hex color as a fallback + +### Requirement: Client list derived from awareness states +The frontend SHALL derive the list of connected clients and their colors from `awareness.getStates()`. There SHALL be no separate server-side client tracking (the `cache-manager` client cache is removed). + +#### Scenario: New client appears in the list +- **WHEN** a new client connects and sets its awareness state +- **THEN** all other clients SHALL receive an awareness change event and update their client list to include the new client's color + +#### Scenario: Client disconnects and disappears from list +- **WHEN** a client disconnects +- **THEN** the Yjs Awareness protocol SHALL automatically remove the client's state and all other clients SHALL receive a change event to update their client list + +### Requirement: Node selection highlighting from awareness +The frontend SHALL observe awareness state changes and highlight nodes that other clients have selected. When a remote client's `selectedNodeId` changes, the local client SHALL call MMP's `highlightNode` method with the remote client's color. + +#### Scenario: Remote client selects a node +- **WHEN** a remote client's awareness state changes to `{ selectedNodeId: "node-1" }` +- **THEN** the local client SHALL highlight "node-1" with the remote client's color via `mmpService.highlightNode()` + +#### Scenario: Remote client deselects a node +- **WHEN** a remote client's awareness state changes from `{ selectedNodeId: "node-1" }` to `{ selectedNodeId: null }` +- **THEN** the local client SHALL remove the highlight from "node-1" (or apply the next client's color if another client still has it selected) + +#### Scenario: Remote client selects a node that no longer exists locally +- **WHEN** a remote client's awareness state references a node ID that does not exist in the local MMP instance +- **THEN** the local client SHALL ignore the highlight update without error + +### Requirement: Connection status from WebSocket provider +The frontend SHALL derive the connection status (`connected` / `disconnected`) from the `WebsocketProvider`'s connection state, replacing the previous Socket.io-based connection tracking. + +#### Scenario: Connection established +- **WHEN** the WebsocketProvider successfully connects +- **THEN** the connection status observable SHALL emit `'connected'` + +#### Scenario: Connection lost +- **WHEN** the WebSocket connection is interrupted +- **THEN** the connection status observable SHALL emit `'disconnected'` diff --git a/openspec/specs/yjs-bridge/spec.md b/openspec/specs/yjs-bridge/spec.md new file mode 100644 index 00000000..6f531af3 --- /dev/null +++ b/openspec/specs/yjs-bridge/spec.md @@ -0,0 +1,128 @@ +## ADDED Requirements + +### Requirement: Local MMP events write to Y.Doc +When the user performs an action in MMP (create, update, remove node), the MapSyncService bridge SHALL write the change to the local Y.Doc. The bridge SHALL NOT send individual network messages — Yjs handles synchronization automatically. All local write operations SHALL use `'local'` as the transaction origin so that `Y.UndoManager` can track them. + +#### Scenario: User creates a node +- **WHEN** MMP fires a `nodeCreate` event with the new node's `ExportNodeProperties` +- **THEN** the bridge SHALL create a new Y.Map entry in `yDoc.getMap('nodes')` with the node's ID as key and all properties as values +- **AND** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` + +#### Scenario: User updates a node property +- **WHEN** MMP fires a `nodeUpdate` event with the updated property and value +- **THEN** the bridge SHALL update the corresponding property on the node's Y.Map entry in the `nodes` map +- **AND** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` + +#### Scenario: User removes a node +- **WHEN** MMP fires a `nodeRemove` event with the removed node's properties +- **THEN** the bridge SHALL delete the node's entry and all descendant entries from `yDoc.getMap('nodes')` +- **AND** the `yDoc.transact()` call SHALL use `'local'` as the origin + +#### Scenario: User pastes multiple nodes +- **WHEN** MMP fires a `nodePaste` event with an array of node properties +- **THEN** the bridge SHALL add all nodes to the Y.Doc `nodes` map within a single `yDoc.transact()` call +- **AND** the `yDoc.transact()` call SHALL use `'local'` as the origin + +#### Scenario: User updates map options +- **WHEN** the user changes map options (e.g., map name) +- **THEN** the bridge SHALL update the corresponding fields in `yDoc.getMap('mapOptions')` +- **AND** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` + +### Requirement: Remote Y.Doc changes apply to MMP +The bridge SHALL observe the Y.Doc for remote changes and apply them to MMP using its existing API. Remote changes SHALL be applied with `notifyWithEvent: false` to prevent echo loops. + +#### Scenario: Remote node added +- **WHEN** the Y.Doc `nodes` map emits an add event from a remote transaction +- **THEN** the bridge SHALL call `mmpService.addNode()` with the node properties and `notifyWithEvent: false` + +#### Scenario: Remote node property updated +- **WHEN** the Y.Doc `nodes` map emits an update event for an existing node from a remote transaction +- **THEN** the bridge SHALL call `mmpService.updateNode()` with the changed property, value, `notifyWithEvent: false`, and the node ID + +#### Scenario: Remote node removed +- **WHEN** the Y.Doc `nodes` map emits a delete event from a remote transaction +- **THEN** the bridge SHALL call `mmpService.removeNode()` with the node ID and `notifyWithEvent: false`, but only if the node exists in MMP + +#### Scenario: Remote map options updated +- **WHEN** the Y.Doc `mapOptions` map emits a change event from a remote transaction +- **THEN** the bridge SHALL call `mmpService.updateAdditionalMapOptions()` with the updated options + +### Requirement: Echo prevention +The bridge SHALL prevent echo loops where a local MMP event writes to Y.Doc, which then triggers a Y.Doc observation that would re-apply the same change to MMP. The bridge SHALL use origin-based filtering: skip transactions that are local AND whose origin is NOT the `Y.UndoManager` instance. UndoManager-originated transactions are local but SHALL be applied to MMP because MMP was not the source of the change. + +#### Scenario: Local change does not echo back +- **WHEN** the user creates a node locally (MMP event -> Y.Doc write with origin `'local'`) +- **THEN** the Y.Doc observer SHALL detect the transaction as local with origin `'local'` and SHALL NOT apply it back to MMP + +#### Scenario: Remote change is applied +- **WHEN** a remote client creates a node (Y.Doc sync update received) +- **THEN** the Y.Doc observer SHALL detect the change as `transaction.local === false` and SHALL apply it to MMP + +#### Scenario: UndoManager change is applied to MMP +- **WHEN** the user triggers undo/redo and `Y.UndoManager` creates a local transaction (origin = UndoManager instance) +- **THEN** the Y.Doc observer SHALL detect `transaction.origin === yUndoManager` and SHALL apply the changes to MMP +- **AND** MMP SHALL re-render the affected nodes + +### Requirement: Coordinate preservation on node restore +When the Y.Doc observer applies a node-add to MMP (from undo/redo or remote sync), and the node's Y.Map contains non-zero coordinates, the bridge SHALL ensure those coordinates are used as the node's position. The MMP layer SHALL NOT recalculate positions for nodes that already have stored coordinates from Y.Doc. + +**Bug reference:** `nodes.ts:124` unconditionally calls `calculateCoordinates(node)`, overwriting the coordinates passed in from Y.Doc with freshly calculated positions. This causes restored nodes to appear at "append to bottom of siblings" positions rather than their original locations. + +#### Scenario: Undo of delete preserves original coordinates +- **WHEN** a node at coordinates `{x: 200, y: -120}` is deleted and the user triggers undo +- **AND** the Y.UndoManager restores the node to Y.Doc with its original coordinates +- **THEN** the bridge SHALL pass the coordinates from Y.Doc to MMP +- **AND** MMP SHALL render the node at `{x: 200, y: -120}`, NOT at a recalculated position + +#### Scenario: Undo of subtree delete preserves all node coordinates +- **WHEN** a parent node with descendants is deleted (single transaction) and the user triggers undo +- **THEN** ALL restored nodes (parent and descendants) SHALL retain their original coordinates from Y.Doc +- **AND** no node SHALL be repositioned by `calculateCoordinates()` + +#### Scenario: Remote node-add with coordinates preserves position +- **WHEN** a remote client creates a node with specific coordinates +- **AND** the Y.Doc sync delivers the node with those coordinates +- **THEN** the bridge SHALL apply the node to MMP at the coordinates from Y.Doc + +### Requirement: Parent-first ordering on batch node restore +When a Y.Doc transaction adds multiple nodes (e.g., undo of a subtree delete), the observer SHALL process parent nodes before their children. This ensures that when `addNode()` is called for a child, the parent already exists in MMP's node map and can be resolved. + +**Bug reference:** `keysChanged.forEach` in `handleTopLevelNodeChanges` (`map-sync.service.ts:1557`) iterates in Y.Map internal order, which may not be parent-first. A child node processed before its parent causes `getNode(parentId)` to return `undefined`, corrupting the parent reference and position. + +#### Scenario: Subtree restore processes parent before children +- **GIVEN** a parent node `A` with child `B` and grandchild `C` were deleted in one transaction +- **WHEN** the user triggers undo and all three nodes are restored in one transaction +- **THEN** the observer SHALL add `A` to MMP before `B`, and `B` before `C` +- **AND** each child SHALL have a valid parent reference in MMP at the time of insertion + +#### Scenario: Flat siblings restored in any order +- **GIVEN** two sibling nodes `B` and `C` (both children of `A`) were deleted +- **WHEN** the undo restores them +- **THEN** `A` SHALL be added first (parent-first), but `B` and `C` MAY be added in any order relative to each other + +### Requirement: Map import via Y.Doc transaction +When a user imports a map, the bridge SHALL clear the Y.Doc `nodes` map and repopulate it with the imported data inside a single `yDoc.transact()` call. Yjs SHALL sync the new state to all connected clients automatically. + +#### Scenario: User imports a map +- **WHEN** the user triggers a map import with new map data +- **THEN** the bridge SHALL execute a Y.Doc transaction that deletes all entries from the `nodes` map and adds all imported nodes as new entries +- **AND** all connected clients SHALL receive the Y.Doc update and re-render via their bridge observers + +### Requirement: Map deletion handling +When a map is deleted via the REST API, the server SHALL destroy the Y.Doc and close all WebSocket connections for that map. The frontend SHALL detect the WebSocket close and handle cleanup. + +#### Scenario: Admin deletes a map +- **WHEN** the admin calls the map deletion REST endpoint +- **THEN** the server SHALL destroy the in-memory Y.Doc for that map and close all associated WebSocket connections + +#### Scenario: Client detects map deletion +- **WHEN** the client's WebSocket connection is closed by the server due to map deletion +- **THEN** the frontend SHALL handle the disconnection (e.g., redirect to home or show a notification) + +### Requirement: Initial map load via Y.Doc +When a user opens a map, the frontend SHALL receive the map state through the Yjs sync protocol instead of a Socket.io `join` response. After the Y.Doc syncs, the bridge SHALL extract all nodes and initialize MMP. + +#### Scenario: User opens an existing map +- **WHEN** the WebsocketProvider connects and the Y.Doc syncs with the server +- **THEN** the bridge SHALL read all entries from the `nodes` map, convert them to `ExportNodeProperties[]`, and call `mmpService.new(snapshot)` to initialize the map + diff --git a/openspec/specs/yjs-persistence/spec.md b/openspec/specs/yjs-persistence/spec.md new file mode 100644 index 00000000..cd31a6bd --- /dev/null +++ b/openspec/specs/yjs-persistence/spec.md @@ -0,0 +1,56 @@ +## ADDED Requirements + +### Requirement: Debounced persistence on Y.Doc change +The server SHALL persist the Y.Doc state to PostgreSQL on a debounced timer. When a Y.Doc is modified, the server SHALL wait for a configurable debounce interval (default 2 seconds) of inactivity before persisting. Each new modification SHALL reset the debounce timer. + +#### Scenario: Single edit triggers persistence +- **WHEN** a client makes a change to the Y.Doc and no further changes occur within the debounce interval +- **THEN** the server SHALL persist the Y.Doc state to the database after the debounce interval elapses + +#### Scenario: Rapid edits coalesce into one persist +- **WHEN** a client makes multiple changes within the debounce interval +- **THEN** the server SHALL persist only once after the debounce interval elapses from the last change + +#### Scenario: Persistence on last client disconnect +- **WHEN** the last client disconnects from a map +- **THEN** the server SHALL immediately persist the Y.Doc state to the database regardless of the debounce timer + +### Requirement: Decode Y.Doc to existing database tables +The persistence service SHALL extract node data from the Y.Doc's `nodes` Y.Map and write it to the existing MmpNode table. The persistence service SHALL extract map options from the Y.Doc's `mapOptions` Y.Map and update the existing MmpMap table. The database schema SHALL NOT be modified. + +#### Scenario: Persist Y.Doc nodes to database +- **WHEN** persistence is triggered +- **THEN** the server SHALL read all entries from the Y.Doc `nodes` map, convert each Y.Map entry to an MmpNode entity, and write them to the database in a transaction (delete existing nodes for the map, then insert all current nodes) + +#### Scenario: Persist map options to database +- **WHEN** persistence is triggered and the Y.Doc `mapOptions` map has been modified +- **THEN** the server SHALL update the MmpMap entity's options column with the current `mapOptions` values + +#### Scenario: Persistence transaction atomicity +- **WHEN** a persistence transaction fails partway through (e.g., database error) +- **THEN** the entire transaction SHALL be rolled back and the server SHALL retry on the next debounce cycle + +### Requirement: Hydrate Y.Doc from database +When a Y.Doc is first created for a map, the server SHALL load all MmpNode rows and the MmpMap row from the database and populate the Y.Doc with this data. + +#### Scenario: Hydrate Y.Doc for existing map +- **WHEN** the first client connects to a map that has nodes in the database +- **THEN** the server SHALL query all MmpNode rows for the map, create a Y.Map entry in the `nodes` map for each row, and populate the `mapOptions` map from the MmpMap entity + +#### Scenario: Hydrate Y.Doc for empty map +- **WHEN** the first client connects to a map that has only a root node in the database +- **THEN** the server SHALL create a Y.Doc with a single entry in the `nodes` map for the root node + +### Requirement: Update lastModified timestamps on persist +The persistence service SHALL update the MmpMap `lastModified` timestamp and each MmpNode `lastModified` timestamp when persisting Y.Doc state to the database. + +#### Scenario: Map timestamp updated on persist +- **WHEN** the Y.Doc is persisted to the database +- **THEN** the MmpMap's `lastModified` column SHALL be set to the current time + +### Requirement: REST API reads from database +The REST API (MapsController) SHALL continue to read map and node data directly from PostgreSQL. The REST API SHALL NOT read from in-memory Y.Docs. This means REST API responses may lag behind real-time Y.Doc state by up to the debounce interval. + +#### Scenario: REST API returns persisted state +- **WHEN** a client calls `GET /api/maps/:id` +- **THEN** the server SHALL return data from the database, which reflects the last persisted Y.Doc state diff --git a/openspec/specs/yjs-sync/spec.md b/openspec/specs/yjs-sync/spec.md new file mode 100644 index 00000000..4f961ac9 --- /dev/null +++ b/openspec/specs/yjs-sync/spec.md @@ -0,0 +1,116 @@ +## ADDED Requirements + +### Requirement: Y.Doc per map on the server +The server SHALL maintain one Y.Doc instance per active map. A Y.Doc is created or loaded when the first client connects to a map and SHALL remain in memory while at least one client is connected. The Y.Doc SHALL be evicted from memory after a grace period (default 30 seconds) following the last client disconnect. + +#### Scenario: First client connects to a map with no active Y.Doc +- **WHEN** a client opens a WebSocket connection for a map that has no Y.Doc in memory +- **THEN** the server SHALL load the map's nodes from PostgreSQL, hydrate a new Y.Doc with the node data, and use it for the connection + +#### Scenario: Client connects to a map with an active Y.Doc +- **WHEN** a client opens a WebSocket connection for a map that already has a Y.Doc in memory +- **THEN** the server SHALL use the existing Y.Doc and sync its current state to the new client + +#### Scenario: Last client disconnects +- **WHEN** the last client disconnects from a map's WebSocket +- **THEN** the server SHALL persist the Y.Doc to the database, then evict it from memory after the grace period + +#### Scenario: Client reconnects within grace period +- **WHEN** a client connects to a map whose Y.Doc is still in the grace period after last disconnect +- **THEN** the server SHALL reuse the in-memory Y.Doc without reloading from the database + +### Requirement: WebSocket server on dedicated path +The server SHALL expose a WebSocket endpoint at the `/yjs` path, mounted on the NestJS HTTP server using the `ws` library. This endpoint SHALL handle the Yjs sync protocol via `y-websocket`'s `setupWSConnection` utility. + +#### Scenario: Client connects to the Yjs WebSocket endpoint +- **WHEN** a client opens a WebSocket connection to `/yjs?mapId=` +- **THEN** the server SHALL establish a Yjs sync connection for the specified map's Y.Doc + +#### Scenario: Client connects to an invalid map ID +- **WHEN** a client opens a WebSocket connection with a `mapId` that does not exist in the database +- **THEN** the server SHALL close the WebSocket connection with an appropriate error code + +### Requirement: Y.Doc structure mirrors node model +Each Y.Doc SHALL contain a `Y.Map("nodes")` where keys are node IDs and values are `Y.Map` instances with the same fields as `ExportNodeProperties` (id, parent, name, isRoot, locked, detached, k, coordinates, colors, font, image, link). A separate `Y.Map("mapOptions")` SHALL hold map-level metadata. + +#### Scenario: Y.Doc hydrated from database +- **WHEN** a Y.Doc is created from database rows +- **THEN** each MmpNode row SHALL be converted to a Y.Map entry in the `nodes` map with all `ExportNodeProperties` fields populated + +#### Scenario: Node added to Y.Doc +- **WHEN** a client adds a new entry to the `nodes` Y.Map +- **THEN** the entry SHALL be a Y.Map containing all required `ExportNodeProperties` fields + +### Requirement: Authentication at WebSocket handshake +The server SHALL verify the modification secret during the WebSocket handshake. The `mapId` and `secret` SHALL be passed as query parameters. Clients with a valid secret SHALL receive read-write access. Clients without a valid secret (or maps with no secret set) SHALL receive read-only access by default, or read-write access if the map has no modification secret. + +#### Scenario: Client connects with valid modification secret +- **WHEN** a client connects with `?mapId=&secret=` +- **THEN** the server SHALL allow the client to send Y.Doc updates (read-write access) + +#### Scenario: Client connects with invalid modification secret +- **WHEN** a client connects with `?mapId=&secret=` +- **THEN** the server SHALL allow the client to receive Y.Doc state but SHALL silently drop any write messages from the client + +#### Scenario: Client connects to a map with no modification secret +- **WHEN** a client connects to a map that has no modification secret set +- **THEN** the server SHALL grant read-write access regardless of the provided secret + +### Requirement: Frontend WebSocket provider +The frontend SHALL connect to the Yjs WebSocket endpoint using `y-websocket`'s `WebsocketProvider`. The provider SHALL be configured with reconnection support. The provider SHALL pass the map ID and modification secret as query parameters. + +#### Scenario: Frontend establishes Yjs connection +- **WHEN** a user navigates to a map +- **THEN** the frontend SHALL create a `WebsocketProvider` targeting `/yjs` with the map's UUID as the room name and the modification secret as a query parameter + +#### Scenario: WebSocket connection lost +- **WHEN** the WebSocket connection is interrupted +- **THEN** the `WebsocketProvider` SHALL automatically attempt reconnection with backoff + +#### Scenario: WebSocket reconnects successfully +- **WHEN** the `WebsocketProvider` reconnects after a disconnection +- **THEN** the Yjs sync protocol SHALL automatically reconcile the local Y.Doc with the server Y.Doc without a full map reload + +### Requirement: Connection status observable +The frontend SHALL expose a reactive `ConnectionStatus` observable (`'connected' | 'disconnected' | null`) via `MapSyncService`. UI components SHALL subscribe to this observable to present connection state (e.g., showing a disconnect dialog). The service SHALL NOT directly control UI elements like toasts or dialogs. + +#### Scenario: Initial sync completes +- **WHEN** the `WebsocketProvider` fires its first `sync` event with `synced: true` +- **THEN** the connection status SHALL transition to `'connected'` +- **AND** edit mode and Y.Doc observers SHALL be initialized + +#### Scenario: WebSocket disconnects +- **WHEN** the WebSocket connection is lost +- **THEN** the connection status SHALL transition to `'disconnected'` + +#### Scenario: Connection reset during cleanup +- **WHEN** the Yjs connection is reset (e.g., navigating away or switching maps) +- **THEN** the connection status SHALL be reset to `null` as part of `resetYjs()` cleanup + +### Requirement: Yjs enabled by default +The `YJS_ENABLED` feature flag SHALL default to `true` when the environment variable is not set. The backend `isYjsEnabled()` method SHALL treat a missing or undefined `YJS_ENABLED` environment variable as `true`. Only an explicit value of `false` (case-insensitive) SHALL disable Yjs. + +#### Scenario: Environment variable not set +- **WHEN** the `YJS_ENABLED` environment variable is absent from the runtime environment +- **THEN** the server SHALL behave as if `YJS_ENABLED=true` and activate the Yjs sync path + +#### Scenario: Environment variable explicitly set to false +- **WHEN** the `YJS_ENABLED` environment variable is set to `false` (any casing) +- **THEN** the server SHALL disable the Yjs sync path and fall back to Socket.io + +#### Scenario: Environment variable explicitly set to true +- **WHEN** the `YJS_ENABLED` environment variable is set to `true` (any casing) +- **THEN** the server SHALL activate the Yjs sync path + +### Requirement: Socket.io removal +The server SHALL NOT use Socket.io for any data synchronization or presence operations. The `@nestjs/platform-socket.io`, `socket.io`, and `socket.io-client` dependencies SHALL be removed. The frontend SHALL NOT import or use `socket.io-client`. + +Note: During the phased rollout, both Socket.io and Yjs code paths coexist behind feature flags (`YJS_ENABLED` backend, `featureFlagYjs` frontend). This requirement is fully satisfied after the cleanup phase (task 13) removes all Socket.io code and feature flag branching. + +#### Scenario: No Socket.io listeners on the server +- **WHEN** the server is running after the cleanup phase is complete +- **THEN** there SHALL be no Socket.io gateway or `@SubscribeMessage` handlers registered + +#### Scenario: No Socket.io client on the frontend +- **WHEN** the frontend application is built after the cleanup phase is complete +- **THEN** the `socket.io-client` package SHALL NOT be included in the bundle diff --git a/openspec/specs/yjs-undo/spec.md b/openspec/specs/yjs-undo/spec.md new file mode 100644 index 00000000..05e62485 --- /dev/null +++ b/openspec/specs/yjs-undo/spec.md @@ -0,0 +1,143 @@ +## ADDED Requirements + +### Requirement: Y.UndoManager lifecycle +When `yjsEnabled` is true, the `MapSyncService` SHALL create a `Y.UndoManager` instance tracking the `nodesMap` (`yDoc.getMap('nodes')`). The UndoManager SHALL be configured with `trackedOrigins: new Set(['local'])`. The UndoManager SHALL be created AFTER the first Y.Doc sync and map load completes, so that the initial hydration is not captured as undoable operations. The UndoManager SHALL be destroyed in `resetYjs()` before the Y.Doc is destroyed. + +#### Scenario: UndoManager created after first sync +- **WHEN** the Y.Doc completes its first sync and `loadMapFromYDoc()` finishes +- **THEN** the service SHALL create a `Y.UndoManager` on the `nodesMap` with `trackedOrigins: new Set(['local'])` +- **AND** the UndoManager's undo stack SHALL be empty (initial load not captured) + +#### Scenario: UndoManager destroyed on reset +- **WHEN** `resetYjs()` is called (e.g., navigating away from a map or disconnecting) +- **THEN** the service SHALL call `yUndoManager.destroy()` before destroying the Y.Doc + +#### Scenario: UndoManager not created when Yjs disabled +- **WHEN** `yjsEnabled` is false +- **THEN** the service SHALL NOT create a `Y.UndoManager` instance + +### Requirement: Transaction origin on local writes +All local MMP-to-YDoc write operations SHALL use `'local'` as the transaction origin so that `Y.UndoManager` captures them. Import operations SHALL use a separate origin (`'import'`) that is NOT tracked by the UndoManager. + +#### Scenario: Node create uses tracked origin +- **WHEN** the user creates a node and the bridge writes to Y.Doc +- **THEN** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` +- **AND** the UndoManager SHALL capture the operation in its undo stack + +#### Scenario: Node update uses tracked origin +- **WHEN** the user updates a node property and the bridge writes to Y.Doc +- **THEN** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` + +#### Scenario: Node remove uses tracked origin +- **WHEN** the user removes a node and the bridge deletes from Y.Doc +- **THEN** the `yDoc.transact()` call SHALL include `'local'` as the origin + +#### Scenario: Node paste uses tracked origin +- **WHEN** the user pastes nodes and the bridge writes to Y.Doc +- **THEN** the `yDoc.transact()` call SHALL include `'local'` as the origin + +#### Scenario: Map options update uses tracked origin +- **WHEN** the user changes map options and the bridge writes to Y.Doc +- **THEN** the write SHALL be wrapped in `yDoc.transact(() => { ... }, 'local')` + +#### Scenario: Map import uses untracked origin +- **WHEN** the user imports a map and the bridge writes to Y.Doc +- **THEN** the `yDoc.transact()` call SHALL use `'import'` as the origin +- **AND** the UndoManager SHALL NOT capture the import operation + +### Requirement: Undo and redo via Y.UndoManager +When `yjsEnabled` is true, the `MapSyncService` SHALL expose public `undo()` and `redo()` methods that delegate to `Y.UndoManager.undo()` and `Y.UndoManager.redo()` respectively. MMP's `History.undo()` and `History.redo()` SHALL NOT be called when Yjs is active. + +#### Scenario: User triggers undo with Yjs active +- **WHEN** the user presses the undo button and `yjsEnabled` is true +- **THEN** `mapSyncService.undo()` SHALL be called +- **AND** `Y.UndoManager.undo()` SHALL reverse the user's last tracked Y.Doc change +- **AND** `mmpService.undo()` SHALL NOT be called + +#### Scenario: User triggers redo with Yjs active +- **WHEN** the user presses the redo button and `yjsEnabled` is true +- **THEN** `mapSyncService.redo()` SHALL be called +- **AND** `Y.UndoManager.redo()` SHALL reapply the user's last undone change +- **AND** `mmpService.redo()` SHALL NOT be called + +#### Scenario: Per-user undo isolation +- **WHEN** User A and User B both edit nodes, and User A presses undo +- **THEN** only User A's last change SHALL be reversed +- **AND** User B's changes SHALL remain intact + +#### Scenario: Undo when stack is empty +- **WHEN** the user presses undo but the UndoManager's undo stack is empty +- **THEN** the `undo()` call SHALL be a no-op (Y.UndoManager handles this gracefully) + +### Requirement: Full property fidelity on undo/redo of delete +When a node (or subtree) is deleted and the deletion is undone, ALL node properties stored in Y.Doc SHALL be restored exactly — including coordinates, colors, font, image, link, parent reference, k value, locked state, and detached state. The redo of such an undo (re-delete) and subsequent undo (re-restore) SHALL also preserve full property fidelity. + +#### Scenario: Undo of single node delete restores all properties +- **GIVEN** a node exists with `coordinates: {x: 200, y: -120}`, `colors: {name: '#000', background: '#fff', branch: '#333'}`, and `k: -1` +- **WHEN** the node is deleted and the user triggers undo +- **THEN** the Y.Doc SHALL contain the restored node with ALL original properties intact +- **AND** `coordinates` SHALL be `{x: 200, y: -120}` (not recalculated) +- **AND** `k` SHALL be `-1` (preserving left/right orientation) + +#### Scenario: Undo of subtree delete restores parent and all descendants +- **GIVEN** node `A` has child `B`, and `B` has child `C` +- **AND** all three nodes have distinct coordinates and properties +- **WHEN** `A` is deleted (which cascades to `B` and `C` in a single transaction) +- **AND** the user triggers undo +- **THEN** the Y.Doc SHALL contain all three nodes with their original properties +- **AND** `B.parent` SHALL be `A.id` and `C.parent` SHALL be `B.id` + +#### Scenario: Multiple undo/redo cycles preserve properties +- **GIVEN** a node exists with specific coordinates and properties +- **WHEN** the node is deleted, then undo, then redo (re-delete), then undo (re-restore) +- **THEN** after the final undo, the node's properties in Y.Doc SHALL match the original values exactly + +### Requirement: Reactive canUndo and canRedo state +The `MapSyncService` SHALL expose `canUndo$` and `canRedo$` as `Observable` so the toolbar can reactively enable/disable undo/redo buttons when Yjs is active. + +#### Scenario: canUndo updates after user edit +- **WHEN** the user makes an edit that is captured by the UndoManager +- **THEN** `canUndo$` SHALL emit `true` + +#### Scenario: canUndo updates after undo empties stack +- **WHEN** the user undoes all operations and the undo stack becomes empty +- **THEN** `canUndo$` SHALL emit `false` + +#### Scenario: canRedo updates after undo +- **WHEN** the user performs an undo +- **THEN** `canRedo$` SHALL emit `true` + +#### Scenario: canRedo resets after new edit +- **WHEN** the user makes a new edit after undoing (clearing the redo stack) +- **THEN** `canRedo$` SHALL emit `false` + +### Requirement: Toolbar conditional routing +The toolbar SHALL route undo/redo actions based on `yjsEnabled`. When `yjsEnabled` is true, the toolbar SHALL call `mapSyncService.undo()`/`redo()` and use `canUndo$`/`canRedo$` for button state. When `yjsEnabled` is false, the toolbar SHALL continue calling `mmpService.undo()`/`redo()` and checking `mmpService.history()` for button state. + +#### Scenario: Toolbar undo with Yjs active +- **WHEN** the user clicks the undo button and `yjsEnabled` is true +- **THEN** the toolbar SHALL call `mapSyncService.undo()` + +#### Scenario: Toolbar undo with Yjs inactive +- **WHEN** the user clicks the undo button and `yjsEnabled` is false +- **THEN** the toolbar SHALL call `mmpService.undo()` + +#### Scenario: Toolbar button state with Yjs active +- **WHEN** `yjsEnabled` is true +- **THEN** the undo button's disabled state SHALL be derived from `canUndo$` (disabled when false) +- **AND** the redo button's disabled state SHALL be derived from `canRedo$` (disabled when false) + +#### Scenario: Toolbar button state with Yjs inactive +- **WHEN** `yjsEnabled` is false +- **THEN** the undo/redo button disabled state SHALL continue using `mmpService.history().snapshots.length > 1` + +### Requirement: Removal of diff-based undo bridge +When `yjsEnabled` is true, the `MapSyncService` SHALL NOT subscribe to MMP's `undo` and `redo` events. The `setupYjsUndoRedoHandlers()` method and `writeUndoRedoDiffToYDoc()` method (and its helpers) SHALL be removed. + +#### Scenario: MMP undo event not subscribed in Yjs path +- **WHEN** `yjsEnabled` is true and the Yjs bridge is initialized +- **THEN** the service SHALL NOT subscribe to `mmpService.on('undo')` or `mmpService.on('redo')` + +#### Scenario: Diff-based write methods removed +- **WHEN** the codebase is updated +- **THEN** `writeUndoRedoDiffToYDoc`, `writeAddedNodesToYDoc`, `writeUpdatedNodesToYDoc`, and `writeDeletedNodesFromYDoc` SHALL no longer exist in `MapSyncService` diff --git a/openspec/specs/yjs-write-access/spec.md b/openspec/specs/yjs-write-access/spec.md new file mode 100644 index 00000000..7506a8a6 --- /dev/null +++ b/openspec/specs/yjs-write-access/spec.md @@ -0,0 +1,69 @@ +## ADDED Requirements + +### Requirement: HTTP-based write-access determination +The `GET /api/maps/:id` endpoint SHALL accept an optional `secret` query parameter. When provided, the server SHALL compare it against the map's `modificationSecret` using the existing `checkWriteAccess()` utility and return a `writable` boolean field in the response. + +#### Scenario: Map with no modification secret +- **WHEN** a client requests `GET /api/maps/:id` for a map that has no `modificationSecret` +- **THEN** the response SHALL include `writable: true` +- **AND** this SHALL be the case regardless of whether `?secret=` is provided + +#### Scenario: Map with correct secret +- **WHEN** a client requests `GET /api/maps/:id?secret=` +- **AND** the map has a `modificationSecret` that matches the provided secret +- **THEN** the response SHALL include `writable: true` + +#### Scenario: Map with wrong or missing secret +- **WHEN** a client requests `GET /api/maps/:id` without a `secret` parameter (or with an incorrect one) +- **AND** the map has a `modificationSecret` +- **THEN** the response SHALL include `writable: false` + +### Requirement: Frontend reads write-access from HTTP response +When the frontend fetches a map via `fetchMapFromServer`, it SHALL pass the stored `modificationSecret` as a `?secret=` query parameter (URL-encoded). The `prepareExistingMap` method SHALL set `yjsWritable` from the response's `writable` field before the WebSocket connection is established. + +#### Scenario: Secret passed in HTTP request +- **WHEN** `modificationSecret` is set in `MapSyncService` +- **AND** `fetchMapFromServer` is called +- **THEN** the HTTP request URL SHALL include `?secret=` + +#### Scenario: No secret omits query parameter +- **WHEN** `modificationSecret` is empty or null +- **AND** `fetchMapFromServer` is called +- **THEN** the HTTP request URL SHALL NOT include a `secret` query parameter + +#### Scenario: writable set from HTTP response +- **WHEN** `prepareExistingMap` receives the server response +- **THEN** `yjsWritable` SHALL be set to `serverMap.writable !== false` +- **AND** `yjsWritable` SHALL be set BEFORE the WebSocket connection is established + +#### Scenario: writable defaults to true when absent +- **WHEN** the server response does not include a `writable` field +- **THEN** `yjsWritable` SHALL default to `true` +- **AND** server-side enforcement SHALL still apply (client writes silently dropped if unauthorized) + +### Requirement: Removal of custom WebSocket message type 4 +The custom `MESSAGE_WRITE_ACCESS` (type 4) WebSocket message SHALL be removed from both frontend and backend. The server SHALL NOT send write-access messages over WebSocket. The client SHALL NOT register custom message handlers or raw WebSocket listeners for write-access. + +#### Scenario: Server does not send write-access message +- **WHEN** a WebSocket connection is established in `setupSync()` +- **THEN** the server SHALL NOT call `encodeWriteAccessMessage()` or send a type 4 message +- **AND** the server SHALL still send SyncStep1, SyncStep2, and awareness messages + +#### Scenario: Client does not hack messageHandlers +- **WHEN** the `WebsocketProvider` is created +- **THEN** the client SHALL NOT modify `wsProvider.messageHandlers[4]` +- **AND** the client SHALL NOT attach raw `ws.addEventListener('message', ...)` listeners for write-access parsing + +#### Scenario: Protocol utils cleaned up +- **WHEN** the codebase is updated +- **THEN** `MESSAGE_WRITE_ACCESS` and `encodeWriteAccessMessage()` SHALL NOT exist in `yjsProtocol.ts` +- **AND** `MESSAGE_WRITE_ACCESS` and `parseWriteAccessBytes()` SHALL NOT exist in `yjs-utils.ts` +- **AND** `checkWriteAccess()` SHALL remain in `yjsProtocol.ts` (used by gateway and controller) + +### Requirement: Server-side enforcement unchanged +The `processReadOnlySyncMessage` function in the Yjs gateway SHALL continue to silently drop SyncStep2 and Update messages from read-only clients. This is the security boundary and is NOT affected by the move from WebSocket to HTTP for permission signaling. + +#### Scenario: Read-only client writes are dropped +- **WHEN** a client connected without a valid secret sends a Y.Doc update +- **THEN** the server SHALL silently drop the update via `processReadOnlySyncMessage` +- **AND** no error SHALL be sent to the client (silent drop allows continued reading) diff --git a/openspec/specs/yjs-ws-connection-resilience/spec.md b/openspec/specs/yjs-ws-connection-resilience/spec.md new file mode 100644 index 00000000..5664ade4 --- /dev/null +++ b/openspec/specs/yjs-ws-connection-resilience/spec.md @@ -0,0 +1,63 @@ +## ADDED Requirements + +### Requirement: WebSocket error handler on individual connections + +The gateway SHALL register a `ws.on('error')` handler on every WebSocket connection during setup. The handler SHALL log the error with connection context (mapId) and call `ws.terminate()` to force-close the connection. The existing `close` event handler SHALL execute its cleanup path after termination. + +#### Scenario: Connection emits an error event + +- **WHEN** a WebSocket connection emits an `error` event (e.g., ECONNRESET, write error) +- **THEN** the error is logged with the associated mapId and the connection is terminated via `ws.terminate()` + +#### Scenario: Error handler does not crash the process + +- **WHEN** a WebSocket connection emits an `error` event +- **THEN** the error does not propagate as an uncaught exception and the server process continues running + +#### Scenario: Cleanup runs after error-triggered termination + +- **WHEN** a connection is terminated due to an error +- **THEN** the `close` handler fires, removing the connection from tracking, cleaning up awareness state, and decrementing the client count + +### Requirement: Ping/pong heartbeat for zombie connection detection + +The gateway SHALL implement a periodic ping/pong heartbeat mechanism using the WebSocket protocol's native ping/pong frames. The heartbeat interval SHALL be 30 seconds. Connections that fail to respond with a pong before the next ping cycle SHALL be terminated. + +#### Scenario: Healthy connection responds to ping + +- **WHEN** the server sends a ping frame to a connected client +- **THEN** the client responds with a pong frame and the connection remains open through the next heartbeat cycle + +#### Scenario: Zombie connection detected and terminated + +- **WHEN** a connection does not respond with a pong before the next 30-second ping cycle +- **THEN** the server terminates the connection via `ws.terminate()` and the standard close cleanup path executes + +#### Scenario: New connection marked as alive + +- **WHEN** a new WebSocket connection is established +- **THEN** the connection is marked as alive so it is not terminated on the first heartbeat cycle + +#### Scenario: Heartbeat interval cleaned up on shutdown + +- **WHEN** the server module is destroyed +- **THEN** the heartbeat interval is cleared and no further pings are sent + +### Requirement: Connection setup timeout + +The gateway SHALL enforce a timeout on the async connection setup phase (database lookups and document hydration). The timeout SHALL be 10 seconds. If setup does not complete within the timeout, the WebSocket SHALL be closed with code 1013 (Try Again Later). + +#### Scenario: Setup completes within timeout + +- **WHEN** `findMap` and `getOrCreateDoc` complete within 10 seconds +- **THEN** the connection is established normally and the timeout timer is canceled + +#### Scenario: Setup exceeds timeout + +- **WHEN** the database operations in `handleConnection` take longer than 10 seconds +- **THEN** the WebSocket is closed with close code 1013 and the timeout timer is cleaned up + +#### Scenario: Timeout timer does not leak + +- **WHEN** the connection setup completes (success or failure) before the timeout +- **THEN** the timeout timer is canceled to prevent resource leaks diff --git a/openspec/specs/yjs-ws-lifecycle-integrity/spec.md b/openspec/specs/yjs-ws-lifecycle-integrity/spec.md new file mode 100644 index 00000000..07a2d9de --- /dev/null +++ b/openspec/specs/yjs-ws-lifecycle-integrity/spec.md @@ -0,0 +1,86 @@ +## ADDED Requirements + +### Requirement: Persistence service graceful shutdown + +`YjsPersistenceService` SHALL implement `OnModuleDestroy`. On module destruction, the service SHALL clear all active debounce timers, unregister all doc update observers, and perform a best-effort flush of pending persistence for docs that had active timers. The flush SHALL have a maximum timeout of 5 seconds. + +#### Scenario: Clean shutdown with pending debounce timers + +- **WHEN** the server shuts down while debounce timers are active +- **THEN** all timers are cleared, observers are unregistered, and pending docs are flushed to the database + +#### Scenario: Shutdown flush timeout + +- **WHEN** the best-effort flush takes longer than 5 seconds during shutdown +- **THEN** the shutdown completes without waiting further and remaining unflushed data is accepted as lost + +#### Scenario: No timers fire after shutdown + +- **WHEN** `onModuleDestroy` completes +- **THEN** no debounce timer callbacks execute after the database connection is closed + +### Requirement: Awaited async operations in close handler + +The gateway's close handler SHALL handle the async `decrementClientCount` call with explicit error handling via `.catch()`. Persistence errors during client disconnect SHALL be logged rather than silently swallowed. + +#### Scenario: Successful decrement with persistence + +- **WHEN** a client disconnects and `decrementClientCount` triggers `persistImmediately` +- **THEN** the persistence completes and is logged + +#### Scenario: Persistence error during disconnect + +- **WHEN** `decrementClientCount` fails due to a database error +- **THEN** the error is logged with context (mapId) and the close handler completes without crashing + +### Requirement: Grace timer race condition prevention + +The gateway SHALL ensure that if `handleConnection` fails after `getOrCreateDoc` but before the connection is fully tracked and the client count is incremented, the doc manager's eviction mechanism is not left in an inconsistent state. The grace timer that was canceled by `getOrCreateDoc` SHALL be restored if the connection setup does not complete. + +#### Scenario: Error between getOrCreateDoc and incrementClientCount + +- **WHEN** `handleConnection` throws after `getOrCreateDoc` succeeds but before `incrementClientCount` is called +- **THEN** the doc remains eligible for grace-period eviction (the grace timer is restored or the doc is otherwise not orphaned) + +#### Scenario: Successful connection setup + +- **WHEN** `handleConnection` completes the full sequence (getOrCreateDoc → trackConnection → incrementClientCount) +- **THEN** the grace timer remains canceled and the client count accurately reflects the active connection + +### Requirement: Unified client count tracking + +The gateway SHALL be the single source of truth for client counts per map. The client count SHALL be derived from the size of the connection set (`mapConnections.get(mapId).size`) rather than maintained as a separate counter in the doc manager. The doc manager SHALL accept the connection count from the gateway when determining eviction eligibility. + +#### Scenario: Client count matches connection set size + +- **WHEN** multiple clients connect to and disconnect from a map +- **THEN** the client count reported for that map always equals the number of entries in the connection set + +#### Scenario: No drift between counter and connections + +- **WHEN** an error occurs during connection setup or teardown +- **THEN** the client count remains accurate because it is derived from the connection set, not independently tracked + +#### Scenario: Eviction triggered at zero connections + +- **WHEN** the last connection for a map is removed from the connection set +- **THEN** the doc manager receives a count of 0 and initiates the grace-period eviction timer + +### Requirement: Async deleteMap with awaited database call + +`MapsService.deleteMap` SHALL return `Promise` and SHALL `await` the repository delete operation. All callers (REST controller and Socket.IO gateway) SHALL `await` the result. Database errors during deletion SHALL propagate to callers. + +#### Scenario: Successful map deletion + +- **WHEN** `deleteMap` is called with a valid map ID +- **THEN** the repository delete is awaited and the promise resolves after the database operation completes + +#### Scenario: Database error during deletion + +- **WHEN** the repository delete fails with a database error +- **THEN** the error propagates to the caller as a rejected promise + +#### Scenario: Callers await deleteMap + +- **WHEN** the REST controller or Socket.IO gateway calls `deleteMap` +- **THEN** the call is awaited so errors are caught by the caller's error handling diff --git a/openspec/specs/yjs-ws-resource-protection/spec.md b/openspec/specs/yjs-ws-resource-protection/spec.md new file mode 100644 index 00000000..88ee19c2 --- /dev/null +++ b/openspec/specs/yjs-ws-resource-protection/spec.md @@ -0,0 +1,72 @@ +## ADDED Requirements + +### Requirement: Maximum message payload size + +The WebSocketServer SHALL be configured with a `maxPayload` of 1,048,576 bytes (1 MiB). Messages exceeding this limit SHALL cause the connection to be closed automatically by the `ws` library with close code 1009 (Message Too Big). + +#### Scenario: Normal-sized message accepted + +- **WHEN** a client sends a Yjs sync message under 1 MiB +- **THEN** the message is processed normally + +#### Scenario: Oversized message rejected + +- **WHEN** a client sends a message exceeding 1 MiB +- **THEN** the `ws` library closes the connection with code 1009 (Message Too Big) and the standard close cleanup path executes + +### Requirement: Global concurrent connection limit + +The gateway SHALL enforce a maximum number of concurrent WebSocket connections globally. The default limit SHALL be 500 connections. When the limit is reached, new upgrade requests SHALL be rejected with HTTP status 503 (Service Unavailable). + +#### Scenario: Connection accepted below global limit + +- **WHEN** a new WebSocket upgrade request arrives and the total connection count is below 500 +- **THEN** the upgrade proceeds normally + +#### Scenario: Connection rejected at global limit + +- **WHEN** a new WebSocket upgrade request arrives and the total connection count has reached 500 +- **THEN** the upgrade is rejected with HTTP status 503 and the socket is destroyed + +#### Scenario: Global count decremented on disconnect + +- **WHEN** a WebSocket connection closes (normal or error) +- **THEN** the global connection count is decremented, allowing new connections + +### Requirement: Per-IP concurrent connection limit + +The gateway SHALL enforce a maximum number of concurrent WebSocket connections per source IP address. The default limit SHALL be 50 connections per IP. When the limit is reached for a given IP, new upgrade requests from that IP SHALL be rejected with HTTP status 429 (Too Many Requests). + +#### Scenario: Connection accepted below per-IP limit + +- **WHEN** a new upgrade request arrives from an IP with fewer than 50 active connections +- **THEN** the upgrade proceeds normally + +#### Scenario: Connection rejected at per-IP limit + +- **WHEN** a new upgrade request arrives from an IP that already has 50 active connections +- **THEN** the upgrade is rejected with HTTP status 429 and the socket is destroyed + +#### Scenario: Per-IP count cleaned up when all connections close + +- **WHEN** all connections from a specific IP address close +- **THEN** the per-IP tracking entry for that IP is removed to prevent memory growth + +### Requirement: Per-IP connection rate limit + +The gateway SHALL enforce a rate limit on new WebSocket connections per source IP address. The limit SHALL be 10 connections per 10-second sliding window. Upgrade requests exceeding the rate limit SHALL be rejected with HTTP status 429 (Too Many Requests). + +#### Scenario: Connections within rate limit accepted + +- **WHEN** a client opens connections at a rate below 10 per 10 seconds +- **THEN** all upgrade requests proceed normally + +#### Scenario: Rapid reconnect loop rejected + +- **WHEN** a client from a single IP opens more than 10 connections within a 10-second window +- **THEN** subsequent upgrade requests are rejected with HTTP status 429 until the window expires + +#### Scenario: Rate limit window expires + +- **WHEN** the 10-second window elapses after rate-limited connections +- **THEN** new connections from that IP are accepted again From 7dac41bf27716ecc7b661c8b50a0abf326a02924 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 24 May 2026 20:34:16 +0200 Subject: [PATCH 11/14] fix release of multi arch build (#1302) --- .github/workflows/release.yml | 66 ++++++++++++----------------------- 1 file changed, 22 insertions(+), 44 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 30372d5f..bb6bc2d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,13 +1,7 @@ -# https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio - -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# GitHub recommends pinning actions to a commit SHA. -# To get a newer version, you will need to update the SHA. -# You can also reference a tag or branch, but the action may change without warning. +# Reusable workflow: docker/github-builder. +# Distributes per-platform builds to native runners, assembles the multi-arch +# manifest, and signs SLSA provenance + SBOM attestations. +# Docs: https://docs.docker.com/build/ci/github-actions/github-builder/ name: Create and publish a Docker image @@ -15,42 +9,26 @@ on: release: types: [published] -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - jobs: - build-and-push-image: - runs-on: ubuntu-latest + build: + uses: docker/github-builder/.github/workflows/build.yml@c2782c55efa56a01b9c30021db8f5ec3993228a3 # v1.8.0 permissions: contents: read packages: write - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - - name: Log in to the Container registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} + id-token: write + with: + output: image + push: true + platforms: linux/amd64,linux/arm64 + target: production + meta-images: ghcr.io/${{ github.repository }} + set-meta-annotations: true + set-meta-labels: true + cache: true + cache-mode: max + sbom: true + secrets: + registry-auths: | + - registry: ghcr.io + username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - - - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 - with: - platforms: linux/amd64,linux/arm64/v8 - target: production - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file From 63583bd9e9caec9f850137141e5b230cde6deda2 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sun, 24 May 2026 21:56:56 +0200 Subject: [PATCH 12/14] Use own workflow docker build (#1303) --- .dockerignore | 6 +- .github/actions/setup-pnpm/action.yml | 24 +++++ .github/workflows/ci.yml | 102 +++------------------ .github/workflows/playwright.yml | 18 ++-- .github/workflows/release.yml | 124 +++++++++++++++++++++----- entrypoint.prod.sh | 20 +++-- 6 files changed, 167 insertions(+), 127 deletions(-) create mode 100644 .github/actions/setup-pnpm/action.yml diff --git a/.dockerignore b/.dockerignore index e451258a..9d56251c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -25,4 +25,8 @@ teammapper-frontend/node_modules teammapper-frontend/npm-debug.log teammapper-frontend/README.md README.md -teammapper-backend/benchmark \ No newline at end of file +teammapper-backend/benchmark +node_modules +openspec +docs +coverage \ No newline at end of file diff --git a/.github/actions/setup-pnpm/action.yml b/.github/actions/setup-pnpm/action.yml new file mode 100644 index 00000000..17ed4902 --- /dev/null +++ b/.github/actions/setup-pnpm/action.yml @@ -0,0 +1,24 @@ +name: Setup pnpm + Node +description: Activate Node + pnpm via corepack, restore the pnpm store cache, and run a frozen-lockfile install. + +runs: + using: composite + steps: + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version: '24' + + - shell: bash + run: | + corepack enable + corepack prepare pnpm@10.33.4 --activate + + - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 + with: + path: ~/.local/share/pnpm/store + key: pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} + restore-keys: | + pnpm-store-${{ runner.os }}- + + - shell: bash + run: pnpm install --frozen-lockfile diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd465c1a..48605b9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Docker Image CI +name: CI on: push: @@ -14,55 +14,29 @@ permissions: jobs: teammapper-backend-build: runs-on: ubuntu-latest - permissions: - contents: read - packages: read steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - name: Set up QEMU - uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 - - name: Login to GitHub Container Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - name: Build and export to Docker + - name: Build Docker image (smoke test) uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 with: context: . target: production tags: | ghcr.io/b310-digital/teammapper:latest + cache-from: type=gha + cache-to: type=gha,mode=max teammapper-backend-lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter teammapper-backend run lint teammapper-frontend-lint: @@ -70,15 +44,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter teammapper-frontend run lint teammapper-frontend-tsc: @@ -86,15 +52,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build - run: pnpm --filter teammapper-frontend run tsc @@ -103,15 +61,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter teammapper-backend run tsc teammapper-backend-test-e2e: @@ -119,28 +69,18 @@ jobs: services: postgres: - image: postgres:10.8 + image: postgres:15-alpine env: POSTGRES_USER: teammapper-user POSTGRES_PASSWORD: teammapper-password POSTGRES_DB: teammapper-backend-test ports: - # Will assign a random free host port - 5432/tcp - # Needed because the postgres container does not provide a healthcheck options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter teammapper-backend run test env: POSTGRES_DATABASE: "teammapper-backend-test" @@ -159,16 +99,10 @@ jobs: teammapper-frontend-test: runs-on: ubuntu-latest + steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - run: pnpm --filter @teammapper/mermaid-mindmap-parser run build - run: pnpm --filter teammapper-frontend run test @@ -177,15 +111,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - name: pnpm audit (fail on high/critical production deps) run: pnpm audit --audit-level=high --prod @@ -200,4 +126,4 @@ jobs: uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: fail-on-severity: high - allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Zlib, CC-BY-4.0 \ No newline at end of file + allow-licenses: MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Zlib, CC-BY-4.0 diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 4ddacb9c..1f1340ef 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -2,6 +2,8 @@ name: Playwright Tests permissions: contents: read on: + push: + branches: [main] pull_request: branches: [main] jobs: @@ -10,7 +12,7 @@ jobs: DB_PASSWORD: ${{ secrets.CI_POSTGRES_PASSWORD != '' && secrets.CI_POSTGRES_PASSWORD || format('fallback_ci_password_{0}', github.run_number) }} services: postgres: - image: postgres + image: postgres:15-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: ${{ env.DB_PASSWORD }} @@ -20,19 +22,13 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - ports: + ports: - 5432:5432 - timeout-minutes: 60 + timeout-minutes: 30 runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 - with: - node-version: '24' - - run: corepack enable - - run: corepack prepare pnpm@10.33.4 --activate - - name: Install dependencies - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup-pnpm - name: Build packages run: pnpm --filter @teammapper/mermaid-mindmap-parser run build - name: Cache Playwright browsers @@ -64,4 +60,4 @@ jobs: with: name: playwright path: teammapper-frontend/playwright - retention-days: 7 \ No newline at end of file + retention-days: 7 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bb6bc2d2..60cff374 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,114 @@ -# Reusable workflow: docker/github-builder. -# Distributes per-platform builds to native runners, assembles the multi-arch -# manifest, and signs SLSA provenance + SBOM attestations. -# Docs: https://docs.docker.com/build/ci/github-actions/github-builder/ - name: Create and publish a Docker image on: release: types: [published] +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + jobs: build: - uses: docker/github-builder/.github/workflows/build.yml@c2782c55efa56a01b9c30021db8f5ec3993228a3 # v1.8.0 + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + suffix: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + suffix: arm64 + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: Log in to the Container registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image by digest + id: build + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7 + with: + platforms: ${{ matrix.platform }} + target: production + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=${{ matrix.suffix }} + cache-to: type=gha,scope=${{ matrix.suffix }},mode=max + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: digests-${{ matrix.suffix }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: build permissions: contents: read packages: write - id-token: write - with: - output: image - push: true - platforms: linux/amd64,linux/arm64 - target: production - meta-images: ghcr.io/${{ github.repository }} - set-meta-annotations: true - set-meta-labels: true - cache: true - cache-mode: max - sbom: true - secrets: - registry-auths: | - - registry: ghcr.io - username: ${{ github.repository_owner }} + + steps: + - name: Download digests + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4 + + - name: Log in to the Container registry + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Create and push multi-arch manifest + working-directory: /tmp/digests + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + if: steps.meta.outputs.version != '' + run: | + docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/entrypoint.prod.sh b/entrypoint.prod.sh index 9220ef4c..1e9ee55b 100755 --- a/entrypoint.prod.sh +++ b/entrypoint.prod.sh @@ -1,14 +1,24 @@ #!/bin/sh -set -e +set -eu + +: "${POSTGRES_HOST:?POSTGRES_HOST not set}" +: "${POSTGRES_PORT:?POSTGRES_PORT not set}" +: "${POSTGRES_USER:?POSTGRES_USER not set}" echo "Looking for the database ..." -while ! pg_isready -q -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER -do - echo "Waiting for database." +attempts=0 +max_attempts=60 +until pg_isready -q -h "$POSTGRES_HOST" -p "$POSTGRES_PORT" -U "$POSTGRES_USER"; do + attempts=$((attempts + 1)) + if [ "$attempts" -ge "$max_attempts" ]; then + echo "Database not ready after $((max_attempts * 2))s; giving up." >&2 + exit 1 + fi + echo "Waiting for database ($attempts/$max_attempts)..." sleep 2 done echo "Found database." -echo "Starting the application..." +echo "Starting the application..." pnpm run prod:typeorm:migrate exec pnpm run start:prod From ba8c61803793ed48fc29c3fcc425f5b24d440039 Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Mon, 25 May 2026 06:37:51 +0200 Subject: [PATCH 13/14] further harden github workflow (#1304) --- .github/workflows/release.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 60cff374..8bfdfdd8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ on: release: types: [published] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} @@ -57,10 +61,11 @@ jobs: cache-to: type=gha,scope=${{ matrix.suffix }},mode=max - name: Export digest + env: + DIGEST: ${{ steps.build.outputs.digest }} run: | mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" + touch "/tmp/digests/${DIGEST#sha256:}" - name: Upload digest artifact uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 @@ -103,12 +108,16 @@ jobs: - name: Create and push multi-arch manifest working-directory: /tmp/digests + env: + IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} run: | docker buildx imagetools create \ $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *) + $(printf "${IMAGE_REF}@sha256:%s " *) - name: Inspect image - if: steps.meta.outputs.version != '' + env: + IMAGE_REF: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + VERSION: ${{ steps.meta.outputs.version }} run: | - docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + docker buildx imagetools inspect "${IMAGE_REF}:${VERSION}" From 32bc3d6f42ae020e31427912c8d039a6b05cab6e Mon Sep 17 00:00:00 2001 From: JannikStreek Date: Sat, 30 May 2026 15:28:42 +0200 Subject: [PATCH 14/14] improve and fix setup after pnpm and docker change (#1306) --- docker-compose-prod.yml | 8 ++++---- docs/pnpm-security.md | 4 ++-- pnpm-workspace.yaml | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml index d22d3998..2d0a0259 100644 --- a/docker-compose-prod.yml +++ b/docker-compose-prod.yml @@ -12,7 +12,7 @@ services: POSTGRES_DATABASE: ${POSTGRES_PROD_DB} POSTGRES_HOST: ${POSTGRES_PROD_HOST:-postgres_prod} POSTGRES_PASSWORD: ${POSTGRES_PROD_PASSWORD} - POSTGRES_PORT: ${POSTGRES_PROD_PROD_PORT:-5432} + POSTGRES_PORT: ${POSTGRES_PROD_PORT:-5432} POSTGRES_SSL: ${POSTGRES_PROD_SSL:-true} POSTGRES_SSL_REJECT_UNAUTHORIZED: ${POSTGRES_PROD_SSL_REJECT_UNAUTHORIZED:-false} POSTGRES_USER: ${POSTGRES_PROD_USER:-teammapper-user} @@ -26,7 +26,7 @@ services: depends_on: - postgres_prod volumes: - - ./config/settings.override.json:/home/node/app/teammapper-backend/config/settings.override.json:ro + - ./config/settings.override.json:/home/node/app/teammapper/config/settings.override.json:ro postgres_prod: image: postgres:15-alpine @@ -43,8 +43,8 @@ services: # To setup an ssl-enabled postgres server locally, you need to generate a self-signed ssl certificate. # See README.md for more information. # Mount the ssl_cert_file and ssl_key_file into the docker container. - - ./ca/server.crt:/var/lib/postgresql/server.crt - - ./ca/server.key:/var/lib/postgresql/server.key + # - ./ca/server.crt:/var/lib/postgresql/server.crt + # - ./ca/server.key:/var/lib/postgresql/server.key - postgres_prod_data:/var/lib/postgresql/data/pgdata volumes: diff --git a/docs/pnpm-security.md b/docs/pnpm-security.md index db500cd7..a3ea4b03 100644 --- a/docs/pnpm-security.md +++ b/docs/pnpm-security.md @@ -7,7 +7,7 @@ This repo applies a subset of the [bodadotsh npm security best practices](https: | Practice | Where | Why | |----------|-------|-----| | Exact-version saves | `.npmrc` (`save-exact=true`) | New `pnpm add` calls write exact versions, keeping the manifest aligned with what the lockfile already pins. | -| Lifecycle scripts gated by allowlist | `pnpm-workspace.yaml` (`allowBuilds:`) | pnpm 10 disables install scripts by default for transitive deps. `allowBuilds` is the explicit allowlist for the few packages (currently `@parcel/watcher` and `esbuild`) whose native builds we trust. | +| Lifecycle scripts gated by allowlist | `pnpm-workspace.yaml` (`allowBuilds:` + `strictDepBuilds: true`) | pnpm 10 disables install scripts by default for transitive deps. `allowBuilds` is the explicit allowlist for the few packages (currently `@parcel/watcher` and `esbuild`) whose native builds we trust. `strictDepBuilds: true` makes an unreviewed build script fail the install (non-zero exit) instead of only printing a warning, so a new dependency lifecycle script is a hard CI gate. | | 7-day quarantine on new versions | `pnpm-workspace.yaml` (`minimumReleaseAge: 10080`) | New package versions are held back from installation for 7 days, giving the ecosystem time to revoke compromised releases. If a specific package needs an exemption, add it to `minimumReleaseAgeExclude:`. | | Frozen-lockfile installs | All CI workflows, the Dockerfile, and the `teammapper-frontend` `build:packages` script | Keeps the lockfile authoritative across every install path. | | pnpm patch version pinned in CI | `corepack prepare pnpm@10.33.4 --activate` in all CI jobs | Matches the Dockerfile's `PNPM_VERSION` so CI and production builds use the same pnpm. | @@ -16,7 +16,7 @@ This repo applies a subset of the [bodadotsh npm security best practices](https: | Action pinning | All `uses:` lines in `.github/workflows/` carry a 40-char SHA | Protects against tag-rewrite supply-chain attacks. | | Least-privilege workflow permissions | Workflow-level `permissions: contents: read`, with per-job overrides only where a job needs more | CI runs with `secrets.GITHUB_TOKEN` scoped to the minimum each job requires. | | Dependabot 7-day cooldown | `cooldown:` block in `.github/dependabot.yml` (each ecosystem) | Holds version-update PRs for 7 days after a release, mirroring `minimumReleaseAge: 10080`. Security updates are exempt from cooldown so CVE fixes still flow promptly. | -| Node base image bumped manually | `ignore: dependency-name: "node"` in `.github/dependabot.yml` | Dependabot does not open PRs for the Node base image; bumps follow the Node LTS release cycle and are applied by hand. | +| Node base image pinned by digest, bumped manually | `FROM node:…@sha256:…` in `Dockerfile` + `ignore: dependency-name: "node"` in `.github/dependabot.yml` | The base image is pinned to a multi-arch manifest digest so builds are reproducible and immune to tag re-publishing. Dependabot does not open PRs for the Node image; bumps follow the Node LTS release cycle and are applied by hand, re-resolving the digest with `docker buildx imagetools inspect node:-alpine`. | | Dependency review on every PR | `dependency-review` job in `ci.yml` (`actions/dependency-review-action`) | Fails the PR if it introduces a new dep with a high/critical advisory or a license outside the allow-list (MIT, Apache-2.0, BSD-2/3-Clause, ISC, 0BSD, CC0-1.0, Unlicense, BlueOak-1.0.0, Zlib, CC-BY-4.0). Complements `pnpm audit`, which only checks the installed tree. | ## Running checks locally diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e947f116..6997767a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,6 +4,7 @@ packages: - 'teammapper-frontend/packages/*' # 7-day quarantine on newly published versions (mitigates fast-revoked malicious releases). minimumReleaseAge: 10080 +strictDepBuilds: true allowBuilds: '@compodoc/compodoc': false '@nestjs/core': false