diff --git a/.env.default b/.env.default index 30a3c1ce3..3004140ab 100644 --- a/.env.default +++ b/.env.default @@ -41,6 +41,8 @@ POSTGRES_STATEMENT_TIMEOUT=100000 DELETE_AFTER_DAYS=30 +YJS_ENABLED=true + DEV_BUILD_CONTEXT= JWT_SECRET= \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 927c0fe87..50a56abd7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,6 +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} TESTING_PLAYWRIGHT_WS_ENDPOINT: "ws://playwright:9323" TESTING_PLAYWRIGHT_BASE_URL: "http://app:4200" @@ -41,6 +42,8 @@ services: - 9876:9876 volumes: - .:/home/node/app + - app_backend_node_modules:/home/node/app/teammapper-backend/node_modules + - app_frontend_node_modules:/home/node/app/teammapper-frontend/node_modules depends_on: - postgres @@ -59,7 +62,7 @@ services: - postgres_data:/var/lib/postgresql/data/pgdata playwright: - image: mcr.microsoft.com/playwright:v1.57.0-noble + image: mcr.microsoft.com/playwright:v1.58.2-noble container_name: playwright depends_on: - app diff --git a/openspec/changes/yjs-introduction/.openspec.yaml b/openspec/changes/yjs-introduction/.openspec.yaml new file mode 100644 index 000000000..2a45c1f43 --- /dev/null +++ b/openspec/changes/yjs-introduction/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-02-15 diff --git a/openspec/changes/yjs-introduction/design.md b/openspec/changes/yjs-introduction/design.md new file mode 100644 index 000000000..133046eda --- /dev/null +++ b/openspec/changes/yjs-introduction/design.md @@ -0,0 +1,271 @@ +## Context + +TeamMapper is a collaborative mind mapping application (NestJS backend, Angular frontend, PostgreSQL). Real-time collaboration currently works via Socket.io: each user action (add/update/remove node) is sent as an individual message, validated against the DB, and broadcast to other clients. In larger teams this causes state divergence — clients reference nodes that no longer exist server-side, triggering FK constraint violations, full-map-state reloads, and lost edits. + +The codebase has clear separation: the MMP library handles rendering (D3) and internal state, `MapSyncService` handles all network sync, `MapsGateway` handles server-side WebSocket logic, and `MapsService` handles persistence. The proposal calls for replacing the Socket.io sync layer with Yjs while keeping MMP, the DB schema, and the REST API unchanged. + +The new Yjs WebSocket backend (`yjs-gateway.service.ts`, `yjs-doc-manager.service.ts`, `yjs-persistence.service.ts`) also requires hardening for production stability. The system is single-instance (no horizontal scaling), uses the `ws` library in `noServer` mode attached to NestJS's HTTP server, and persists Y.Doc state to PostgreSQL via TypeORM. An audit identified 11 actionable stability issues across these three services. + +Key constraints: +- MMP library must not be modified (it stays as the renderer with its own internal state) +- DB schema (MmpMap, MmpNode tables) must remain unchanged +- History/undo replacement with Y.UndoManager is deferred to a future change +- The REST API (MapsController) continues to work against the same DB tables + +## Goals / Non-Goals + +**Goals:** +- Eliminate state divergence between clients by using CRDT-based synchronization +- Remove per-operation DB validation for real-time edits (DB becomes a persistence target) +- Simplify the sync architecture: one Y.Doc per map replaces ~10 Socket.io message types +- Maintain the existing DB schema for backward compatibility and REST API access +- Support the existing auth model (modification secret) at the connection level +- Preserve presence features (client colors, selection highlighting) +- Prevent process crashes from unhandled WebSocket errors +- Detect and clean up zombie connections automatically +- Protect against resource exhaustion from malicious or buggy clients +- Eliminate race conditions in connection lifecycle state management +- Ensure clean shutdown with no lost data or orphaned timers + +**Non-Goals:** +- Replacing MMP's internal state management or history system (Phase 2) +- Storing Y.Doc binary state in the database (future optimization) +- Offline-first support or local-first persistence (could build on Yjs later, but not in scope) +- Changing the MMP library in any way +- Modifying the REST API or MapsController +- Multi-server / horizontal scaling (single-server Y.Doc hosting is sufficient for now) +- CRDT document compaction or tombstone cleanup +- Distributed/multi-instance connection tracking (Redis, etc.) +- Frontend client changes (beyond Yjs migration) + +## Decisions + +### 1. Transport: y-websocket on a separate WebSocket path + +**Decision:** Use the `y-websocket` package with a raw `ws` WebSocket server mounted on the NestJS HTTP server at a dedicated path (e.g., `/yjs`). Remove Socket.io entirely. + +**Alternatives considered:** +- *Socket.io adapter for Yjs*: Community packages exist but are less mature than y-websocket. Socket.io adds overhead (polling fallback, packet framing) that Yjs doesn't need. +- *y-protocols directly on NestJS WebSocket gateway*: Maximum control but requires reimplementing the sync and awareness protocols manually. Unnecessary complexity. +- *Hocuspocus server*: Full-featured Yjs server framework. Adds a large dependency for features we don't need (webhooks, extensions, auth framework). Overkill. + +**Rationale:** `y-websocket` is the battle-tested reference implementation. Mounting a `ws` server on NestJS's HTTP server (`app.getHttpServer()`) is straightforward — the existing proxy config already forwards `/socket.io` with `ws: true`, and we simply change the path to `/yjs`. This avoids running a separate process. + +**Implementation approach:** +- Create a `YjsGateway` NestJS provider that initializes a `ws.Server` on the shared HTTP server with `path: '/yjs'` +- On WebSocket `connection`, authenticate (see Decision 4), then call `y-websocket`'s `setupWSConnection(ws, doc, { gc: true })` utility +- Update the frontend proxy config to forward `/yjs` to the backend with `ws: true` +- Remove `@nestjs/platform-socket.io`, `socket.io`, and `socket.io-client` dependencies + +### 2. Y.Doc structure: Y.Map of Y.Maps, mirroring existing node model + +**Decision:** Each map's Y.Doc contains a `Y.Map("nodes")` keyed by node ID, where each value is a `Y.Map` with the same fields as `ExportNodeProperties`. A separate `Y.Map("mapOptions")` holds map-level metadata. + +``` +Y.Doc (one per map) +├── Y.Map("nodes") +│ ├── "" → Y.Map +│ │ ├── "id" → string +│ │ ├── "parent" → string | null +│ │ ├── "name" → string +│ │ ├── "isRoot" → boolean +│ │ ├── "locked" → boolean +│ │ ├── "detached" → boolean +│ │ ├── "k" → number +│ │ ├── "coordinates" → { x, y } +│ │ ├── "colors" → { name, background, branch } +│ │ ├── "font" → { size, weight, style } +│ │ ├── "image" → { src, size } +│ │ └── "link" → { href } +│ └── ... +└── Y.Map("mapOptions") + ├── "name" → string + └── ... +``` + +**Alternatives considered:** +- *Y.Array of nodes*: Simpler, but doesn't support efficient single-node updates — the entire array entry would need replacing on any property change. Y.Map allows granular per-property conflict resolution. +- *Nested Y.Maps for sub-objects (coordinates, colors, font)*: Would allow per-field CRDT merging within sub-objects (e.g., two users changing `colors.name` and `colors.background` simultaneously). However, this adds complexity to the bridge layer and the sub-objects are small. Using plain JS objects for sub-properties is simpler and matches the existing data model directly. Can be refined later if sub-property conflicts become an issue. + +**Rationale:** Y.Map of Y.Maps gives us O(1) node lookup, per-node granular updates, and automatic CRDT merging. The structure maps 1:1 to `ExportNodeProperties`, minimizing the bridge layer's conversion logic. + +### 3. Persistence: Decode-on-save to existing tables + +**Decision:** A debounced persistence service reads the Y.Doc, extracts all nodes, and writes them to the existing MmpNode/MmpMap tables using a delete-all-then-insert transaction. Persistence triggers on a debounce timer (2 seconds after last change) and on last-client-disconnect. + +**Alternatives considered:** +- *Store Y.Doc binary (Y.encodeStateAsUpdate)*: Faster save/load, but requires a schema migration to add a binary column, and the existing REST API/queries would need a separate decode path. Deferred as future optimization. +- *Diff-based persistence (compare Y.Doc with DB, apply delta)*: More efficient for large maps, but significantly more complex. The existing `updateMap()` method already uses delete-all-then-insert, so this pattern is proven. + +**Rationale:** Delete-all-then-insert in a transaction is simple, correct, and matches the existing `MapsService.updateMap()` pattern. The DB schema stays completely unchanged. The REST API and scheduled cleanup jobs continue to work without modification. + +**Y.Doc lifecycle on server:** +1. First client connects to map → load nodes from DB via `MapsService.findNodes()`, hydrate a Y.Doc +2. While clients are connected → Y.Doc lives in memory, changes synced via y-websocket +3. On Y.Doc change → debounce 2s, then persist to DB +4. Last client disconnects → final persist, then evict Y.Doc from memory (with a grace period TTL of ~30s to handle quick reconnects) + +### 4. Auth: Modification secret verified at WebSocket handshake + +**Decision:** The modification secret is passed as a query parameter in the WebSocket URL (e.g., `ws://host/yjs?mapId=&secret=`). The server verifies it on connection and grants read-write or read-only access. Unauthorized write attempts are silently dropped. + +**Alternatives considered:** +- *Cookie-based auth*: The frontend already sends a `person_id` cookie, but this identifies the user, not the map permission. The modification secret is map-specific. +- *Auth after connection (first message)*: Adds complexity and a window where an unauthenticated client could receive data before auth completes. + +**Rationale:** Query parameters are available at handshake time, allowing immediate accept/reject. The existing `EditGuard.validateRequest()` logic can be reused directly. Read-only clients (no valid secret) still connect and receive Y.Doc state for viewing, but their write messages are not applied to the server Y.Doc. + +### 5. MMP integration: Bridge pattern in MapSyncService + +**Decision:** MapSyncService becomes a two-way bridge. MMP is unaware of Yjs. The bridge: +- Listens to MMP events (`nodeCreate`, `nodeUpdate`, `nodeRemove`, etc.) → writes changes to the local Y.Doc +- Observes Y.Doc changes → applies remote changes to MMP via its existing API (`addNode`, `updateNode`, `removeNode`) +- Uses a flag to prevent echo loops (MMP event → Y.Doc write → Y.Doc observe → back to MMP) + +**Alternatives considered:** +- *Deep MMP integration (MMP reads from Y.Doc directly)*: Cleanest architecture, but requires modifying MMP internals, violating the constraint. +- *MMP as pure renderer (strip state management)*: Even more invasive MMP changes. + +**Rationale:** The bridge pattern requires zero MMP changes. MMP's `notifyWithEvent` parameter (present on `addNode`, `updateNode`, `removeNode`) already supports suppressing events for server-originated changes — the current `MapSyncService` already uses this pattern when applying Socket.io updates. The bridge simply swaps the source from Socket.io events to Y.Doc observations. + +**Echo prevention:** +- When applying a remote Y.Doc change to MMP, call MMP methods with `notifyWithEvent: false` to suppress the MMP event +- When writing a local MMP event to Y.Doc, the Y.Doc `observe` callback checks `transaction.local` to distinguish local vs remote changes + +### 6. Presence: Yjs Awareness API + +**Decision:** Use the Yjs Awareness protocol (bundled with y-websocket) for client presence, colors, and node selection. Each client sets its awareness state with `{ color, selectedNodeId, userName }`. + +**Implementation:** +- On connect: `awareness.setLocalStateField('user', { color, selectedNodeId: null })` +- On node select: `awareness.setLocalStateField('user', { ...state, selectedNodeId: id })` +- Observe: `awareness.on('change', ...)` → update MMP node highlights and client list + +**What this replaces:** +- Socket.io `clientListUpdated` event → `awareness.getStates()` provides all connected clients +- Socket.io `selectionUpdated` event → awareness state changes propagate automatically +- Socket.io `clientDisconnect` event → awareness automatically removes disconnected clients +- Server-side `cache-manager` client tracking → no longer needed + +### 7. Map import and deletion: Special operations via Y.Doc transaction / REST + +**Map import** (current `updateMap` flow): +- Instead of disconnecting all clients and replacing data: perform a Y.Doc transaction that clears the `nodes` map and repopulates it from the imported data +- Yjs syncs the new state to all clients automatically — no disconnect/reconnect needed +- MMP's bridge layer receives the Y.Doc changes and calls `mmpService.new(snapshot)` to redraw + +**Map deletion:** +- Admin calls the existing REST endpoint (`DELETE /api/maps/:id`) +- Server destroys the Y.Doc instance and closes all WebSocket connections for that map +- Frontend detects WebSocket close → handles cleanup (same as current `mapDeleted` behavior) + +--- + +### WebSocket Hardening Decisions (not yet implemented) + +Decisions 8–17 address stability gaps identified in an audit of the Yjs WebSocket backend. These are required before production deployment but are not yet implemented. See tasks 6–12 for implementation plan. + +--- + +### 8. WebSocket error handling — log-and-terminate pattern + +Register `ws.on('error', handler)` in `setupSync` that logs the error and calls `ws.terminate()`. Termination (not `close()`) is deliberate — errors indicate a broken connection where the close handshake may not complete. The existing `close` handler already handles cleanup, and `terminate()` triggers it. + +**Alternative considered:** Attempting recovery on certain error codes. Rejected because WebSocket errors indicate transport-level failures where the connection is already unusable. + +### 9. Heartbeat — ws library ping/pong with `isAlive` flag + +Use the standard `ws` ping/pong pattern: a 30-second `setInterval` on the server iterates all clients, terminates those that haven't responded since the last ping, marks survivors as `isAlive = false`, and sends a new ping. Each connection registers a `pong` listener that sets `isAlive = true`. + +The interval is stored on the gateway and cleared in `onModuleDestroy`. The `isAlive` flag is stored on the WebSocket instance via a typed wrapper property. + +**Why 30 seconds:** Balances responsiveness (zombie detected in 30–60s) against network overhead. This is the standard interval used by the `ws` library examples. + +**Alternative considered:** Application-level heartbeat messages within the Yjs protocol. Rejected because ping/pong is a WebSocket standard (RFC 6455 §5.5.2), handled at the frame level with no application payload overhead, and `ws` provides native support. + +### 10. maxPayload — 1 MiB limit on WebSocketServer + +Set `maxPayload: 1_048_576` (1 MiB) in the `WebSocketServer` constructor options. The `ws` library automatically closes connections that exceed this with code 1009 (Message Too Big). No application-level enforcement needed. + +**Why 1 MiB:** Yjs sync messages for mind maps are typically 1–50 KB. A 1 MiB limit provides generous headroom for large initial syncs while blocking the 100 MiB default that enables memory attacks. + +### 11. Connection limits — in-memory tracking in the gateway + +Add three limit mechanisms to the `upgrade` handler in `onModuleInit`: + +1. **Global connection cap** (configurable, default 500): reject with HTTP 503 when exceeded +2. **Per-IP connection limit** (configurable, default 50): reject with HTTP 429 when exceeded +3. **Per-IP rate limit** (max 10 connections per 10-second window): reject rapid reconnect loops with HTTP 429 + +Track state using `Map` for per-IP counts (incremented on upgrade, decremented on close) and a simple sliding-window counter for rate limiting. Clean up IP entries when counts reach zero. + +**Why in-memory, not Redis:** TeamMapper is single-instance. In-memory tracking has zero latency and no external dependency. If horizontal scaling becomes a goal, this can be extracted to Redis later. + +**Alternative considered:** Using a middleware library like `express-rate-limit`. Rejected because the WebSocket upgrade path bypasses Express middleware — limits must be applied in the raw `server.on('upgrade')` handler. + +### 12. Connection setup timeout — Promise.race with AbortController + +Wrap the async `handleConnection` database operations (`findMap`, `getOrCreateDoc`) in a `Promise.race` against a 10-second timeout. On timeout, close the WebSocket with code 1013 (Try Again Later). + +Use `AbortController` to cancel the timeout timer when the operation completes successfully, avoiding timer leaks. + +### 13. Client count — derive from connection set (single source of truth) + +Replace the separate `clientCount` field in `DocEntry` with a method that returns `mapConnections.get(mapId)?.size ?? 0`. This eliminates the dual-tracking bug where `clientCount` in `yjs-doc-manager.service.ts` and the connection set in `yjs-gateway.service.ts` can drift. + +**Approach:** Pass the connection count into `decrementClientCount` / the eviction check from the gateway rather than maintaining an independent counter. The doc manager's `getClientCount` method will accept the count from the gateway. The gateway is the sole owner of connection state. + +**Alternative considered:** Making the doc manager own the connection set. Rejected because the gateway already tracks connections for broadcasting and awareness — moving that state would create a larger refactor with no benefit. + +### 14. Persistence service shutdown — OnModuleDestroy with flush + +Add `OnModuleDestroy` to `YjsPersistenceService`. On shutdown: +1. Clear all debounce timers +2. Unregister all doc observers +3. Flush pending persistence synchronously (best-effort) for any docs with active timers + +This runs before the gateway's `onModuleDestroy` (NestJS destroys in reverse dependency order), ensuring timers don't fire after database connections close. + +### 15. Grace timer race fix — try/finally in handleConnection + +Wrap the `getOrCreateDoc` → `trackConnection` → `incrementClientCount` sequence in `handleConnection` with a try/finally. If an error occurs after `getOrCreateDoc` but before the connection is fully tracked, the finally block checks whether the connection was tracked and, if not, ensures the doc manager can still start its grace timer (by not leaving the doc in a state where `clientCount` was never incremented but the grace timer was already canceled). + +### 16. Await decrementClientCount — add error handling + +Change the `handleClose` method to properly handle the async `decrementClientCount` call. Rather than `await` (which would require making `handleClose` async and changing the event listener), add `.catch()` to log and handle persistence errors explicitly. This prevents silent error swallowing while keeping the close handler non-blocking. + +### 17. Async deleteMap — await repository call + +Make `deleteMap` in `MapsService` return `Promise` and `await` the repository delete. Update both callers (`maps.controller.ts` line 74 and `maps.gateway.ts` line 146) to `await` the result. This is a **BREAKING** change to the method signature but all callers are already in async contexts. + +## Risks / Trade-offs + +**[Data loss window on server crash]** → The Y.Doc is persisted every ~2s. A server crash could lose up to 2 seconds of edits. Mitigation: This is acceptable for a collaborative mind mapping tool. The debounce interval can be tuned. Binary Y.Doc snapshots (future work) would enable faster, more frequent persistence. + +**[Memory usage for large maps]** → Each active map holds a Y.Doc in server memory. Mitigation: Y.Docs are evicted after last client disconnects (with 30s grace period). For the typical TeamMapper usage (maps with tens to hundreds of nodes), memory usage is negligible. Monitor and add eviction pressure if needed. + +**[History/undo is local-only in Phase 1]** → MMP's undo/redo continues to work for the local user, but undo/redo changes are no longer broadcast to other clients. This means User A's undo doesn't propagate to User B. Mitigation: This matches the behavior of most collaborative editors (Google Docs undo is also local). Replacing with Y.UndoManager in Phase 2 will give proper collaborative undo semantics. + +**[Breaking change for Socket.io clients]** → Any external integration relying on the Socket.io event protocol will break. Mitigation: There are no known external integrations. The REST API (which remains unchanged) is the stable external interface. + +**[No horizontal scaling]** → Y.Docs live in a single server's memory. If the app needs multiple server instances, Y.Doc state would need to be shared (e.g., via Redis pub/sub or a shared persistence layer). Mitigation: TeamMapper currently runs as a single instance. This can be addressed when/if scaling becomes necessary. + +**[Sub-property conflicts on nested objects]** → Two users changing different sub-properties of the same nested object (e.g., `colors.name` vs `colors.background`) will result in last-write-wins at the object level since nested objects are stored as plain JS objects, not nested Y.Maps. Mitigation: This is a rare scenario for mind map editing. If it becomes an issue, specific nested objects can be promoted to nested Y.Maps. + +**[Connection limits too aggressive]** → Make limits configurable via environment variables with sensible defaults. Log rejections at `warn` level so operators can tune. + +**[Heartbeat adds network overhead]** → Ping frames are 2 bytes + framing. At 30-second intervals with typical connection counts (<100), overhead is negligible. + +**[Client count refactor touches multiple files]** → The gateway and doc manager interfaces change, requiring test updates. Mitigated by keeping the change mechanical — derive count from set size instead of maintaining separately. + +**[Persistence flush on shutdown may timeout]** → Use a maximum 5-second flush timeout. After that, accept data loss for in-flight debounces (the data is already persisted from the most recent `persistImmediately` on last-client-disconnect). + +**[Rate limit state grows unbounded with many IPs]** → Clean up IP entries when connection count reaches zero. For the sliding-window rate limiter, expire old entries periodically (piggyback on the heartbeat interval). + +## Open Questions + +- **Debounce interval tuning**: Is 2 seconds the right default? Should it be configurable via settings? +- **Grace period on disconnect**: 30 seconds before Y.Doc eviction — sufficient for reconnection scenarios? +- **Map import notification**: The current flow shows a toast ("import in progress") to other clients. With Y.Doc transactions, the change is near-instant. Do we still need a notification? Could use Awareness to broadcast a transient "importing" state. +- **Configurable limits**: Should connection limits be configurable via `config.service.ts` or hardcoded? Recommendation: configurable with env vars and sensible defaults. +- **Metrics/observability**: Should we add counters for rejected connections, zombie kills, and timeout events? Useful for operators but adds scope. Recommendation: defer to a follow-up unless trivial to add. diff --git a/openspec/changes/yjs-introduction/proposal.md b/openspec/changes/yjs-introduction/proposal.md new file mode 100644 index 000000000..7a2cfe08b --- /dev/null +++ b/openspec/changes/yjs-introduction/proposal.md @@ -0,0 +1,71 @@ +## Why + +The current real-time collaboration system uses individual Socket.io messages per operation (addNode, updateNode, removeNode, etc.), each validated against the PostgreSQL database. In larger teams, clients frequently drift out of sync — e.g., a client sends an update for a node that another client already deleted, causing FK constraint violations, full-map-state reloads, and a degraded experience. There are many edge cases where local state diverges from server state, and the current last-write-wins approach with per-operation DB validation cannot resolve them reliably. Introducing Yjs (a CRDT library) as a sync layer eliminates these conflicts by design — concurrent changes merge deterministically without coordination. + +Additionally, the new Yjs WebSocket backend needs hardening for production stability. An audit revealed critical gaps: no error handler on individual connections (risking process crashes), no heartbeat mechanism (causing zombie connection accumulation), no message size or connection limits (enabling resource exhaustion), and several race conditions in connection lifecycle management. + +## What Changes + +### Yjs Introduction +- **Replace Socket.io data sync with Yjs sync protocol**: All node/map operations (addNodes, updateNode, removeNode, applyMapChangesByDiff) stop being individual Socket.io messages. Instead, clients share a Y.Doc that syncs via y-websocket over a standard WebSocket connection. +- **Replace Socket.io presence with Yjs Awareness**: Client list, colors, and node selection tracking move from custom Socket.io events to the Yjs Awareness API. +- **Add server-side Y.Doc management**: The backend hosts Y.Doc instances per active map, loaded from DB on first client connect, evicted after last client disconnects. +- **Add decode-on-save persistence**: A debounced persistence service reads the Y.Doc, extracts nodes, and writes them to the existing MmpNode/MmpMap tables in a transaction. +- **Rewrite MapSyncService as a Y.Doc bridge**: The frontend MapSyncService becomes a two-way bridge — MMP events write to Y.Doc, Y.Doc observations apply remote changes to MMP. +- **Remove per-operation server-side validation**: The DB is no longer the real-time arbiter. Yjs handles merge correctness. The DB becomes a persistence target written to periodically. +- **Remove Socket.io dependency for data operations**: Socket.io is fully replaced by y-websocket (data sync) and Yjs Awareness (presence). **BREAKING** for any clients relying on the current Socket.io event protocol. +- **Auth moves to WebSocket handshake**: The modification secret is verified when the WebSocket connection is established, not per-message. Read-only clients can receive state but not send updates. +- **MMP library stays unchanged**: MMP keeps its internal state, rendering, and event system. It is not aware of Yjs. +- **DB schema stays unchanged**: MmpMap and MmpNode tables, all migrations, and the REST API remain as-is. +- **History/undo stays as-is (Phase 1)**: MMP's snapshot-based history continues to work locally. Undo/redo diffs are no longer broadcast over the network. Replacing with Y.UndoManager is deferred to a future change. + +### WebSocket Hardening +- Add `ws.on('error')` handler to prevent unhandled errors from crashing the process +- Implement ping/pong heartbeat to detect and terminate zombie connections +- Set `maxPayload` on WebSocketServer to prevent memory exhaustion from oversized messages +- Add per-IP and global connection limits on WebSocket upgrades +- Add connection setup timeout for slow/unresponsive database scenarios +- Implement `OnModuleDestroy` in `YjsPersistenceService` to clear pending timers on shutdown +- Fix unawaited `decrementClientCount` in connection close handler +- Fix grace timer / client count increment race condition in `handleConnection` +- Unify client count tracking to derive from the connection set (single source of truth) +- Make `deleteMap` properly async with awaited database calls + +## Non-goals + +- Document compaction / CRDT tombstone cleanup (separate optimization effort) +- Awareness cleanup race on REST-triggered map deletion (existing defensive handling is sufficient) +- Explicit listener cleanup replacing implicit `doc.destroy()` behavior (low risk) +- Stale snapshot on rapid reconnect during grace period (self-correcting via debounced persistence) +- Frontend WebSocket client changes (beyond Yjs migration) +- Load balancing or horizontal scaling +- Replacing MMP's internal state management or history system (Phase 2) +- Storing Y.Doc binary state in the database (future optimization) +- Offline-first support or local-first persistence + +## Capabilities + +### New Capabilities +- `yjs-sync`: Y.Doc-based real-time synchronization — server-side Y.Doc lifecycle (load, sync, evict), y-websocket integration with NestJS, and the frontend WebsocketProvider setup. +- `yjs-persistence`: Decode-on-save persistence — debounced extraction of Y.Doc state into existing MmpNode/MmpMap tables, and hydration of Y.Doc from DB rows on first connect. +- `yjs-awareness`: Presence and selection tracking via Yjs Awareness API — client colors, connected client list, and node selection highlighting. +- `yjs-bridge`: MapSyncService rewrite — two-way bridge between MMP events and Y.Doc, replacing all Socket.io event handlers. +- `yjs-ws-connection-resilience`: Error handling on individual connections, ping/pong heartbeat for zombie detection, and connection setup timeout. +- `yjs-ws-resource-protection`: Message size limits (`maxPayload`), per-IP and global connection rate limiting, and concurrent connection caps. +- `yjs-ws-lifecycle-integrity`: Persistence service graceful shutdown, awaited async operations, grace timer race fix, unified client count tracking, and async `deleteMap`. + +### Modified Capabilities + + +## Impact + +- **Frontend MapSyncService** (`map-sync.service.ts`): Heavy rewrite. All Socket.io event setup, error handling, and operation methods replaced with Y.Doc observation and writes. Socket.io client dependency removed. +- **Backend MapsGateway** (`maps.gateway.ts`): Heavy rewrite. All `@SubscribeMessage` handlers for node operations replaced by Yjs WebSocket handler. Socket.io server dependency removed. +- **Backend MapsService** (`maps.service.ts`): Simplified. Per-operation validation logic largely removed. `deleteMap` made async with awaited repository call. +- **Backend YjsGateway** (`yjs-gateway.service.ts`): Error handler, heartbeat, connection limits, timeout, await fixes. +- **Backend YjsDocManager** (`yjs-doc-manager.service.ts`): Client count refactored to derive from connection set. +- **Backend YjsPersistence** (`yjs-persistence.service.ts`): `OnModuleDestroy` added for graceful shutdown. +- **Dependencies**: Add `yjs`, `y-websocket`, `y-protocols`. Remove `socket.io`, `socket.io-client` (for data ops). Remove `cache-manager` usage for client tracking (Awareness replaces it). No new runtime dependencies for hardening. +- **REST API**: Unchanged. Map CRUD, export/import endpoints continue to work against the same DB tables. +- **APIs**: WebSocket connections may be rejected with HTTP 429/503 when limits are exceeded. Connections will receive periodic pings. Oversized messages will cause connection termination. +- **Testing**: New unit tests for error handling, heartbeat, connection limits, timeout, shutdown, and lifecycle race conditions. E2E tests updated for WebSocket transport change. diff --git a/openspec/changes/yjs-introduction/specs/yjs-awareness/spec.md b/openspec/changes/yjs-introduction/specs/yjs-awareness/spec.md new file mode 100644 index 000000000..d9d0d9601 --- /dev/null +++ b/openspec/changes/yjs-introduction/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/changes/yjs-introduction/specs/yjs-bridge/spec.md b/openspec/changes/yjs-introduction/specs/yjs-bridge/spec.md new file mode 100644 index 000000000..1f7616d44 --- /dev/null +++ b/openspec/changes/yjs-introduction/specs/yjs-bridge/spec.md @@ -0,0 +1,93 @@ +## 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. + +#### 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 + +#### 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 + +#### 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 from `yDoc.getMap('nodes')` + +#### 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 + +#### 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')` + +### 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 `transaction.local` on Y.Doc observations to distinguish local from remote changes and only apply remote changes to MMP. + +#### Scenario: Local change does not echo back +- **WHEN** the user creates a node locally (MMP event → Y.Doc write) +- **THEN** the Y.Doc observer SHALL detect the change as `transaction.local === true` 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 + +### 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 + +### Requirement: Local undo/redo stays local +MMP's existing undo/redo (snapshot-based history) SHALL continue to function for local changes. Undo/redo diffs SHALL NOT be broadcast to other clients. Other clients will see the result of the undo/redo as normal Y.Doc changes. + +#### Scenario: User performs undo +- **WHEN** the user triggers undo in MMP +- **THEN** MMP SHALL redraw from its history snapshot and the bridge SHALL write the resulting node changes to the Y.Doc (adds, updates, deletes from the undo diff) +- **AND** other clients SHALL receive these as normal node changes, not as an undo operation + +#### Scenario: User performs redo +- **WHEN** the user triggers redo in MMP +- **THEN** MMP SHALL redraw from its history snapshot and the bridge SHALL write the resulting node changes to the Y.Doc +- **AND** other clients SHALL receive these as normal node changes, not as a redo operation diff --git a/openspec/changes/yjs-introduction/specs/yjs-persistence/spec.md b/openspec/changes/yjs-introduction/specs/yjs-persistence/spec.md new file mode 100644 index 000000000..cd31a6bd8 --- /dev/null +++ b/openspec/changes/yjs-introduction/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/changes/yjs-introduction/specs/yjs-sync/spec.md b/openspec/changes/yjs-introduction/specs/yjs-sync/spec.md new file mode 100644 index 000000000..32e884a6b --- /dev/null +++ b/openspec/changes/yjs-introduction/specs/yjs-sync/spec.md @@ -0,0 +1,85 @@ +## 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: 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/changes/yjs-introduction/specs/yjs-ws-connection-resilience/spec.md b/openspec/changes/yjs-introduction/specs/yjs-ws-connection-resilience/spec.md new file mode 100644 index 000000000..5664ade47 --- /dev/null +++ b/openspec/changes/yjs-introduction/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/changes/yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md b/openspec/changes/yjs-introduction/specs/yjs-ws-lifecycle-integrity/spec.md new file mode 100644 index 000000000..07a2d9def --- /dev/null +++ b/openspec/changes/yjs-introduction/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/changes/yjs-introduction/specs/yjs-ws-resource-protection/spec.md b/openspec/changes/yjs-introduction/specs/yjs-ws-resource-protection/spec.md new file mode 100644 index 000000000..88ee19c23 --- /dev/null +++ b/openspec/changes/yjs-introduction/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 diff --git a/openspec/changes/yjs-introduction/tasks.md b/openspec/changes/yjs-introduction/tasks.md new file mode 100644 index 000000000..4108839b6 --- /dev/null +++ b/openspec/changes/yjs-introduction/tasks.md @@ -0,0 +1,223 @@ + + +## 1. Feature Flags & Dependencies + +> **PR scope**: Add all new packages, introduce feature flags, add proxy config. No behavioral change. +> **App state after merge**: Identical to before. New dependencies installed but unused. + +- [x] 1.1 Add `yjs`, `y-protocols`, `y-websocket`, and `ws` (+ `@types/ws`) to backend dependencies +- [x] 1.2 Add `yjs` and `y-websocket` to frontend dependencies +- [x] 1.3 Add `YJS_ENABLED` env var support to backend `ConfigService` (default: `false`), with a `isYjsEnabled()` method +- [x] 1.4 Add `featureFlagYjs: false` to frontend `environment.ts` and `environment.prod.ts` +- [x] 1.5 Add `/yjs` entry to frontend `proxy.conf.json` forwarding to backend with `ws: true` +- [x] 1.6 Verify both frontend and backend build, lint, and tests pass with no behavioral change + +## 2. Backend: Y.Doc Management & Persistence Services + +> **PR scope**: New backend services for Y.Doc lifecycle and persistence. Fully self-contained, not wired to any WebSocket endpoint yet. Gated by feature flag. +> **App state after merge**: Identical to before. New services exist but are not invoked. + +- [x] 2.1 Create shared Y.Doc conversion utilities: functions to convert MmpNode entities ↔ Y.Map entries and MmpMap options ↔ Y.Map entries (reuse `mapClientNodeToMmpNode` / `mapMmpNodeToClient` patterns) +- [x] 2.2 Create `YjsDocManagerService`: maintains an in-memory `Map` keyed by map UUID +- [x] 2.3 Implement `getOrCreateDoc(mapId)`: returns existing Y.Doc or hydrates a new one from database by loading MmpNode rows via `MapsService.findNodes()` and MmpMap via `MapsService.findMap()`, populating `Y.Map("nodes")` and `Y.Map("mapOptions")` +- [x] 2.4 Implement client connection tracking per Y.Doc (increment/decrement count on connect/disconnect) +- [x] 2.5 Implement Y.Doc eviction: on last client disconnect, persist to DB, start a 30-second grace period timer, then evict Y.Doc from memory if no new connections arrive +- [x] 2.6 Implement `destroyDoc(mapId)`: force-destroy a Y.Doc (cancel timers, close connections) — used by map deletion +- [x] 2.7 Create `YjsPersistenceService` with `persistDoc(mapId, yDoc)`: decode Y.Doc nodes/options and write to existing MmpNode/MmpMap tables using delete-all-then-insert in a transaction (same pattern as existing `MapsService.updateMap()`) +- [x] 2.8 Implement debounced persistence: register a Y.Doc `update` observer that resets a 2-second timer, then calls `persistDoc()` on expiry +- [x] 2.9 Implement immediate persistence on last client disconnect (called before eviction grace period) +- [x] 2.10 Implement persistence error handling: log errors, do not crash, retry on next debounce cycle +- [x] 2.11 Update `lastModified` timestamps on MmpMap and MmpNode when persisting +- [x] 2.12 Write unit tests for: Y.Doc hydration from DB, Y.Doc eviction lifecycle, grace period, persistence decode, transaction atomicity, debounce coalescing, error recovery +- [x] 2.13 Verify backend builds, lints, and all tests pass + +## 3. Backend: Yjs WebSocket Server & Map Deletion + +> **PR scope**: New WebSocket endpoint at `/yjs` with auth. Integrate map deletion with Y.Doc cleanup. Wire new services into MapModule. Gated by `YJS_ENABLED` — when disabled, the ws.Server does not accept upgrades. Socket.io continues to work. +> **App state after merge**: Identical to before (flag is off). The `/yjs` endpoint exists but rejects connections when flag is off. + +- [x] 3.1 Create `YjsGateway` NestJS provider: initialize a `ws.Server` on the NestJS HTTP server at path `/yjs`, only accepting WebSocket upgrades when `YJS_ENABLED=true` +- [x] 3.2 Implement WebSocket `connection` handler: parse `mapId` and `secret` from URL query params, validate map exists via `MapsService.findMap()`, close connection with error if map not found +- [x] 3.3 Implement authentication at handshake: verify modification secret (reuse logic from `EditGuard.validateRequest()`), tag connection as read-write or read-only +- [x] 3.4 On authenticated connection: call `YjsDocManagerService.getOrCreateDoc(mapId)` and `setupWSConnection(ws, doc)` from `y-websocket` +- [x] 3.5 Implement read-only enforcement: for read-only clients, intercept and drop incoming Y.Doc sync update messages +- [x] 3.6 Implement connection close handler: decrement client count in `YjsDocManagerService`, trigger eviction logic +- [x] 3.7 Integrate map deletion with Y.Doc: update `MapsGateway.onDeleteMap` (or `MapsController`) to call `YjsDocManagerService.destroyDoc(mapId)` which closes all WebSocket connections for that map — only when `YJS_ENABLED=true` +- [x] 3.8 Register `YjsGateway`, `YjsDocManagerService`, `YjsPersistenceService` in `MapModule` providers +- [x] 3.9 Write integration tests: WebSocket connection with valid/invalid map ID, auth with valid/invalid secret, read-only client cannot write, map deletion closes connections +- [x] 3.10 Verify backend builds, lints, and all tests pass (Socket.io gateway still active and functional) + +## 4. Frontend: Yjs Bridge & Awareness (behind feature flag) + +> **PR scope**: Full frontend Yjs integration behind `featureFlagYjs`. When flag is off, the existing Socket.io code path runs unchanged. When flag is on, MapSyncService uses Y.Doc + WebsocketProvider. Both code paths coexist. +> **App state after merge**: Identical to before (flag is off). Yjs path is testable by setting flag to true. + +- [x] 4.1 Refactor `MapSyncService` to branch on `featureFlagYjs`: extract current Socket.io logic into a private `initSocketIo()` path, create a parallel `initYjs()` path. The `initMap()` and `listenServerEvents()` methods call one or the other based on the flag. +- [x] 4.2 In the Yjs path: create a `Y.Doc` instance and a `WebsocketProvider` connecting to `/yjs` with map UUID as room name and modification secret as `params` query parameter +- [x] 4.3 Configure `WebsocketProvider` with reconnection support (reconnectionDelay matching current Socket.io config) +- [x] 4.4 Derive connection status from `WebsocketProvider` status events (`'connected'` / `'disconnected'`) and emit to `connectionStatusSubject` +- [x] 4.5 Implement bridge — MMP events to Y.Doc writes: `nodeCreate` → set in `nodes` Y.Map, `nodeUpdate` → update Y.Map entry, `nodeRemove` → delete from Y.Map, `nodePaste` → batch add in `yDoc.transact()`, `updateMapOptions` → update `mapOptions` Y.Map +- [x] 4.6 Implement bridge — Y.Doc observations to MMP: observe `nodes` Y.Map for remote changes (add → `mmpService.addNode()`, update → `mmpService.updateNode()`, delete → `mmpService.removeNode()`), all with `notifyWithEvent: false` +- [x] 4.7 Implement echo prevention: in Y.Doc observers, check `transaction.local` and skip local changes +- [x] 4.8 Implement initial map load: on first Y.Doc sync, extract all nodes from `nodes` map, convert to `ExportNodeProperties[]`, call `mmpService.new(snapshot)` to initialize MMP +- [x] 4.9 Implement map import in Yjs path: clear and repopulate Y.Doc `nodes` map in a `yDoc.transact()` call (replacing the Socket.io `updateMap` flow) +- [x] 4.10 Implement undo/redo in Yjs path: after MMP redraws from history snapshot, write the resulting node changes (adds, updates, deletes) to Y.Doc instead of `socket.emit('applyMapChangesByDiff')` +- [x] 4.11 Implement map deletion handling in Yjs path: detect WebSocket close by server → redirect or show notification (replacing Socket.io `mapDeleted` handler) +- [x] 4.12 Implement Yjs Awareness — presence: set initial awareness state `{ color, selectedNodeId: null }` on connect, with color collision detection via `awareness.getStates()` +- [x] 4.13 Implement Yjs Awareness — selection: update `selectedNodeId` on MMP `nodeSelect`/`nodeDeselect` events +- [x] 4.14 Implement Yjs Awareness — client list: observe awareness changes, derive client colors from `awareness.getStates()`, emit to `clientListSubject` +- [x] 4.15 Implement Yjs Awareness — node highlighting: observe remote clients' `selectedNodeId` changes, call `mmpService.highlightNode()` with their color, ignore references to non-existent nodes +- [x] 4.16 Implement cleanup on destroy: disconnect `WebsocketProvider`, destroy Y.Doc, remove observers +- [x] 4.17 Verify frontend builds, lints, and unit tests pass with flag off (Socket.io path unchanged) + +## 5. Integration Testing & Flag Activation + +> **PR scope**: Enable the feature flags, verify end-to-end functionality with Yjs active. Both backends (Socket.io gateway + Yjs gateway) are still running, but the frontend now uses Yjs by default. +> **App state after merge**: App uses Yjs for real-time sync. Socket.io code still present but unused. + +- [x] 5.1 Set `YJS_ENABLED=true` as the default in backend configuration (or `.env` / docker-compose) +- [x] 5.2 Set `featureFlagYjs: true` in `environment.ts` +- [x] 5.3 Update E2E tests to work with the Yjs WebSocket transport (update any test helpers that depend on Socket.io events or connection patterns) +- [x] 5.4 Run E2E tests (`pnpm run playwright test --reporter=list`) +- [x] 5.5 Manual smoke test: open two browser tabs on the same map and verify real-time sync of node create, update, delete, selection highlighting, undo/redo, map import, and map deletion +- [x] 5.6 Verify backend and frontend build, lint, and all tests pass + +--- + + + +## 6. WebSocket Error Handler and maxPayload + +> **PR scope**: Add error handling on individual connections and message size limits. No behavioral change for normal clients. +> **App state after merge**: Server resilient to connection errors and oversized messages. +> **PRODUCTION-BLOCKING**: Without this, a single ECONNRESET crashes the server process. + +- [x] 6.1 Add `ws.on('error')` handler in `setupSync` that logs error with mapId and calls `ws.terminate()` +- [x] 6.2 Set `maxPayload: 1_048_576` on the `WebSocketServer` constructor in `onModuleInit` +- [x] 6.3 Add unit tests: error event logs and terminates without crashing, oversized message triggers close +- [x] 6.4 Run lint, test, format — verify app still works + +## 7. Ping/Pong Heartbeat + +> **PR scope**: Add zombie connection detection via WebSocket ping/pong. Self-contained addition. +> **App state after merge**: Server automatically detects and cleans up zombie connections. +> **PRODUCTION-BLOCKING**: Without this, zombie connections accumulate indefinitely and leak memory. + +- [x] 7.1 Add `isAlive` typed property tracking on WebSocket connections (set `true` on connect and on `pong`) +- [x] 7.2 Add 30-second `setInterval` in `onModuleInit` that iterates clients, terminates dead ones, pings live ones +- [x] 7.3 Register `ws.on('pong')` handler in `setupSync` to set `isAlive = true` +- [x] 7.4 Clear the heartbeat interval in `cleanup` / `onModuleDestroy` +- [x] 7.5 Add unit tests: zombie connection terminated after missed pong, healthy connection survives, interval cleared on shutdown +- [x] 7.6 Run lint, test, format — verify app still works + +## 8. Connection Limits + +> **PR scope**: Add per-IP and global connection limits to prevent resource exhaustion. Self-contained addition. +> **App state after merge**: Server rejects connections exceeding configured limits. +> **PRODUCTION-BLOCKING**: Without this, a single client can open unlimited connections and exhaust server resources. + +- [x] 8.1 Add connection limit constants to `config.service.ts` (global max 500, per-IP max 50, rate limit 10/10s) with env var overrides +- [x] 8.2 Add in-memory tracking state to gateway: global count, per-IP count map, per-IP rate window map +- [x] 8.3 Add limit checks in the `server.on('upgrade')` handler — reject with HTTP 503 (global) or 429 (per-IP / rate) +- [x] 8.4 Decrement per-IP count on connection close in `handleClose`; clean up entries at zero +- [x] 8.5 Add periodic cleanup of expired rate-limit window entries (piggyback on heartbeat interval) +- [x] 8.6 Add unit tests: connection rejected at global limit (503), per-IP limit (429), rate limit (429); counts decrement on close; IP entry removed at zero +- [x] 8.7 Run lint, test, format — verify app still works + +## 9. Connection Setup Timeout + +> **PR scope**: Add timeout on async connection setup to handle slow/unresponsive database. Self-contained addition. +> **App state after merge**: Connections that stall during setup are closed after 10 seconds. +> **PRODUCTION-BLOCKING**: Without this, slow DB responses hang connections indefinitely. + +- [x] 9.1 Wrap async operations in `handleConnection` with `Promise.race` against a 10-second timeout using `AbortController` +- [x] 9.2 Close WebSocket with code 1013 on timeout; cancel timer on success/failure via `AbortController` +- [x] 9.3 Add unit tests: setup completes within timeout (normal), setup exceeds timeout (closed with 1013), timer canceled on early completion +- [x] 9.4 Run lint, test, format — verify app still works + +## 10. Persistence Shutdown and Async Close Fixes + +> **PR scope**: Fix persistence service shutdown lifecycle and async error handling in close handler. Fix grace timer race condition. +> **App state after merge**: Clean shutdown with no orphaned timers, no silently swallowed errors. +> **PRODUCTION-BLOCKING**: Without this, grace timer race can orphan Y.Docs in memory permanently. + +- [x] 10.1 Implement `OnModuleDestroy` in `YjsPersistenceService`: clear all debounce timers, unregister observers, best-effort flush with 5-second timeout +- [x] 10.2 Add `.catch()` error handling on `decrementClientCount` call in `handleClose` to log persistence errors with mapId +- [x] 10.3 Add try/finally in `handleConnection` around `getOrCreateDoc` → `trackConnection` → `incrementClientCount` to restore grace timer on failure +- [x] 10.4 Add unit tests: persistence shutdown clears timers and flushes, decrement errors are logged not swallowed, grace timer restored on setup failure +- [x] 10.5 Run lint, test, format — verify app still works + +## 11. Unified Client Count Tracking + +> **PR scope**: Refactor client count to derive from connection set (single source of truth). Touches gateway and doc manager. +> **App state after merge**: Client count always matches actual connection count — no drift possible. +> **PRODUCTION-BLOCKING**: Without this, client count drift can cause premature or delayed Y.Doc eviction. + +- [x] 11.1 Remove `clientCount` field from `DocEntry` in `yjs-doc-manager.service.ts` +- [x] 11.2 Replace `incrementClientCount` / `decrementClientCount` with a `notifyClientCount(mapId: string, count: number)` method that accepts the count from the gateway +- [x] 11.3 Update gateway's `handleClose` to pass `mapConnections.get(mapId)?.size ?? 0` to doc manager after removing the connection +- [x] 11.4 Update gateway's `handleConnection` to pass connection count after `trackConnection` +- [x] 11.5 Update `getClientCount` to accept count from gateway or return 0 if doc not tracked +- [x] 11.6 Update existing unit tests in `yjs-doc-manager.service.spec.ts` and `yjs-gateway.service.spec.ts` for the new interface +- [x] 11.7 Run lint, test, format — verify app still works + +## 12. Async deleteMap + +> **PR scope**: Make `deleteMap` properly async with awaited database calls. Small change touching callers. +> **App state after merge**: Map deletion errors properly propagate to callers. +> **PRODUCTION-BLOCKING**: Without this, deletion errors are silently swallowed and callers respond before delete completes. + +- [x] 12.1 Change `deleteMap` in `maps.service.ts` to `async deleteMap(uuid: string): Promise` with `await` on the repository delete +- [x] 12.2 Update `maps.controller.ts` to `await this.mapsService.deleteMap(mapId)` +- [x] 12.3 Update `maps.gateway.ts` to `await this.mapsService.deleteMap(request.mapId)` +- [x] 12.4 Update `maps.controller.spec.ts` mock to return a resolved promise +- [x] 12.5 Run lint, test, format — verify app still works + +## 13. Cleanup: Remove Socket.io & Dead Code + +> **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`) diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 000000000..d1926562c --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,21 @@ +schema: spec-driven + +context: | + TeamMapper: collaborative web-based mind mapping application (real-time, multi-user) + Monorepo with pnpm workspaces (teammapper-backend, teammapper-frontend) + Backend: NestJS, TypeORM, PostgreSQL, Socket.IO, TypeScript + Frontend: Angular 21, Angular Material, D3.js, Socket.IO client, TypeScript + Real-time collaboration via WebSockets (Socket.IO) + Testing: Jest (unit), Playwright (e2e) + Linting/formatting: ESLint, Prettier + Methods max 10 lines, no `any` types, reuse existing code + +# Per-artifact rules (optional) +# Add custom rules for specific artifacts. +rules: + proposal: + - Keep proposals under 500 words + - Always include a "Non-goals" section + tasks: + - Structure tasks in sections that are good estimates for common pull / merge requests + - Use feature flags if needed, so that after each section the app is still working diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd0d6c8f4..bb7cac87f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,52 +16,52 @@ importers: dependencies: '@ai-sdk/openai': specifier: 2.0.88 - version: 2.0.88(zod@4.1.13) + version: 2.0.88(zod@4.3.5) '@ai-sdk/openai-compatible': specifier: 1.0.29 - version: 1.0.29(zod@4.1.13) + version: 1.0.29(zod@4.3.5) '@nestjs/cache-manager': - specifier: ^3.0.1 - version: 3.0.1(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(cache-manager@7.2.7)(keyv@5.5.5)(rxjs@7.8.2) + specifier: ^3.1.0 + version: 3.1.0(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2) '@nestjs/cli': - specifier: ^11.0.14 - version: 11.0.14(@types/node@25.0.3) + specifier: ^11.0.16 + version: 11.0.16(@types/node@25.2.3) '@nestjs/common': - specifier: ^11.1.9 - version: 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.13 + version: 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/config': - specifier: 4.0.2 - version: 4.0.2(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) + specifier: 4.0.3 + version: 4.0.3(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2) '@nestjs/core': - specifier: ^11.1.9 - version: 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.13 + version: 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/platform-express': - specifier: ^11.1.9 - version: 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) + specifier: ^11.1.13 + version: 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) '@nestjs/platform-socket.io': - specifier: ^11.1.9 - version: 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.9)(rxjs@7.8.2) + specifier: ^11.1.13 + version: 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.13)(rxjs@7.8.2) '@nestjs/schedule': - specifier: ^6.1.0 - version: 6.1.0(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) + specifier: ^6.1.1 + version: 6.1.1(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) '@nestjs/serve-static': specifier: ^5.0.4 - version: 5.0.4(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(express@5.2.1) + version: 5.0.4(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(express@5.2.1) '@nestjs/typeorm': specifier: ^11.0.0 - version: 11.0.0(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.16.3)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))) + version: 11.0.0(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3))) '@nestjs/websockets': - specifier: ^11.1.9 - version: 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-socket.io@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + specifier: ^11.1.13 + version: 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-socket.io@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@types/uuid': specifier: ^11.0.0 version: 11.0.0 ai: specifier: 5.0.116 - version: 5.0.116(zod@4.1.13) + version: 5.0.116(zod@4.3.5) cache-manager: - specifier: ^7.2.7 - version: 7.2.7 + specifier: ^7.2.8 + version: 7.2.8 class-validator: specifier: ^0.14.3 version: 0.14.3 @@ -77,9 +77,12 @@ importers: jsonwebtoken: specifier: ^9.0.3 version: 9.0.3 + lib0: + specifier: ^0.2.117 + version: 0.2.117 pg: - specifier: ^8.16.3 - version: 8.16.3 + specifier: ^8.18.0 + version: 8.18.0 pq: specifier: ^0.0.3 version: 0.0.3 @@ -87,30 +90,42 @@ importers: specifier: ^0.2.2 version: 0.2.2 rimraf: - specifier: ^6.1.2 - version: 6.1.2 + specifier: ^6.1.3 + version: 6.1.3 rxjs: specifier: ^7.8.2 version: 7.8.2 socket.io: - specifier: 4.8.1 - version: 4.8.1 + specifier: 4.8.3 + version: 4.8.3 typeorm: specifier: ^0.3.28 - version: 0.3.28(pg@8.16.3)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + version: 0.3.28(pg@8.18.0)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) uuid: specifier: 11.1.0 version: 11.1.0 + ws: + specifier: ^8.19.0 + version: 8.19.0 + y-protocols: + specifier: ^1.0.7 + version: 1.0.7(yjs@13.6.29) + y-websocket: + specifier: ^3.0.0 + version: 3.0.0(yjs@13.6.29) + yjs: + specifier: ^13.6.29 + version: 13.6.29 devDependencies: '@eslint/compat': - specifier: ^2.0.0 - version: 2.0.0(eslint@9.39.2(jiti@2.6.1)) + specifier: ^2.0.2 + version: 2.0.2(eslint@9.39.2(jiti@2.6.1)) '@eslint/js': specifier: ^9.39.2 version: 9.39.2 '@golevelup/ts-jest': - specifier: ^1.1.1 - version: 1.1.1 + specifier: ^1.2.1 + version: 1.2.1 '@jest/globals': specifier: ^30.2.0 version: 30.2.0 @@ -118,11 +133,11 @@ importers: specifier: ^11.0.9 version: 11.0.9(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': - specifier: ^11.1.9 - version: 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9) + specifier: ^11.1.13 + version: 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-express@11.1.13) '@stylistic/eslint-plugin': - specifier: ^5.6.1 - version: 5.6.1(eslint@9.39.2(jiti@2.6.1)) + specifier: ^5.8.0 + version: 5.8.0(eslint@9.39.2(jiti@2.6.1)) '@types/cache-manager': specifier: 5.0.0 version: 5.0.0 @@ -136,8 +151,8 @@ importers: specifier: ^5.0.6 version: 5.0.6 '@types/express-serve-static-core': - specifier: ^5.1.0 - version: 5.1.0 + specifier: ^5.1.1 + version: 5.1.1 '@types/jest': specifier: 30.0.0 version: 30.0.0 @@ -145,17 +160,20 @@ importers: specifier: ^9.0.10 version: 9.0.10 '@types/node': - specifier: ^25.0.3 - version: 25.0.3 + specifier: ^25.2.3 + version: 25.2.3 '@types/supertest': specifier: ^6.0.3 version: 6.0.3 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 '@typescript-eslint/eslint-plugin': - specifier: ^8.50.0 - version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.50.0 - version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) @@ -167,43 +185,43 @@ importers: version: 4.4.4(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-import: specifier: 2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + version: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jest: - specifier: 29.6.0 - version: 29.6.0(@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3) + specifier: 29.15.0 + version: 29.15.0(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) eslint-plugin-nestjs: specifier: 1.2.3 version: 1.2.3 eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4) + specifier: ^5.5.5 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) eslint-plugin-typeorm: specifier: 0.0.19 version: 0.0.19 globals: - specifier: ^16.5.0 - version: 16.5.0 + specifier: ^17.3.0 + version: 17.3.0 jest: specifier: 30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + version: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) prettier: - specifier: ^3.7.4 - version: 3.7.4 + specifier: ^3.8.1 + version: 3.8.1 socket.io-client: - specifier: ^4.8.1 - version: 4.8.1 + specifier: ^4.8.3 + version: 4.8.3 supertest: - specifier: ^7.1.4 - version: 7.1.4 + specifier: ^7.2.2 + version: 7.2.2 ts-jest: specifier: 29.4.6 - version: 29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3) + version: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) ts-loader: specifier: ^9.5.4 - version: 9.5.4(typescript@5.9.3)(webpack@5.104.0) + version: 9.5.4(typescript@5.9.3)(webpack@5.105.0) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + version: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -211,77 +229,77 @@ importers: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.50.0 - version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) teammapper-frontend: dependencies: '@angular-devkit/build-angular': - specifier: 21.0.4 - version: 21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.0.3)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3) + specifier: 21.1.4 + version: 21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.2.3)(chokidar@5.0.0)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3) '@angular/animations': - specifier: 21.0.6 - version: 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + specifier: 21.1.4 + version: 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@angular/cdk': - specifier: 21.0.5 - version: 21.0.5(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + specifier: 21.1.4 + version: 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) '@angular/cli': - specifier: 21.0.4 - version: 21.0.4(@types/node@25.0.3)(chokidar@4.0.3) + specifier: 21.1.4 + version: 21.1.4(@types/node@25.2.3)(chokidar@5.0.0) '@angular/common': - specifier: 21.0.6 - version: 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + specifier: 21.1.4 + version: 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) '@angular/compiler': - specifier: 21.0.6 - version: 21.0.6 + specifier: 21.1.4 + version: 21.1.4 '@angular/compiler-cli': - specifier: 21.0.6 - version: 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) + specifier: 21.1.4 + version: 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) '@angular/core': - specifier: 21.0.6 - version: 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + specifier: 21.1.4 + version: 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) '@angular/forms': - specifier: 21.0.6 - version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + specifier: 21.1.4 + version: 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) '@angular/material': - specifier: 21.0.5 - version: 21.0.5(fa41ef7fb045ee86255967d043d95fbe) + specifier: 21.1.4 + version: 21.1.4(243a22586a6c3cb85765d3835a5d9640) '@angular/platform-browser': - specifier: 21.0.6 - version: 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + specifier: 21.1.4 + version: 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@angular/platform-browser-dynamic': - specifier: 21.0.6 - version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))) + specifier: 21.1.4 + version: 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))) '@angular/router': - specifier: 21.0.6 - version: 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + specifier: 21.1.4 + version: 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) '@fortawesome/angular-fontawesome': specifier: ^4.0.0 - version: 4.0.0(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + version: 4.0.0(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@fortawesome/fontawesome-svg-core': - specifier: ^7.1.0 - version: 7.1.0 + specifier: ^7.2.0 + version: 7.2.0 '@fortawesome/free-brands-svg-icons': - specifier: ^7.1.0 - version: 7.1.0 + specifier: ^7.2.0 + version: 7.2.0 '@fortawesome/free-solid-svg-icons': - specifier: ^7.1.0 - version: 7.1.0 + specifier: ^7.2.0 + version: 7.2.0 '@material-design-icons/font': specifier: ^0.14.15 version: 0.14.15 '@ngx-translate/core': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + version: 17.0.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@ngx-translate/http-loader': specifier: ^17.0.0 - version: 17.0.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + version: 17.0.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@teammapper/mermaid-mindmap-parser': specifier: workspace:^ version: link:packages/mermaid-mindmap-parser ai: specifier: ^5.0.116 - version: 5.0.116(zod@4.1.13) + version: 5.0.116(zod@4.3.5) angular2-hotkeys: specifier: ^16.0.1 version: 16.0.1 @@ -295,17 +313,17 @@ importers: specifier: 3.3.1 version: 3.3.1 jspdf: - specifier: ^3.0.4 - version: 3.0.4 + specifier: ^4.1.0 + version: 4.1.0 localforage: specifier: 1.10.0 version: 1.10.0 ngx-color-picker: specifier: ^20.1.1 - version: 20.1.1(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/forms@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)) + version: 20.1.1(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/forms@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)) ngx-toastr: specifier: ^19.1.0 - version: 19.1.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))) + version: 19.1.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))) qr-code-styling: specifier: 1.9.2 version: 1.9.2 @@ -313,60 +331,66 @@ importers: specifier: ~7.8.2 version: 7.8.2 socket.io-client: - specifier: ~4.8.1 - version: 4.8.1 + specifier: ~4.8.3 + version: 4.8.3 tslib: specifier: ^2.8.1 version: 2.8.1 uuid: specifier: 13.0.0 version: 13.0.0 + y-websocket: + specifier: ^3.0.0 + version: 3.0.0(yjs@13.6.29) + yjs: + specifier: ^13.6.29 + version: 13.6.29 zone.js: specifier: ^0.16.0 version: 0.16.0 devDependencies: '@angular-builders/jest': - specifier: ^21.0.0-beta.1 - version: 21.0.0-beta.1(634017c93eb9ec4294b09b831a21836c) + specifier: ^21.0.3 + version: 21.0.3(ff520fd63e6ac660c8d0c8706a71b20b) '@angular-devkit/architect': - specifier: 0.2100.4 - version: 0.2100.4(chokidar@4.0.3) + specifier: 0.2101.4 + version: 0.2101.4(chokidar@5.0.0) '@angular-devkit/core': - specifier: 21.0.4 - version: 21.0.4(chokidar@4.0.3) + specifier: 21.1.4 + version: 21.1.4(chokidar@5.0.0) '@angular-devkit/schematics': - specifier: 21.0.4 - version: 21.0.4(chokidar@4.0.3) + specifier: 21.1.4 + version: 21.1.4(chokidar@5.0.0) '@angular-eslint/builder': - specifier: 21.1.0 - version: 21.1.0(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 21.2.0 + version: 21.2.0(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@angular-eslint/eslint-plugin': - specifier: 21.1.0 - version: 21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 21.2.0 + version: 21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@angular-eslint/eslint-plugin-template': - specifier: 21.1.0 - version: 21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 21.2.0 + version: 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@angular-eslint/schematics': - specifier: 21.1.0 - version: 21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 21.2.0 + version: 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@angular-eslint/template-parser': - specifier: 21.1.0 - version: 21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: 21.2.0 + version: 21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@angular/language-service': - specifier: 21.0.6 - version: 21.0.6 + specifier: 21.1.4 + version: 21.1.4 '@compodoc/compodoc': - specifier: ^1.1.32 - version: 1.1.32(@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)) + 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)) '@eslint/js': specifier: ^9.39.2 version: 9.39.2 '@playwright/test': - specifier: ^1.57.0 - version: 1.57.0 + specifier: ^1.58.2 + version: 1.58.2 '@schematics/angular': - specifier: ^21.0.4 - version: 21.0.4(chokidar@4.0.3) + specifier: ^21.1.4 + version: 21.1.4(chokidar@5.0.0) '@types/d3': specifier: 7.4.3 version: 7.4.3 @@ -377,17 +401,17 @@ importers: specifier: 1.6.15 version: 1.6.15 '@types/node': - specifier: ^25.0.3 - version: 25.0.3 + specifier: ^25.2.3 + version: 25.2.3 '@typescript-eslint/eslint-plugin': - specifier: ^8.50.0 - version: 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.50.0 - version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) angular-eslint: - specifier: ^21.1.0 - version: 21.1.0(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript-eslint@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3) + specifier: ^21.2.0 + version: 21.2.0(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3) eslint: specifier: ^9.39.2 version: 9.39.2(jiti@2.6.1) @@ -395,17 +419,17 @@ importers: specifier: ^10.1.8 version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jest: - specifier: ^29.6.0 - version: 29.6.0(@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3) + specifier: ^29.15.0 + version: 29.15.0(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4) + specifier: ^5.5.5 + version: 5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1) globals: - specifier: ^16.5.0 - version: 16.5.0 + specifier: ^17.3.0 + version: 17.3.0 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + version: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-canvas-mock: specifier: ^2.5.2 version: 2.5.2 @@ -414,35 +438,35 @@ importers: version: 30.2.0 jest-preset-angular: specifier: ^16.0.0 - version: 16.0.0(b4775edd0d85938ea7ee883bbb12d4b1) + version: 16.0.0(128486741484e87e44937e8ea7d8f148) minimist: specifier: ^1.2.8 version: 1.2.8 prettier: - specifier: ^3.7.4 - version: 3.7.4 + specifier: ^3.8.1 + version: 3.8.1 ts-node: specifier: ~10.9.2 - version: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + version: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) typescript: specifier: ~5.9.3 version: 5.9.3 typescript-eslint: - specifier: ^8.50.0 - version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + specifier: ^8.55.0 + version: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) optionalDependencies: '@nx/nx-darwin-arm64': - specifier: 22.3.3 - version: 22.3.3 + specifier: 22.5.1 + version: 22.5.1 '@nx/nx-darwin-x64': - specifier: 22.3.3 - version: 22.3.3 + specifier: 22.5.1 + version: 22.5.1 '@nx/nx-linux-x64-gnu': - specifier: 22.3.3 - version: 22.3.3 + specifier: 22.5.1 + version: 22.5.1 '@nx/nx-win32-x64-msvc': - specifier: 22.3.3 - version: 22.3.3 + specifier: 22.5.1 + version: 22.5.1 teammapper-frontend/packages/mermaid-mindmap-parser: dependencies: @@ -457,8 +481,8 @@ importers: specifier: ~5.9.3 version: 5.9.3 vite: - specifier: ^7.3.0 - version: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.1) + specifier: ^7.3.1 + version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.46.0) packages: @@ -493,72 +517,72 @@ packages: resolution: {integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==} engines: {node: '>=18'} - '@algolia/abtesting@1.6.1': - resolution: {integrity: sha512-wV/gNRkzb7sI9vs1OneG129hwe3Q5zPj7zigz3Ps7M5Lpo2hSorrOnXNodHEOV+yXE/ks4Pd+G3CDFIjFTWhMQ==} + '@algolia/abtesting@1.12.2': + resolution: {integrity: sha512-oWknd6wpfNrmRcH0vzed3UPX0i17o4kYLM5OMITyMVM2xLgaRbIafoxL0e8mcrNNb0iORCJA0evnNDKRYth5WQ==} engines: {node: '>= 14.0.0'} - '@algolia/client-abtesting@5.40.1': - resolution: {integrity: sha512-cxKNATPY5t+Mv8XAVTI57altkaPH+DZi4uMrnexPxPHODMljhGYY+GDZyHwv9a+8CbZHcY372OkxXrDMZA4Lnw==} + '@algolia/client-abtesting@5.46.2': + resolution: {integrity: sha512-oRSUHbylGIuxrlzdPA8FPJuwrLLRavOhAmFGgdAvMcX47XsyM+IOGa9tc7/K5SPvBqn4nhppOCEz7BrzOPWc4A==} engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@5.40.1': - resolution: {integrity: sha512-XP008aMffJCRGAY8/70t+hyEyvqqV7YKm502VPu0+Ji30oefrTn2al7LXkITz7CK6I4eYXWRhN6NaIUi65F1OA==} + '@algolia/client-analytics@5.46.2': + resolution: {integrity: sha512-EPBN2Oruw0maWOF4OgGPfioTvd+gmiNwx0HmD9IgmlS+l75DatcBkKOPNJN+0z3wBQWUO5oq602ATxIfmTQ8bA==} engines: {node: '>= 14.0.0'} - '@algolia/client-common@5.40.1': - resolution: {integrity: sha512-gWfQuQUBtzUboJv/apVGZMoxSaB0M4Imwl1c9Ap+HpCW7V0KhjBddqF2QQt5tJZCOFsfNIgBbZDGsEPaeKUosw==} + '@algolia/client-common@5.46.2': + resolution: {integrity: sha512-Hj8gswSJNKZ0oyd0wWissqyasm+wTz1oIsv5ZmLarzOZAp3vFEda8bpDQ8PUhO+DfkbiLyVnAxsPe4cGzWtqkg==} engines: {node: '>= 14.0.0'} - '@algolia/client-insights@5.40.1': - resolution: {integrity: sha512-RTLjST/t+lsLMouQ4zeLJq2Ss+UNkLGyNVu+yWHanx6kQ3LT5jv8UvPwyht9s7R6jCPnlSI77WnL80J32ZuyJg==} + '@algolia/client-insights@5.46.2': + resolution: {integrity: sha512-6dBZko2jt8FmQcHCbmNLB0kCV079Mx/DJcySTL3wirgDBUH7xhY1pOuUTLMiGkqM5D8moVZTvTdRKZUJRkrwBA==} engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@5.40.1': - resolution: {integrity: sha512-2FEK6bUomBzEYkTKzD0iRs7Ljtjb45rKK/VSkyHqeJnG+77qx557IeSO0qVFE3SfzapNcoytTofnZum0BQ6r3Q==} + '@algolia/client-personalization@5.46.2': + resolution: {integrity: sha512-1waE2Uqh/PHNeDXGn/PM/WrmYOBiUGSVxAWqiJIj73jqPqvfzZgzdakHscIVaDl6Cp+j5dwjsZ5LCgaUr6DtmA==} engines: {node: '>= 14.0.0'} - '@algolia/client-query-suggestions@5.40.1': - resolution: {integrity: sha512-Nju4NtxAvXjrV2hHZNLKVJLXjOlW6jAXHef/CwNzk1b2qIrCWDO589ELi5ZHH1uiWYoYyBXDQTtHmhaOVVoyXg==} + '@algolia/client-query-suggestions@5.46.2': + resolution: {integrity: sha512-EgOzTZkyDcNL6DV0V/24+oBJ+hKo0wNgyrOX/mePBM9bc9huHxIY2352sXmoZ648JXXY2x//V1kropF/Spx83w==} engines: {node: '>= 14.0.0'} - '@algolia/client-search@5.40.1': - resolution: {integrity: sha512-Mw6pAUF121MfngQtcUb5quZVqMC68pSYYjCRZkSITC085S3zdk+h/g7i6FxnVdbSU6OztxikSDMh1r7Z+4iPlA==} + '@algolia/client-search@5.46.2': + resolution: {integrity: sha512-ZsOJqu4HOG5BlvIFnMU0YKjQ9ZI6r3C31dg2jk5kMWPSdhJpYL9xa5hEe7aieE+707dXeMI4ej3diy6mXdZpgA==} engines: {node: '>= 14.0.0'} - '@algolia/ingestion@1.40.1': - resolution: {integrity: sha512-z+BPlhs45VURKJIxsR99NNBWpUEEqIgwt10v/fATlNxc4UlXvALdOsWzaFfe89/lbP5Bu4+mbO59nqBC87ZM/g==} + '@algolia/ingestion@1.46.2': + resolution: {integrity: sha512-1Uw2OslTWiOFDtt83y0bGiErJYy5MizadV0nHnOoHFWMoDqWW0kQoMFI65pXqRSkVvit5zjXSLik2xMiyQJDWQ==} engines: {node: '>= 14.0.0'} - '@algolia/monitoring@1.40.1': - resolution: {integrity: sha512-VJMUMbO0wD8Rd2VVV/nlFtLJsOAQvjnVNGkMkspFiFhpBA7s/xJOb+fJvvqwKFUjbKTUA7DjiSi1ljSMYBasXg==} + '@algolia/monitoring@1.46.2': + resolution: {integrity: sha512-xk9f+DPtNcddWN6E7n1hyNNsATBCHIqAvVGG2EAGHJc4AFYL18uM/kMTiOKXE/LKDPyy1JhIerrh9oYb7RBrgw==} engines: {node: '>= 14.0.0'} - '@algolia/recommend@5.40.1': - resolution: {integrity: sha512-ehvJLadKVwTp9Scg9NfzVSlBKH34KoWOQNTaN8i1Ac64AnO6iH2apJVSP6GOxssaghZ/s8mFQsDH3QIZoluFHA==} + '@algolia/recommend@5.46.2': + resolution: {integrity: sha512-NApbTPj9LxGzNw4dYnZmj2BoXiAc8NmbbH6qBNzQgXklGklt/xldTvu+FACN6ltFsTzoNU6j2mWNlHQTKGC5+Q==} engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@5.40.1': - resolution: {integrity: sha512-PbidVsPurUSQIr6X9/7s34mgOMdJnn0i6p+N6Ab+lsNhY5eiu+S33kZEpZwkITYBCIbhzDLOvb7xZD3gDi+USA==} + '@algolia/requester-browser-xhr@5.46.2': + resolution: {integrity: sha512-ekotpCwpSp033DIIrsTpYlGUCF6momkgupRV/FA3m62SreTSZUKjgK6VTNyG7TtYfq9YFm/pnh65bATP/ZWJEg==} engines: {node: '>= 14.0.0'} - '@algolia/requester-fetch@5.40.1': - resolution: {integrity: sha512-ThZ5j6uOZCF11fMw9IBkhigjOYdXGXQpj6h4k+T9UkZrF2RlKcPynFzDeRgaLdpYk8Yn3/MnFbwUmib7yxj5Lw==} + '@algolia/requester-fetch@5.46.2': + resolution: {integrity: sha512-gKE+ZFi/6y7saTr34wS0SqYFDcjHW4Wminv8PDZEi0/mE99+hSrbKgJWxo2ztb5eqGirQTgIh1AMVacGGWM1iw==} engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@5.40.1': - resolution: {integrity: sha512-H1gYPojO6krWHnUXu/T44DrEun/Wl95PJzMXRcM/szstNQczSbwq6wIFJPI9nyE95tarZfUNU3rgorT+wZ6iCQ==} + '@algolia/requester-node-http@5.46.2': + resolution: {integrity: sha512-ciPihkletp7ttweJ8Zt+GukSVLp2ANJHU+9ttiSxsJZThXc4Y2yJ8HGVWesW5jN1zrsZsezN71KrMx/iZsOYpg==} engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@angular-builders/common@5.0.0-beta.1': - resolution: {integrity: sha512-XtnCMsCxedvJt507iOwI9WxWRemJrYhzG5Zh3tYZtxXUYPk2zZpNxVfW41yVicRZ89aWULH3p+mAYeZC4KHX6Q==} + '@angular-builders/common@5.0.3': + resolution: {integrity: sha512-Dro3574mu4/xqmjdA3159+TXDhgTbIJpEY/iBETSKUvHJiCgHel+R3eT105RpHN5o7NaD2rau5Zk2wuZqOk35Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular-builders/jest@21.0.0-beta.1': - resolution: {integrity: sha512-ozlRWhc3XRueX9+5RN3CbeAxEc/ou3XVOLAmP79Z6xtFfXA7k4xfwoCQMcjVYFkR6WWNvAf/SBnVWF9UuAsvZQ==} + '@angular-builders/jest@21.0.3': + resolution: {integrity: sha512-RYIsJQJkke4Dns+lYBYzn0JcmABCQKvTWkqMibi5v8dgtNS8pgPS8pE5x8DSmgraqJikL3ukqaUQmdeL6r38aw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@angular-devkit/build-angular': ^21.0.0 @@ -567,12 +591,13 @@ packages: '@angular/platform-browser-dynamic': ^21.0.0 jest: ^30.0.0 - '@angular-devkit/architect@0.2100.4': - resolution: {integrity: sha512-tKtb0I8AU59m75JjHlL1XEsoPxVaEWhnHKeesDpk49RNm0sVqWnfXesse8IXqdVds0Hpjisc3In7j4xKbigfXg==} + '@angular-devkit/architect@0.2101.4': + resolution: {integrity: sha512-3yyebORk+ovtO+LfDjIGbGCZhCMDAsyn9vkCljARj3sSshS4blOQBar0g+V3kYAweLT5Gf+rTKbN5jneOkBAFQ==} 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-devkit/build-angular@21.0.4': - resolution: {integrity: sha512-w81o1AYUloBLTyaBjGP5V2N4l6/zLpifc6kdu9QATNEhzZOoFdUG+vUiX4GOKBIXNV1OltnwvOfWsE9auJcNQA==} + '@angular-devkit/build-angular@21.1.4': + resolution: {integrity: sha512-2HPCo6vEu5EIwxxFYhnmdfbktRBoOVQD3q7lG9PMQPf/jRCnyIZ70qSbXbAV96IMDLFl8mLRfY4scoaFMIYGMw==} 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: '@angular/compiler-cli': ^21.0.0 @@ -581,7 +606,7 @@ packages: '@angular/platform-browser': ^21.0.0 '@angular/platform-server': ^21.0.0 '@angular/service-worker': ^21.0.0 - '@angular/ssr': ^21.0.4 + '@angular/ssr': ^21.1.4 '@web/test-runner': ^0.20.0 browser-sync: ^3.0.2 jest: ^30.2.0 @@ -621,8 +646,8 @@ packages: tailwindcss: optional: true - '@angular-devkit/build-webpack@0.2100.4': - resolution: {integrity: sha512-tiWmC6AinrfDLarhGHrPuqQN6hLkGzrXBhhiC0ntzwK8sBlu9d44guxXAzR3Wl9sBnHuOPeoNZ0t6x/H6FzBUA==} + '@angular-devkit/build-webpack@0.2101.4': + resolution: {integrity: sha512-lPjPxeEzUha4bnlGzD3KFFf3yxcQjOfV9wwZIa4XLsqjCZsUk95TzHQH7i64OCTw9uKTEQkJBAuO6v2WXHxopw==} 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: webpack: ^5.30.0 @@ -646,20 +671,20 @@ packages: chokidar: optional: true - '@angular-devkit/core@20.3.4': - resolution: {integrity: sha512-r83jn9yVdPh618oGgoKPggMsQGOkQqJbxEutd4CE9mnotPCE2uRTIyaFMh8sohNUeoQNRmj9rbr2pWGVlgERpg==} + '@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.0.4': - resolution: {integrity: sha512-Mbze8tMtBs7keSOx4UIR9utLQs1uSiGjfTaOkCu/dbBEiG6umopy1OlUCvHiHyeiYqh+wR0yiGtTS+Cexo5iLg==} + '@angular-devkit/core@21.1.4': + resolution: {integrity: sha512-ObPTI5gYCB1jGxTRhcqZ6oQVUBFVJ8GH4LksVuAiz0nFX7xxpzARWvlhq943EtnlovVlUd9I8fM3RQqjfGVVAQ==} 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 @@ -677,66 +702,66 @@ packages: resolution: {integrity: sha512-J4Jarr0SohdrHcb40gTL4wGPCQ952IMWF1G/MSAQfBAPvA9ZKApYhpxcY7PmehVePve+ujpus1dGsJ7dPxz8Kg==} 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@20.3.4': - resolution: {integrity: sha512-JYlcmVBKNT9+cQ6T2tmu+yVQ2bJk8tG0mXvPHWXrl/M4c6NObhSSThK50tJHy0Xo3gl8WgogOxUeJNnBq67cIQ==} + '@angular-devkit/schematics@21.1.0': + 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.0.4': - resolution: {integrity: sha512-am39kuaBB/v7RL++bsepvUhP2JKDmfMLQbyJvyHIG6UxnQztxQYZ2/CiPb91dz9NMiqAZqIJaN+kqvIc8h7AeQ==} + '@angular-devkit/schematics@21.1.4': + resolution: {integrity: sha512-Nqq0ioCUxrbEX+L4KOarETcZZJNnJ1mAJ0ubO4VM91qnn8RBBM9SnQ91590TfC34Szk/wh+3+Uj6KUvTJNuegQ==} 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-eslint/builder@21.1.0': - resolution: {integrity: sha512-pcUlDkGqeZ+oQC0oEjnkDDlB96gbgHQhnBUKdhYAiAOSuiBod4+npP0xQOq5chYtRNPBprhDqgrJrp5DBeDMOA==} + '@angular-eslint/builder@21.2.0': + resolution: {integrity: sha512-wcp3J9cbrDwSeI/o1D/DSvMQa8zpKjc5WhRGTx33omhWijCfiVNEAiBLWiEx5Sb/dWcoX8yFNWY5jSgFVy9Sjw==} peerDependencies: '@angular/cli': '>= 21.0.0 < 22.0.0' eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/bundled-angular-compiler@21.1.0': - resolution: {integrity: sha512-t52J6FszgEHaJ+IjuzU9qaWfVxsjlVNkAP+B5z2t4NDgbbDDsmI+QJh0OtP1qdlqzjh2pbocEml30KhYmNZm/Q==} + '@angular-eslint/bundled-angular-compiler@21.2.0': + resolution: {integrity: sha512-J0DWL+j6t9ItFIyIADvzHGqwDA1qfVJ9bx+oTmJ/Hlo7cUpIRoXpcTXpug0CEEABFH0RfDu6PDG2b0FoZ1+7bg==} - '@angular-eslint/eslint-plugin-template@21.1.0': - resolution: {integrity: sha512-FlbRfOCn8IUHvP1ebcCSQFVNh+4X/HqZqL7SW5oj9WIYPiOX9ijS03ndNbfX/pBPSIi8GHLKMjLt8zIy1l5Lww==} + '@angular-eslint/eslint-plugin-template@21.2.0': + resolution: {integrity: sha512-lJ13Dj0DjR6YiceQR0sRbyWzSzOQ6uZPwK9CJUF3wuZjYAUvL1D61zaU9QrVLtf89NVOxv+dYZHDdu3IDeIqbA==} peerDependencies: - '@angular-eslint/template-parser': 21.1.0 + '@angular-eslint/template-parser': 21.2.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 typescript: '*' - '@angular-eslint/eslint-plugin@21.1.0': - resolution: {integrity: sha512-oNp+4UzN2M3KwGwEw03NUdXz93vqJd9sMzTbGXWF9+KVfA2LjckGDTrI6g6asGcJMdyTo07rDcnw0m0MkLB5VA==} + '@angular-eslint/eslint-plugin@21.2.0': + resolution: {integrity: sha512-X2Qn2viDsjm91CEMxNrxDH3qkKpp6un0C1F1BW2p/m9J4AUVfOcXwWz9UpHFSHTRQ+YlTJbiH1ZwwAPeKhFaxA==} peerDependencies: '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/schematics@21.1.0': - resolution: {integrity: sha512-Hal1mYwx4MTjCcNHqfIlua31xrk2tZJoyTiXiGQ21cAeK4sFuY+9V7/8cxbwJMGftX0G4J7uhx8woOdIFuqiZw==} + '@angular-eslint/schematics@21.2.0': + resolution: {integrity: sha512-WtT4fPKIUQ/hswy+l2GF/rKOdD+42L3fUzzcwRzNutQbe2tU9SimoSOAsay/ylWEuhIOQTs7ysPB8fUgFQoLpA==} peerDependencies: '@angular/cli': '>= 21.0.0 < 22.0.0' - '@angular-eslint/template-parser@21.1.0': - resolution: {integrity: sha512-PYVgNbjNtuD5/QOuS6cHR8A7bRqsVqxtUUXGqdv76FYMAajQcAvyfR0QxOkqf3NmYxgNgO3hlUHWq0ILjVbcow==} + '@angular-eslint/template-parser@21.2.0': + resolution: {integrity: sha512-TCb3qYOC/uXKZCo56cJ6N9sHeWdFhyVqrbbYfFjTi09081T6jllgHDZL5Ms7gOMNY8KywWGGbhxwvzeA0RwTgA==} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular-eslint/utils@21.1.0': - resolution: {integrity: sha512-rWINgxGREu+NFUPCpAVsBGG8B4hfXxyswM0N5GbjykvsfB5W6PUix2Gsoh++iEsZPT+c9lvgXL5GbpwfanjOow==} + '@angular-eslint/utils@21.2.0': + resolution: {integrity: sha512-E19/hkuvHoNFvctBkmEiGWpy2bbC6cgbr3GNVrn2nGtbI4jnwnDFCGHv50I4LBfvj0PA9E6TWe73ejJ5qoMJWQ==} peerDependencies: '@typescript-eslint/utils': ^7.11.0 || ^8.0.0 eslint: ^8.57.0 || ^9.0.0 typescript: '*' - '@angular/animations@21.0.6': - resolution: {integrity: sha512-dSxhkh/ZlljdglZ0rriSy7GdC1Y3rGaagkx6oAzF5XqAoBbFmiVFEBZPxssSeQ+O0izmAw3GwsUnz3E/1JYsbA==} + '@angular/animations@21.1.4': + resolution: {integrity: sha512-8xQ0Ylw7qqVyw4ZJ/Tyw/z5Mtqtp8AMj+R+Z1sCWcyxBgDU4+qfxteVYdiipWC3tX77A0FTsXqwvNP9WVY2/WA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 21.0.6 + '@angular/core': 21.1.4 - '@angular/build@21.0.4': - resolution: {integrity: sha512-tnh9llk9288noG6buV9HtsAfR/QCVIArTsx9pFJebAFOIDyObpHItfWTnmqYBQecSNEwH5l4XlkDInbjxM9MuA==} + '@angular/build@21.1.4': + resolution: {integrity: sha512-7CAAQPWFMMqod40ox5MOVB/CnoBXFDehyQhs0hls6lu7bOy/M0EDy0v6bERkyNGRz1mihWWBiCV8XzEinrlq1A==} 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: '@angular/compiler': ^21.0.0 @@ -746,7 +771,7 @@ packages: '@angular/platform-browser': ^21.0.0 '@angular/platform-server': ^21.0.0 '@angular/service-worker': ^21.0.0 - '@angular/ssr': ^21.0.4 + '@angular/ssr': ^21.1.4 karma: ^6.4.0 less: ^4.2.0 ng-packagr: ^21.0.0 @@ -781,45 +806,46 @@ packages: vitest: optional: true - '@angular/cdk@21.0.5': - resolution: {integrity: sha512-yO/IRYEZ5wJkpwg3GT3b6RST4pqNFTAhuyPdEdLcE81cs283K3aKOsCYh2xUR3bR4WxBh2kBPSJ31AFZyJXbSA==} + '@angular/cdk@21.1.4': + resolution: {integrity: sha512-PElA4Ww4TIa3+B/ND+fm8ZPDKONTIqc9a/s0qNxhcAD9IpDqjaBVi/fyg+ZWBtS+x0DQgJtKeCsSZ6sr2aFQaQ==} peerDependencies: '@angular/common': ^21.0.0 || ^22.0.0 '@angular/core': ^21.0.0 || ^22.0.0 + '@angular/platform-browser': ^21.0.0 || ^22.0.0 rxjs: ^6.5.3 || ^7.4.0 - '@angular/cli@21.0.4': - resolution: {integrity: sha512-L4uKhC3KorF04x9A7noff2m25Phkq54wdqzuWNnbGg3bNfOHdXMv97t2e02J1mk+XOeEcPfDJmOiXj4fcviCLA==} + '@angular/cli@21.1.4': + resolution: {integrity: sha512-XsMHgxTvHGiXXrhYZz3zMZYhYU0gHdpoHKGiEKXwcx+S1KoYbIssyg6oF2Kq49ZaE0OYCTKjnvgDce6ZqdkJ/A==} 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.0.6': - resolution: {integrity: sha512-Yd8PF0dR37FAzqEcBHAyVCiSGMJOezSJe6rV/4BC6AVLfaZ7oZLl8CNVxKsod2UHd6rKxt1hzx05QdVcVvYNeA==} + '@angular/common@21.1.4': + resolution: {integrity: sha512-1uOxPrHO9PFZBU/JavzYzjxAm+5x7vD2z6AeUndqdT4LjqOBIePswxFDRqM9dlfF8FIwnnfmNFipiC/yQjJSnA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/core': 21.0.6 + '@angular/core': 21.1.4 rxjs: ^6.5.3 || ^7.4.0 - '@angular/compiler-cli@21.0.6': - resolution: {integrity: sha512-UcIUx+fbn0VLlCBCIYxntAzWG3zPRUo0K7wvuK0MC6ZFCWawgewx9SdLLZTqcaWe1g5FRQlQeVQcFgHAO5R2Mw==} + '@angular/compiler-cli@21.1.4': + resolution: {integrity: sha512-Uw8KmpVCo58/f5wf6pY8ZS5fodv65hn5jxms8Nv/K7/LVe3i1nNFrHyneBx5+a7qkz93nSV4rdwBVnMvjIyr+g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true peerDependencies: - '@angular/compiler': 21.0.6 + '@angular/compiler': 21.1.4 typescript: '>=5.9 <6.0' peerDependenciesMeta: typescript: optional: true - '@angular/compiler@21.0.6': - resolution: {integrity: sha512-rBMzG7WnQMouFfDST+daNSAOVYdtw560645PhlxyVeIeHMlCm0j1jjBgVPGTBNpVgKRdT/sqbi6W6JYkY9mERA==} + '@angular/compiler@21.1.4': + resolution: {integrity: sha512-H0qtASeqOTaS44ioF4DYE/yNqwzUmEJpMYrcNEUFEwA20/DkLzyoaEx4y1CjIxtXxuhtge95PNymDBOLWSjIdg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/core@21.0.6': - resolution: {integrity: sha512-SvWbOkkrsqprYJSBmzQEWkWjfZB/jkRYyFp2ClMJBPqOLxP1a+i3Om2rolcNQjZPz87bs9FszwgRlXUy7sw5cQ==} + '@angular/core@21.1.4': + resolution: {integrity: sha512-QBDO5SaVYTVQ0fIN9Qd7US9cUCgs2vM9x6K18PTUKmygIkHVHTFdzwm4MO5gpCOFzJseGbS+dNzqn+v0PaKf9g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/compiler': 21.0.6 + '@angular/compiler': 21.1.4 rxjs: ^6.5.3 || ^7.4.0 zone.js: ~0.15.0 || ~0.16.0 peerDependenciesMeta: @@ -828,56 +854,56 @@ packages: zone.js: optional: true - '@angular/forms@21.0.6': - resolution: {integrity: sha512-aAkAAKuUrP8U7R4aH/HbmG/CXP90GlML77ECBI5b4qCSb+bvaTEYsaf85mCyTpr9jvGkia2LTe42hPcOuyzdsQ==} + '@angular/forms@21.1.4': + resolution: {integrity: sha512-duVT/eOncmFNBYRlK/F7WDg6GD1vL1mxUrDdnp7M9J8JvNrKH0PvdfzuOAmjbB8/bsvUNTLFXCV4+43Mc2Hqsw==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.6 - '@angular/core': 21.0.6 - '@angular/platform-browser': 21.0.6 + '@angular/common': 21.1.4 + '@angular/core': 21.1.4 + '@angular/platform-browser': 21.1.4 rxjs: ^6.5.3 || ^7.4.0 - '@angular/language-service@21.0.6': - resolution: {integrity: sha512-hxquRSxUW2FBeXHChCq5lOwo5jjJoOs7OdZmtBJtcNE93+6HUANRXLhcqG8kKkvwvGg7QUQf7+R9h3TCJYiz1A==} + '@angular/language-service@21.1.4': + resolution: {integrity: sha512-E0OKcbYMJPaWlDsz4clPoFJRCgpWBSmMZtgzd4Py3C6yxTyPCZYgA43UyzUDiQI7rHHjD5V6d5EvocgSq6uBfQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@angular/material@21.0.5': - resolution: {integrity: sha512-LcDotwwTl2GFtFK1BqcKjj0eRAkOjTSj1C6ux0rOlpU2hJaECxue/ZqEyB/KMHngFAqMJpecxF4zGy/ISCp2xA==} + '@angular/material@21.1.4': + resolution: {integrity: sha512-Vte+4os5JexhtaPk7Duk5bIC+zU5SYF7ZHUnpmAJxW0u4D4C9lpTVepm2bfMVV2AxHjHVAnW+/anINIS401UsA==} peerDependencies: - '@angular/cdk': 21.0.5 + '@angular/cdk': 21.1.4 '@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.0.6': - resolution: {integrity: sha512-7mvlvEx66C1cwbAbaeTnbfw1EeZwK5eRCT55pGW+Fsx+vg/8TVF/6NPEbYO65earwIp9Xqt9mGGtq+fPopsbSA==} + '@angular/platform-browser-dynamic@21.1.4': + resolution: {integrity: sha512-lThgNDFHPQyrx0liNX3x8wHcgp1sd/Dym19zm1PSQ67k6J4snwxZFhNlwFHVr1K86XvX/vilyeR2edPLe9lF3Q==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.6 - '@angular/compiler': 21.0.6 - '@angular/core': 21.0.6 - '@angular/platform-browser': 21.0.6 + '@angular/common': 21.1.4 + '@angular/compiler': 21.1.4 + '@angular/core': 21.1.4 + '@angular/platform-browser': 21.1.4 - '@angular/platform-browser@21.0.6': - resolution: {integrity: sha512-tPk8rlUEBPXIUPRYq6Xu7QhJgKtnVr0dOHHuhyi70biKTupr5VikpZC5X9dy2Q3H3zYbK6MHC6384YMuwfU2kg==} + '@angular/platform-browser@21.1.4': + resolution: {integrity: sha512-S6Iw5CkORih5omh+MQY35w0bUBxdSFAPLDg386S6/9fEUjDClo61hvXNKxaNh9g7tnh1LD7zmTmKrqufnhnFDQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/animations': 21.0.6 - '@angular/common': 21.0.6 - '@angular/core': 21.0.6 + '@angular/animations': 21.1.4 + '@angular/common': 21.1.4 + '@angular/core': 21.1.4 peerDependenciesMeta: '@angular/animations': optional: true - '@angular/router@21.0.6': - resolution: {integrity: sha512-HOfomKq7jRSgxt/uUvpdbB8RNaYuGB/FJQ3BfQCFfGw1O9L3B72b7Hilk6AcjCruul6cfv/kmT4EB6Vqi3dQtA==} + '@angular/router@21.1.4': + resolution: {integrity: sha512-nPYuRJ8ub/X8GK55U2KqYy/ducVRn6HSoDmZz0yiXtI6haFsZlv9R1j5zi0EDIqrrN0HGARMs6jNDXZC1Ded3w==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: - '@angular/common': 21.0.6 - '@angular/core': 21.0.6 - '@angular/platform-browser': 21.0.6 + '@angular/common': 21.1.4 + '@angular/core': 21.1.4 + '@angular/platform-browser': 21.1.4 rxjs: ^6.5.3 || ^7.4.0 '@arr/every@1.0.1': @@ -887,40 +913,44 @@ packages: '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} engines: {node: '>=6.9.0'} '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.5': - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -931,8 +961,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.5': - resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -944,12 +974,12 @@ packages: resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -958,8 +988,8 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-remap-async-to-generator@7.27.1': @@ -968,8 +998,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -994,16 +1024,16 @@ packages: resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.28.3': - resolution: {integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==} + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} engines: {node: '>=6.0.0'} hasBin: true @@ -1031,8 +1061,8 @@ packages: peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': - resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1064,14 +1094,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.27.1': - resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1158,44 +1188,56 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-to-generator@7.27.1': resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-block-scoped-functions@7.27.1': resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.28.5': - resolution: {integrity: sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==} + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.27.1': - resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.28.3': - resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.28.4': - resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.27.1': - resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1206,8 +1248,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.27.1': - resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1218,8 +1260,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1230,14 +1272,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-explicit-resource-management@7.28.0': - resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.28.5': - resolution: {integrity: sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==} + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1260,8 +1302,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-json-strings@7.27.1': - resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1272,8 +1314,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-logical-assignment-operators@7.28.5': - resolution: {integrity: sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==} + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1290,14 +1332,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.27.1': - resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.28.5': - resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==} + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1308,8 +1350,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': - resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1320,20 +1362,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': - resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.27.1': - resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.28.4': - resolution: {integrity: sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==} + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1344,14 +1386,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.27.1': - resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.28.5': - resolution: {integrity: sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==} + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1362,14 +1404,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.27.1': - resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.27.1': - resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1380,14 +1422,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.28.4': - resolution: {integrity: sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==} + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regexp-modifiers@7.27.1': - resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1398,8 +1440,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-runtime@7.28.3': - resolution: {integrity: sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==} + '@babel/plugin-transform-runtime@7.28.5': + resolution: {integrity: sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1410,8 +1452,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.27.1': - resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1440,8 +1482,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-property-regex@7.27.1': - resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1452,14 +1494,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-sets-regex@7.27.1': - resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.28.3': - resolution: {integrity: sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==} + '@babel/preset-env@7.28.5': + resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-env@7.28.6': + resolution: {integrity: sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1473,33 +1521,41 @@ packages: resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@borewit/text-codec@0.1.1': - resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} - '@cacheable/utils@2.3.2': - resolution: {integrity: sha512-8kGE2P+HjfY8FglaOiW+y8qxcaQAfAhVML+i66XJR3YX5FtyDqn6Txctr3K2FrbxLKixRRYYBWMbuGciOhYNDg==} + '@cacheable/utils@2.3.4': + resolution: {integrity: sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@compodoc/compodoc@1.1.32': - resolution: {integrity: sha512-kaYk5+o4k7GB585iphwV5NE49BKKk8d+gJLNBE8eu2fIRdhnHOWblasRbOBRULfwJ+qxfmgrIqi32K34wCag6A==} + '@compodoc/compodoc@1.2.1': + resolution: {integrity: sha512-won7I0OeFM0zSi+cwVilwtLA7VpY9NzsBch6+gGElYQdbM4/01XgA//leF8EsNzhxuQb+kP86MqJ3loZhz+Big==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} hasBin: true @@ -1559,474 +1615,168 @@ packages: '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.12': - resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.26.0': - resolution: {integrity: sha512-hj0sKNCQOOo2fgyII3clmJXP28VhgDfU5iy3GNHlWO76KG6N7x4D9ezH5lJtQTG+1J6MFDAJXC1qsI+W+LvZoA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.12': - resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.26.0': - resolution: {integrity: sha512-DDnoJ5eoa13L8zPh87PUlRd/IyFaIKOlRbxiwcSbeumcJ7UZKdtuMCHa1Q27LWQggug6W4m28i4/O2qiQQ5NZQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.27.2': resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.12': - resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.26.0': - resolution: {integrity: sha512-C0hkDsYNHZkBtPxxDx177JN90/1MiCpvBNjz1f5yWJo1+5+c5zr8apjastpEG+wtPjo9FFtGG7owSsAxyKiHxA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.27.2': resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.12': - resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.26.0': - resolution: {integrity: sha512-bKDkGXGZnj0T70cRpgmv549x38Vr2O3UWLbjT2qmIkdIWcmlg8yebcFWoT9Dku7b5OV3UqPEuNKRzlNhjwUJ9A==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.27.2': resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.12': - resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.26.0': - resolution: {integrity: sha512-6Z3naJgOuAIB0RLlJkYc81An3rTlQ/IeRdrU3dOea8h/PvZSgitZV+thNuIccw0MuK1GmIAnAmd5TrMZad8FTQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.27.2': resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.12': - resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.26.0': - resolution: {integrity: sha512-OPnYj0zpYW0tHusMefyaMvNYQX5pNQuSsHFTHUBNp3vVXupwqpxofcjVsUx11CQhGVkGeXjC3WLjh91hgBG2xw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.27.2': resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.12': - resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.26.0': - resolution: {integrity: sha512-jix2fa6GQeZhO1sCKNaNMjfj5hbOvoL2F5t+w6gEPxALumkpOV/wq7oUBMHBn2hY2dOm+mEV/K+xfZy3mrsxNQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.27.2': resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.12': - resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.26.0': - resolution: {integrity: sha512-tccJaH5xHJD/239LjbVvJwf6T4kSzbk6wPFerF0uwWlkw/u7HL+wnAzAH5GB2irGhYemDgiNTp8wJzhAHQ64oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.27.2': resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.12': - resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.26.0': - resolution: {integrity: sha512-IMJYN7FSkLttYyTbsbme0Ra14cBO5z47kpamo16IwggzzATFY2lcZAwkbcNkWiAduKrTgFJP7fW5cBI7FzcuNQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.27.2': resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.12': - resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.26.0': - resolution: {integrity: sha512-JY8NyU31SyRmRpuc5W8PQarAx4TvuYbyxbPIpHAZdr/0g4iBr8KwQBS4kiiamGl2f42BBecHusYCsyxi7Kn8UQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.27.2': resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.12': - resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.26.0': - resolution: {integrity: sha512-XITaGqGVLgk8WOHw8We9Z1L0lbLFip8LyQzKYFKO4zFo1PFaaSKsbNjvkb7O8kEXytmSGRkYpE8LLVpPJpsSlw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.27.2': resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.12': - resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.26.0': - resolution: {integrity: sha512-MkggfbDIczStUJwq9wU7gQ7kO33d8j9lWuOCDifN9t47+PeI+9m2QVh51EI/zZQ1spZtFMC1nzBJ+qNGCjJnsg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.27.2': resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.12': - resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.26.0': - resolution: {integrity: sha512-fUYup12HZWAeccNLhQ5HwNBPr4zXCPgUWzEq2Rfw7UwqwfQrFZ0SR/JljaURR8xIh9t+o1lNUFTECUTmaP7yKA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.27.2': resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.12': - resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.26.0': - resolution: {integrity: sha512-MzRKhM0Ip+//VYwC8tialCiwUQ4G65WfALtJEFyU0GKJzfTYoPBw5XNWf0SLbCUYQbxTKamlVwPmcw4DgZzFxg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.27.2': resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.12': - resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.26.0': - resolution: {integrity: sha512-QhCc32CwI1I4Jrg1enCv292sm3YJprW8WHHlyxJhae/dVs+KRWkbvz2Nynl5HmZDW/m9ZxrXayHzjzVNvQMGQA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.27.2': resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.12': - resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.26.0': - resolution: {integrity: sha512-1D6vi6lfI18aNT1aTf2HV+RIlm6fxtlAp8eOJ4mmnbYmZ4boz8zYDar86sIYNh0wmiLJEbW/EocaKAX6Yso2fw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.27.2': resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.12': - resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.26.0': - resolution: {integrity: sha512-rnDcepj7LjrKFvZkx+WrBv6wECeYACcFjdNPvVPojCPJD8nHpb3pv3AuR9CXgdnjH1O23btICj0rsp0L9wAnHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.27.2': resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.12': - resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-arm64@0.26.0': - resolution: {integrity: sha512-FSWmgGp0mDNjEXXFcsf12BmVrb+sZBBBlyh3LwB/B9ac3Kkc8x5D2WimYW9N7SUkolui8JzVnVlWh7ZmjCpnxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - '@esbuild/netbsd-arm64@0.27.2': resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.12': - resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.26.0': - resolution: {integrity: sha512-0QfciUDFryD39QoSPUDshj4uNEjQhp73+3pbSAaxjV2qGOEDsM67P7KbJq7LzHoVl46oqhIhJ1S+skKGR7lMXA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.27.2': resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.12': - resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.26.0': - resolution: {integrity: sha512-vmAK+nHhIZWImwJ3RNw9hX3fU4UGN/OqbSE0imqljNbUQC3GvVJ1jpwYoTfD6mmXmQaxdJY6Hn4jQbLGJKg5Yw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-arm64@0.27.2': resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.12': - resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.26.0': - resolution: {integrity: sha512-GPXF7RMkJ7o9bTyUsnyNtrFMqgM3X+uM/LWw4CeHIjqc32fm0Ir6jKDnWHpj8xHFstgWDUYseSABK9KCkHGnpg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.27.2': resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.12': - resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/openharmony-arm64@0.26.0': - resolution: {integrity: sha512-nUHZ5jEYqbBthbiBksbmHTlbb5eElyVfs/s1iHQ8rLBq1eWsd5maOnDpCocw1OM8kFK747d1Xms8dXJHtduxSw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - '@esbuild/openharmony-arm64@0.27.2': resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.12': - resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.26.0': - resolution: {integrity: sha512-TMg3KCTCYYaVO+R6P5mSORhcNDDlemUVnUbb8QkboUtOhb5JWKAzd5uMIMECJQOxHZ/R+N8HHtDF5ylzLfMiLw==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.27.2': resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.12': - resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.26.0': - resolution: {integrity: sha512-apqYgoAUd6ZCb9Phcs8zN32q6l0ZQzQBdVXOofa6WvHDlSOhwCWgSfVQabGViThS40Y1NA4SCvQickgZMFZRlA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.27.2': resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.12': - resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.26.0': - resolution: {integrity: sha512-FGJAcImbJNZzLWu7U6WB0iKHl4RuY4TsXEwxJPl9UZLS47agIZuILZEX3Pagfw7I4J3ddflomt9f0apfaJSbaw==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.27.2': resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.12': - resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.26.0': - resolution: {integrity: sha512-WAckBKaVnmFqbEhbymrPK7M086DQMpL1XoRbpmN0iW8k5JSXjDRQBhcZNa0VweItknLq9eAeCL34jK7/CDcw7A==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.27.2': resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} @@ -2039,15 +1789,21 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.2': resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/compat@2.0.0': - resolution: {integrity: sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==} + '@eslint/compat@2.0.2': + resolution: {integrity: sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: - eslint: ^8.40 || 9 + eslint: ^8.40 || 9 || 10 peerDependenciesMeta: eslint: optional: true @@ -2064,8 +1820,8 @@ packages: resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@1.0.0': - resolution: {integrity: sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==} + '@eslint/core@1.1.0': + resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@eslint/eslintrc@3.3.3': @@ -2089,24 +1845,30 @@ packages: peerDependencies: '@angular/core': ^21.0.0 - '@fortawesome/fontawesome-common-types@7.1.0': - resolution: {integrity: sha512-l/BQM7fYntsCI//du+6sEnHOP6a74UixFyOYUyz2DLMXKx+6DEhfR3F2NYGE45XH1JJuIamacb4IZs9S0ZOWLA==} + '@fortawesome/fontawesome-common-types@7.2.0': + resolution: {integrity: sha512-IpR0bER9FY25p+e7BmFH25MZKEwFHTfRAfhOyJubgiDnoJNsSvJ7nigLraHtp4VOG/cy8D7uiV0dLkHOne5Fhw==} engines: {node: '>=6'} - '@fortawesome/fontawesome-svg-core@7.1.0': - resolution: {integrity: sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==} + '@fortawesome/fontawesome-svg-core@7.2.0': + resolution: {integrity: sha512-6639htZMjEkwskf3J+e6/iar+4cTNM9qhoWuRfj9F3eJD6r7iCzV1SWnQr2Mdv0QT0suuqU8BoJCZUyCtP9R4Q==} engines: {node: '>=6'} - '@fortawesome/free-brands-svg-icons@7.1.0': - resolution: {integrity: sha512-9byUd9bgNfthsZAjBl6GxOu1VPHgBuRUP9juI7ZoM98h8xNPTCTagfwUFyYscdZq4Hr7gD1azMfM9s5tIWKZZA==} + '@fortawesome/free-brands-svg-icons@7.2.0': + resolution: {integrity: sha512-VNG8xqOip1JuJcC3zsVsKRQ60oXG9+oYNDCosjoU/H9pgYmLTEwWw8pE0jhPz/JWdHeUuK6+NQ3qsM4gIbdbYQ==} engines: {node: '>=6'} - '@fortawesome/free-solid-svg-icons@7.1.0': - resolution: {integrity: sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==} + '@fortawesome/free-solid-svg-icons@7.2.0': + resolution: {integrity: sha512-YTVITFGN0/24PxzXrwqCgnyd7njDuzp5ZvaCx5nq/jg55kUYd94Nj8UTchBdBofi/L0nwRfjGOg0E41d2u9T1w==} engines: {node: '>=6'} - '@golevelup/ts-jest@1.1.1': - resolution: {integrity: sha512-F5MKhEoNWDHK3YBTKnViBjWijHeDMS/5d4Pug/DCI5UWSqfCg5oGCve2W+/WM0x/hhUM9GedDT3ztHHwbIYjQA==} + '@golevelup/ts-jest@1.2.1': + resolution: {integrity: sha512-1LQ6iDkKJve1AifgVNN0C27hUfd6zZ2qiAbK8x9Mvur+sB1YGc8Z70uEJPtyHL/akA8e4IEKZWC8lkKe/WuTlw==} + + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -2137,15 +1899,6 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.19': - resolution: {integrity: sha512-wQNz9cfcxrtEnUyG5PndC8g3gZ7lGDBzmWiXZkX8ot3vfZ+/BLjR8EvyGX4YzQLeVqtAlY/YScZpW7CW8qMoDQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/confirm@5.1.21': resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} @@ -2240,15 +1993,6 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.9.0': - resolution: {integrity: sha512-X7/+dG9SLpSzRkwgG5/xiIzW0oMrV3C0HOa7YHG1WnrLK+vCQHfte4k/T80059YBdei29RBC3s+pSMvPJDU9/A==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@inquirer/rawlist@4.1.11': resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} engines: {node: '>=18'} @@ -2285,18 +2029,14 @@ packages: '@types/node': optional: true - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@isaacs/cliui@9.0.0': + resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==} + engines: {node: '>=18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -2429,36 +2169,120 @@ packages: peerDependencies: tslib: '2' + '@jsonjoy.com/base64@17.67.0': + resolution: {integrity: sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/buffers@1.2.1': resolution: {integrity: sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/buffers@17.67.0': + resolution: {integrity: sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/codegen@1.0.0': resolution: {integrity: sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/codegen@17.67.0': + resolution: {integrity: sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-core@4.56.10': + resolution: {integrity: sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-fsa@4.56.10': + resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-node-builtins@4.56.10': + resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-node-to-fsa@4.56.10': + resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-node-utils@4.56.10': + resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-node@4.56.10': + resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-print@4.56.10': + resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + + '@jsonjoy.com/fs-snapshot@4.56.10': + resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/json-pack@1.21.0': resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/json-pack@17.67.0': + resolution: {integrity: sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/json-pointer@1.0.2': resolution: {integrity: sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/json-pointer@17.67.0': + resolution: {integrity: sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/util@1.9.0': resolution: {integrity: sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/util@17.67.0': + resolution: {integrity: sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} @@ -2472,38 +2296,38 @@ packages: '@inquirer/prompts': '>= 3 < 8' listr2: 9.0.5 - '@lmdb/lmdb-darwin-arm64@3.4.3': - resolution: {integrity: sha512-zR6Y45VNtW5s+A+4AyhrJk0VJKhXdkLhrySCpCu7PSdnakebsOzNxf58p5Xoq66vOSuueGAxlqDAF49HwdrSTQ==} + '@lmdb/lmdb-darwin-arm64@3.4.4': + resolution: {integrity: sha512-XaKL705gDWd6XVls3ATDj13ZdML/LqSIxwgnYpG8xTzH2ifArx8fMMDdvqGE/Emd+W6R90W2fveZcJ0AyS8Y0w==} cpu: [arm64] os: [darwin] - '@lmdb/lmdb-darwin-x64@3.4.3': - resolution: {integrity: sha512-nfGm5pQksBGfaj9uMbjC0YyQreny/Pl7mIDtHtw6g7WQuCgeLullr9FNRsYyKplaEJBPrCVpEjpAznxTBIrXBw==} + '@lmdb/lmdb-darwin-x64@3.4.4': + resolution: {integrity: sha512-GPHGEVcwJlkD01GmIr7B4kvbIcUDS2+kBadVEd7lU4can1RZaZQLDDBJRrrNfS2Kavvl0VLI/cMv7UASAXGrww==} cpu: [x64] os: [darwin] - '@lmdb/lmdb-linux-arm64@3.4.3': - resolution: {integrity: sha512-uX9eaPqWb740wg5D3TCvU/js23lSRSKT7lJrrQ8IuEG/VLgpPlxO3lHDywU44yFYdGS7pElBn6ioKFKhvALZlw==} + '@lmdb/lmdb-linux-arm64@3.4.4': + resolution: {integrity: sha512-mALqr7DE42HsiwVTKpQWxacjHoJk+e9p00RWIJqTACh/hpucxp/0lK/XMh5XzWnU/TDCZLukq1+vNqnNumTP/Q==} cpu: [arm64] os: [linux] - '@lmdb/lmdb-linux-arm@3.4.3': - resolution: {integrity: sha512-Kjqomp7i0rgSbYSUmv9JnXpS55zYT/YcW3Bdf9oqOTjcH0/8tFAP8MLhu/i9V2pMKIURDZk63Ww49DTK0T3c/Q==} + '@lmdb/lmdb-linux-arm@3.4.4': + resolution: {integrity: sha512-cmev5/dZr5ACKri9f6GU6lZCXTjMhV72xujlbOhFCgFXrt4W0TxGsmY8kA1BITvH60JBKE50cSxsiulybAbrrw==} cpu: [arm] os: [linux] - '@lmdb/lmdb-linux-x64@3.4.3': - resolution: {integrity: sha512-7/8l20D55CfwdMupkc3fNxNJdn4bHsti2X0cp6PwiXlLeSFvAfWs5kCCx+2Cyje4l4GtN//LtKWjTru/9hDJQg==} + '@lmdb/lmdb-linux-x64@3.4.4': + resolution: {integrity: sha512-QjLs8OcmCNcraAcLoZyFlo0atzBJniQLLwhtR+ymQqS5kLYpV5RqwriL87BW+ZiR9ZiGgZx3evrz5vnWPtJ1fQ==} cpu: [x64] os: [linux] - '@lmdb/lmdb-win32-arm64@3.4.3': - resolution: {integrity: sha512-yWVR0e5Gl35EGJBsAuqPOdjtUYuN8CcTLKrqpQFoM+KsMadViVCulhKNhkcjSGJB88Am5bRPjMro4MBB9FS23Q==} + '@lmdb/lmdb-win32-arm64@3.4.4': + resolution: {integrity: sha512-tr/pwHDlZ33forLGAr0tI04cRmP4SgF93yHbb+2zvZiDEyln5yMHhbKDySxY66aUOkhvBvTuHq9q/3YmTj6ZHQ==} cpu: [arm64] os: [win32] - '@lmdb/lmdb-win32-x64@3.4.3': - resolution: {integrity: sha512-1JdBkcO0Vrua4LUgr4jAe4FUyluwCeq/pDkBrlaVjX3/BBWP1TzVjCL+TibWNQtPAL1BITXPAhlK5Ru4FBd/hg==} + '@lmdb/lmdb-win32-x64@3.4.4': + resolution: {integrity: sha512-KRzfocJzB/mgoTCqnMawuLSKheHRVTqWfSmouIgYpFs6Hx4zvZSvsZKSCEb5gHmICy7qsx9l06jk3MFTtiFVAQ==} cpu: [x64] os: [win32] @@ -2514,8 +2338,8 @@ packages: '@material-design-icons/font@0.14.15': resolution: {integrity: sha512-h4YFZnYxNuciEKR0jAOekaQmIg2UGUSxbnoyzo4OdE42gy9QB3UnrjLcASiDy9ra8fqrcHy+NqxTHx7F86BH0A==} - '@modelcontextprotocol/sdk@1.24.0': - resolution: {integrity: sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==} + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} engines: {node: '>=18'} peerDependencies: '@cfworker/json-schema': ^4.1.1 @@ -2663,11 +2487,11 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.1.0': - resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@nestjs/cache-manager@3.0.1': - resolution: {integrity: sha512-4UxTnR0fsmKL5YDalU2eLFVnL+OBebWUpX+hEduKGncrVKH4PPNoiRn1kXyOCjmzb0UvWgqubpssNouc8e0MCw==} + '@nestjs/cache-manager@3.1.0': + resolution: {integrity: sha512-pEIqYZrBcE8UdkJmZRduurvoUfdU+3kRPeO1R2muiMbZnRuqlki5klFFNllO9LyYWzrx98bd1j0PSPKSJk1Wbw==} peerDependencies: '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 @@ -2675,8 +2499,8 @@ packages: keyv: '>=5' rxjs: ^7.8.1 - '@nestjs/cli@11.0.14': - resolution: {integrity: sha512-YwP03zb5VETTwelXU+AIzMVbEZKk/uxJL+z9pw0mdG9ogAtqZ6/mpmIM4nEq/NU8D0a7CBRLcMYUmWW/55pfqw==} + '@nestjs/cli@11.0.16': + resolution: {integrity: sha512-P0H+Vcjki6P5160E5QnMt3Q0X5FTg4PZkP99Ig4lm/4JWqfw32j3EXv3YBTJ2DmxLwOQ/IS9F7dzKpMAgzKTGg==} engines: {node: '>= 20.11'} hasBin: true peerDependencies: @@ -2688,8 +2512,8 @@ packages: '@swc/core': optional: true - '@nestjs/common@11.1.9': - resolution: {integrity: sha512-zDntUTReRbAThIfSp3dQZ9kKqI+LjgLp5YZN5c1bgNRDuoeLySAoZg46Bg1a+uV8TMgIRziHocglKGNzr6l+bQ==} + '@nestjs/common@11.1.13': + resolution: {integrity: sha512-ieqWtipT+VlyDWLz5Rvz0f3E5rXcVAnaAi+D53DEHLjc1kmFxCgZ62qVfTX2vwkywwqNkTNXvBgGR72hYqV//Q==} peerDependencies: class-transformer: '>=0.4.1' class-validator: '>=0.13.2' @@ -2701,14 +2525,14 @@ packages: class-validator: optional: true - '@nestjs/config@4.0.2': - resolution: {integrity: sha512-McMW6EXtpc8+CwTUwFdg6h7dYcBUpH5iUILCclAsa+MbCEvC9ZKu4dCHRlJqALuhjLw97pbQu62l4+wRwGeZqA==} + '@nestjs/config@4.0.3': + resolution: {integrity: sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 rxjs: ^7.1.0 - '@nestjs/core@11.1.9': - resolution: {integrity: sha512-a00B0BM4X+9z+t3UxJqIZlemIwCQdYoPKrMcM+ky4z3pkqqG1eTWexjs+YXpGObnLnjtMPVKWlcZHp3adDYvUw==} + '@nestjs/core@11.1.13': + resolution: {integrity: sha512-Tq9EIKiC30EBL8hLK93tNqaToy0hzbuVGYt29V8NhkVJUsDzlmiVf6c3hSPtzx2krIUVbTgQ2KFeaxr72rEyzQ==} engines: {node: '>= 20'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -2725,21 +2549,21 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@11.1.9': - resolution: {integrity: sha512-GVd3+0lO0mJq2m1kl9hDDnVrX3Nd4oH3oDfklz0pZEVEVS0KVSp63ufHq2Lu9cyPdSBuelJr9iPm2QQ1yX+Kmw==} + '@nestjs/platform-express@11.1.13': + resolution: {integrity: sha512-LYmi43BrAs1n74kLCUfXcHag7s1CmGETcFbf9IVyA/KWXAuAH95G3wEaZZiyabOLFNwq4ifnRGnIwUwW7cz3+w==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 - '@nestjs/platform-socket.io@11.1.9': - resolution: {integrity: sha512-OaAW+voXo5BXbFKd9Ot3SL05tEucRMhZRdw5wdWZf/RpIl9hB6G6OHr8DDxNbUGvuQWzNnZHCDHx3EQJzjcIyA==} + '@nestjs/platform-socket.io@11.1.13': + resolution: {integrity: sha512-04Rh16IopZzHRXt0ZjFASqt9oNFV/0m0NsYe4kVOSaTEoef3cH7cTFpNpHsfNHcc4QpYL963XE8SvIRcZs5L8A==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/websockets': ^11.0.0 rxjs: ^7.1.0 - '@nestjs/schedule@6.1.0': - resolution: {integrity: sha512-W25Ydc933Gzb1/oo7+bWzzDiOissE+h/dhIAPugA39b9MuIzBbLybuXpc1AjoQLczO3v0ldmxaffVl87W0uqoQ==} + '@nestjs/schedule@6.1.1': + resolution: {integrity: sha512-kQl1RRgi02GJ0uaUGCrXHCcwISsCsJDciCKe38ykJZgnAeeoeVWs8luWtBo4AqAAXm4nS5K8RlV0smHUJ4+2FA==} peerDependencies: '@nestjs/common': ^10.0.0 || ^11.0.0 '@nestjs/core': ^10.0.0 || ^11.0.0 @@ -2765,8 +2589,8 @@ packages: fastify: optional: true - '@nestjs/testing@11.1.9': - resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==} + '@nestjs/testing@11.1.13': + resolution: {integrity: sha512-bOWP8nLEZAOEEX8jAZGBCc1yU0+nv4g2ipc+QEzkVUe3eEEUKHKaeGafJ3GtDuGavlZKfkXEqflZuICdavu5dQ==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -2787,8 +2611,8 @@ packages: rxjs: ^7.2.0 typeorm: ^0.3.0 - '@nestjs/websockets@11.1.9': - resolution: {integrity: sha512-kkkdeTVcc3X7ZzvVqUVpOAJoh49kTRUjWNUXo5jmG+27OvZoHfs/vuSiqxidrrbIgydSqN15HUsf1wZwQUrxCQ==} + '@nestjs/websockets@11.1.13': + resolution: {integrity: sha512-8r8EadqBkrTYtH2uog42HfIb5fcP5a3iXymH/ityd9bO/gDson5Q1qbtCQRjuU++6NY12YYteKRu4eP/iErbLw==} peerDependencies: '@nestjs/common': ^11.0.0 '@nestjs/core': ^11.0.0 @@ -2799,8 +2623,8 @@ packages: '@nestjs/platform-socket.io': optional: true - '@ngtools/webpack@21.0.4': - resolution: {integrity: sha512-0+XWJqZaRB5GGtJEaWgHV0iYzgB5pDhVjMEb/1Z6+CZazF8Aq2HuU8BErWYzPIwaiTLxR+lc3Z35RsAaeSmGFQ==} + '@ngtools/webpack@21.1.4': + resolution: {integrity: sha512-CgKnMofIVGTwNPqFNZmkmr2aLOFUG/AKm8lauXU+juwSaY7Z28eguFd+J42uVUOnasLxINQY9y7kr9f6deTrcg==} 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: '@angular/compiler-cli': ^21.0.0 @@ -2847,9 +2671,9 @@ packages: resolution: {integrity: sha512-+XTFxK2jJF/EJJ5SoAzXk3qwIDfvFc5/g+bD274LZ7uY7LE8sTfG6Z8rOanPl2ZEvZWqNvmEdtXC25cE54VcoA==} engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/installed-package-contents@3.0.0': - resolution: {integrity: sha512-fkxoPuFGvxyrH+OQzyTkX2LUEamrF4jZSmxjAtPPHHGO0dqsQ8tTKjnIS8SAnPHdk2I03BDtSMR5K/4loKg79Q==} - engines: {node: ^18.17.0 || >=20.5.0} + '@npmcli/installed-package-contents@4.0.0': + resolution: {integrity: sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true '@npmcli/node-gyp@5.0.0': @@ -2860,10 +2684,6 @@ packages: resolution: {integrity: sha512-0wInJG3j/K40OJt/33ax47WfWMzZTm6OQxB9cDhTt5huCP2a9g2GnlsxmfN+PulItNPIpPrZ+kfwwUil7eHcZQ==} engines: {node: ^20.17.0 || >=22.9.0} - '@npmcli/promise-spawn@8.0.3': - resolution: {integrity: sha512-Yb00SWaL4F8w+K8YGhQ55+xE4RUNdMHV43WZGsiTM92gS+lC0mGsn7I4hLug7pbao035S6bj3Y3w0cUNGLfmkg==} - engines: {node: ^18.17.0 || >=20.5.0} - '@npmcli/promise-spawn@9.0.1': resolution: {integrity: sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2881,23 +2701,23 @@ packages: engines: {node: ^14.18.0 || >=16.10.0, npm: '>=5.10.0'} hasBin: true - '@nx/nx-darwin-arm64@22.3.3': - resolution: {integrity: sha512-zBAGFGLal09CxhQkdMpOVwcwa9Y01aFm88jTTn35s/DdIWsfngmPzz0t4mG7u2D05q7TJfGQ31pIf5GkNUjo6g==} + '@nx/nx-darwin-arm64@22.5.1': + resolution: {integrity: sha512-DuvOwhXPO6l9W7/zM4/BaAbGTIXFyHVcbbCD1c7HfgZ3VfJPmcE7H4+TuQH0cigHHtpg/eGqV100NQbd7N4zwg==} cpu: [arm64] os: [darwin] - '@nx/nx-darwin-x64@22.3.3': - resolution: {integrity: sha512-6ZQ6rMqH8NY4Jz+Gc89D5bIH2NxZb5S/vaA4yJ9RrqAfl4QWchNFD5na+aRivSd+UdsYLPKKl6qohet5SE6vOg==} + '@nx/nx-darwin-x64@22.5.1': + resolution: {integrity: sha512-81Lb7+rp3Qltpgy33Kc1qxk+8IWqZLhqvdLdRFSVV1FN1pPSJDFKyPjCn9oMpRryNPSJ8DgZDpfIaVVyP85rUw==} cpu: [x64] os: [darwin] - '@nx/nx-linux-x64-gnu@22.3.3': - resolution: {integrity: sha512-04TEbvgwRaB9ifr39YwJmWh3RuXb4Ry4m84SOJyjNXAfPrepcWgfIQn1VL2ul1Ybq+P023dLO9ME8uqFh6j1YQ==} + '@nx/nx-linux-x64-gnu@22.5.1': + resolution: {integrity: sha512-BZ/i+KTplEJmE8ZHKgPGD513Zl86DuSGyRAvbDZ7Qf19Tei7Of6vxW+ypvVDIwmDbyXfe13u54M5gDt8iiqFGQ==} cpu: [x64] os: [linux] - '@nx/nx-win32-x64-msvc@22.3.3': - resolution: {integrity: sha512-EDR8BtqeDvVNQ+kPwnfeSfmerYetitU3tDkxOMIybjKJDh69U2JwTB8n9ARwNaZQbNk7sCGNRUSZFTbAAUKvuQ==} + '@nx/nx-win32-x64-msvc@22.5.1': + resolution: {integrity: sha512-4e5LduuhpBx96JgD1J3fHUGCwC+/lL+tvXp3UVtjh/AOdINGsyI+scinT3uaI9vcB5GKBcybTxbBZzwcH50w9g==} cpu: [x64] os: [win32] @@ -2905,92 +2725,92 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@oxc-project/types@0.96.0': - resolution: {integrity: sha512-r/xkmoXA0xEpU6UGtn18CNVjXH6erU3KCpCDbpLmbVxBFor1U9MqN5Z2uMmCHJuXjJzlnDR+hWY+yPoLo8oHDw==} + '@oxc-project/types@0.106.0': + resolution: {integrity: sha512-QdsH3rZq480VnOHSHgPYOhjL8O8LBdcnSjM408BpPCCUc0JYYZPG9Gafl9i3OcGk/7137o+gweb4cCv3WAUykg==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + '@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] - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==} engines: {node: '>= 10.0.0'} '@pkgjs/parseargs@0.11.0': @@ -3001,8 +2821,8 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@playwright/test@1.57.0': - resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==} + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} engines: {node: '>=18'} hasBin: true @@ -3043,231 +2863,365 @@ packages: peerDependencies: '@redis/client': ^5.10.0 - '@rolldown/binding-android-arm64@1.0.0-beta.47': - resolution: {integrity: sha512-vPP9/MZzESh9QtmvQYojXP/midjgkkc1E4AdnPPAzQXo668ncHJcVLKjJKzoBdsQmaIvNjrMdsCwES8vTQHRQw==} + '@rolldown/binding-android-arm64@1.0.0-beta.58': + resolution: {integrity: sha512-mWj5eE4Qc8TbPdGGaaLvBb9XfDPvE1EmZkJQgiGKwchkWH4oAJcRAKMTw7ZHnb1L+t7Ah41sBkAecaIsuUgsug==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-beta.47': - resolution: {integrity: sha512-Lc3nrkxeaDVCVl8qR3qoxh6ltDZfkQ98j5vwIr5ALPkgjZtDK4BGCrrBoLpGVMg+csWcaqUbwbKwH5yvVa0oOw==} + '@rolldown/binding-darwin-arm64@1.0.0-beta.58': + resolution: {integrity: sha512-wFxUymI/5R8bH8qZFYDfAxAN9CyISEIYke+95oZPiv6EWo88aa5rskjVcCpKA532R+klFmdqjbbaD56GNmTF4Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-beta.47': - resolution: {integrity: sha512-eBYxQDwP0O33plqNVqOtUHqRiSYVneAknviM5XMawke3mwMuVlAsohtOqEjbCEl/Loi/FWdVeks5WkqAkzkYWQ==} + '@rolldown/binding-darwin-x64@1.0.0-beta.58': + resolution: {integrity: sha512-ybp3MkPj23VDV9PhtRwdU5qrGhlViWRV5BjKwO6epaSlUD5lW0WyY+roN3ZAzbma/9RrMTgZ/a/gtQq8YXOcqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-beta.47': - resolution: {integrity: sha512-Ns+kgp2+1Iq/44bY/Z30DETUSiHY7ZuqaOgD5bHVW++8vme9rdiWsN4yG4rRPXkdgzjvQ9TDHmZZKfY4/G11AA==} + '@rolldown/binding-freebsd-x64@1.0.0-beta.58': + resolution: {integrity: sha512-Evxj3yh7FWvyklUYZa0qTVT9N2zX9TPDqGF056hl8hlCZ9/ndQ2xMv6uw9PD1VlLpukbsqL+/C6M0qwipL0QMg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': - resolution: {integrity: sha512-4PecgWCJhTA2EFOlptYJiNyVP2MrVP4cWdndpOu3WmXqWqZUmSubhb4YUAIxAxnXATlGjC1WjxNPhV7ZllNgdA==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.58': + resolution: {integrity: sha512-tYeXprDOrEgVHUbPXH6MPso4cM/c6RTkmJNICMQlYdki4hGMh92aj3yU6CKs+4X5gfG0yj5kVUw/L4M685SYag==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': - resolution: {integrity: sha512-CyIunZ6D9U9Xg94roQI1INt/bLkOpPsZjZZkiaAZ0r6uccQdICmC99M9RUPlMLw/qg4yEWLlQhG73W/mG437NA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.58': + resolution: {integrity: sha512-N78vmZzP6zG967Ohr+MasCjmKtis0geZ1SOVmxrA0/bklTQSzH5kHEjW5Qn+i1taFno6GEre1E40v0wuWsNOQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': - resolution: {integrity: sha512-doozc/Goe7qRCSnzfJbFINTHsMktqmZQmweull6hsZZ9sjNWQ6BWQnbvOlfZJe4xE5NxM1NhPnY5Giqnl3ZrYQ==} + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.58': + resolution: {integrity: sha512-l+p4QVtG72C7wI2SIkNQw/KQtSjuYwS3rV6AKcWrRBF62ClsFUcif5vLaZIEbPrCXu5OFRXigXFJnxYsVVZqdQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': - resolution: {integrity: sha512-fodvSMf6Aqwa0wEUSTPewmmZOD44rc5Tpr5p9NkwQ6W1SSpUKzD3SwpJIgANDOhwiYhDuiIaYPGB7Ujkx1q0UQ==} + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.58': + resolution: {integrity: sha512-urzJX0HrXxIh0FfxwWRjfPCMeInU9qsImLQxHBgLp5ivji1EEUnOfux8KxPPnRQthJyneBrN2LeqUix9DYrNaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': - resolution: {integrity: sha512-Rxm5hYc0mGjwLh5sjlGmMygxAaV2gnsx7CNm2lsb47oyt5UQyPDZf3GP/ct8BEcwuikdqzsrrlIp8+kCSvMFNQ==} + '@rolldown/binding-linux-x64-musl@1.0.0-beta.58': + resolution: {integrity: sha512-7ijfVK3GISnXIwq/1FZo+KyAUJjL3kWPJ7rViAL6MWeEBhEgRzJ0yEd9I8N9aut8Y8ab+EKFJyRNMWZuUBwQ0A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': - resolution: {integrity: sha512-YakuVe+Gc87jjxazBL34hbr8RJpRuFBhun7NEqoChVDlH5FLhLXjAPHqZd990TVGVNkemourf817Z8u2fONS8w==} + '@rolldown/binding-openharmony-arm64@1.0.0-beta.58': + resolution: {integrity: sha512-/m7sKZCS+cUULbzyJTIlv8JbjNohxbpAOA6cM+lgWgqVzPee3U6jpwydrib328JFN/gF9A99IZEnuGYqEDJdww==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': - resolution: {integrity: sha512-ak2GvTFQz3UAOw8cuQq8pWE+TNygQB6O47rMhvevvTzETh7VkHRFtRUwJynX5hwzFvQMP6G0az5JrBGuwaMwYQ==} + '@rolldown/binding-wasm32-wasi@1.0.0-beta.58': + resolution: {integrity: sha512-6SZk7zMgv+y3wFFQ9qE5P9NnRHcRsptL1ypmudD26PDY+PvFCvfHRkJNfclWnvacVGxjowr7JOL3a9fd1wWhUw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': - resolution: {integrity: sha512-o5BpmBnXU+Cj+9+ndMcdKjhZlPb79dVPBZnWwMnI4RlNSSq5yOvFZqvfPYbyacvnW03Na4n5XXQAPhu3RydZ0w==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.58': + resolution: {integrity: sha512-sFqfYPnBZ6xBhMkadB7UD0yjEDRvs7ipR3nCggblN+N4ODCXY6qhg/bKL39+W+dgQybL7ErD4EGERVbW9DAWvg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': - resolution: {integrity: sha512-FVOmfyYehNE92IfC9Kgs913UerDog2M1m+FADJypKz0gmRg3UyTt4o1cZMCAl7MiR89JpM9jegNO1nXuP1w1vw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ia32] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': - resolution: {integrity: sha512-by/70F13IUE101Bat0oeH8miwWX5mhMFPk1yjCdxoTNHTyTdLgb0THNaebRM6AP7Kz+O3O2qx87sruYuF5UxHg==} + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.58': + resolution: {integrity: sha512-AnFWJdAqB8+IDPcGrATYs67Kik/6tnndNJV2jGRmwlbeNiQQ8GhRJU8ETRlINfII0pqi9k4WWLnb00p1QCxw/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-beta.47': - resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rolldown/pluginutils@1.0.0-beta.58': + resolution: {integrity: sha512-qWhDs6yFGR5xDfdrwiSa3CWGIHxD597uGE/A9xGqytBjANvh4rLCTTkq7szhMV4+Ygh+PMS90KVJ8xWG/TkX4w==} + + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} + cpu: [arm] + os: [android] - '@rollup/rollup-android-arm-eabi@4.54.0': - resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.54.0': - resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.54.0': - resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.54.0': - resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.54.0': - resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.54.0': - resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.54.0': - resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.54.0': - resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.54.0': - resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.54.0': - resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.54.0': - resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.54.0': - resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.54.0': - resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.54.0': - resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.54.0': - resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.54.0': - resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.54.0': - resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.54.0': - resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.54.0': - resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.54.0': - resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.54.0': - resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.54.0': - resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} cpu: [x64] os: [win32] '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@schematics/angular@21.0.4': - resolution: {integrity: sha512-/jJOf3iLvTaVa25xwiYLsfmidVAzC6rPy3Nl85iRo5bVod8be+KhHTn8aGq/8o7pzzB6Cin1oLs+riPR1nLVhg==} + '@schematics/angular@21.1.4': + resolution: {integrity: sha512-I1zdSNzdbrVCWpeE2NsZQmIoa9m0nlw4INgdGIkqUH6FgwvoGKC0RoOxKAmm6HHVJ48FE/sPI13dwAeK89ow5A==} 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'} '@sigstore/bundle@4.0.0': resolution: {integrity: sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==} engines: {node: ^20.17.0 || >=22.9.0} - '@sigstore/core@3.0.0': - resolution: {integrity: sha512-NgbJ+aW9gQl/25+GIEGYcCyi8M+ng2/5X04BMuIgoDfgvp18vDcoNHOQjQsG9418HGNYRxG3vfEXaR1ayD37gg==} + '@sigstore/core@3.1.0': + resolution: {integrity: sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==} engines: {node: ^20.17.0 || >=22.9.0} '@sigstore/protobuf-specs@0.5.0': resolution: {integrity: sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==} engines: {node: ^18.17.0 || >=20.5.0} - '@sigstore/sign@4.0.1': - resolution: {integrity: sha512-KFNGy01gx9Y3IBPG/CergxR9RZpN43N+lt3EozEfeoyqm8vEiLxwRl3ZO5sPx3Obv1ix/p7FWOlPc2Jgwfp9PA==} + '@sigstore/sign@4.1.0': + resolution: {integrity: sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==} engines: {node: ^20.17.0 || >=22.9.0} - '@sigstore/tuf@4.0.0': - resolution: {integrity: sha512-0QFuWDHOQmz7t66gfpfNO6aEjoFrdhkJaej/AOqb4kqWZVbPWFZifXZzkxyQBB1OwTbkhdT3LNpMFxwkTvf+2w==} + '@sigstore/tuf@4.0.1': + resolution: {integrity: sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==} engines: {node: ^20.17.0 || >=22.9.0} - '@sigstore/verify@3.0.0': - resolution: {integrity: sha512-moXtHH33AobOhTZF8xcX1MpOFqdvfCk7v6+teJL8zymBiDXwEsQH6XG9HGx2VIxnJZNm4cNSzflTLDnQLmIdmw==} + '@sigstore/verify@3.1.0': + resolution: {integrity: sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==} engines: {node: ^20.17.0 || >=22.9.0} '@sinclair/typebox@0.34.41': @@ -3288,14 +3242,14 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - '@stylistic/eslint-plugin@5.6.1': - resolution: {integrity: sha512-JCs+MqoXfXrRPGbGmho/zGS/jMcn3ieKl/A8YImqib76C8kjgZwq5uUFzc30lJkMvcchuRn6/v8IApLxli3Jyw==} + '@stylistic/eslint-plugin@5.8.0': + resolution: {integrity: sha512-WNPVF/FfBAjyi3OA7gok8swRiImNLKI4dmV3iK/GC/0xSJR7eCzBFsw9hLZVgb1+MYNLy7aDsjohxN1hA/FIfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=9.0.0' - '@thednp/event-listener@2.0.10': - resolution: {integrity: sha512-TH7YVKmoKg6GBLqZB+ETXObofcqJ/Tp5ycheolvYZMjLbMpzYf6MmOWTcBtx8+zrhWy8deV0hYkPvDFioDXdVQ==} + '@thednp/event-listener@2.0.12': + resolution: {integrity: sha512-PbW05+EwNfGVy2uwz0vL2xbEmcLhpuBZ2nm0pdLT088gjmY9dySfJOZUtWCmzSPJcVFn3BkgH1m1MiS11AubJA==} engines: {node: '>=16', pnpm: '>=8.6.0'} '@thednp/position-observer@1.1.0': @@ -3306,8 +3260,8 @@ packages: resolution: {integrity: sha512-D+rLHt1l7c608yCuzXYJ75aDNWeMVbor+m1HO/XibhiWRbCpD8r6TUv3ayJI+feVfCnBNfrH+p6LSDn9l99uBA==} engines: {node: '>=16', pnpm: '>=8.6.0'} - '@tokenizer/inflate@0.3.1': - resolution: {integrity: sha512-4oeoZEBQdLdt5WmP/hx1KZ6D3/Oid/0cUb2nk4F0pTDAWy+KCH3/EnAkZF/bvckWo8I33EqBm01lIPgmgc8rCA==} + '@tokenizer/inflate@0.4.1': + resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} engines: {node: '>=18'} '@tokenizer/token@0.3.0': @@ -3481,14 +3435,17 @@ packages: '@types/eslint@9.6.1': resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/express-serve-static-core@4.19.7': - resolution: {integrity: sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==} + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} - '@types/express-serve-static-core@5.1.0': - resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} '@types/express@4.17.25': resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} @@ -3550,8 +3507,8 @@ packages: '@types/node-forge@1.3.14': resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} - '@types/node@25.0.3': - resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + '@types/node@25.2.3': + resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} '@types/pako@2.0.4': resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} @@ -3617,63 +3574,63 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.50.0': - resolution: {integrity: sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==} + '@typescript-eslint/eslint-plugin@8.55.0': + resolution: {integrity: sha512-1y/MVSz0NglV1ijHC8OT49mPJ4qhPYjiK08YUQVbIOyu+5k862LKUHFkpKHWu//zmr7hDR2rhwUm6gnCGNmGBQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.50.0 + '@typescript-eslint/parser': ^8.55.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.50.0': - resolution: {integrity: sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==} + '@typescript-eslint/parser@8.55.0': + resolution: {integrity: sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.50.0': - resolution: {integrity: sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==} + '@typescript-eslint/project-service@8.55.0': + resolution: {integrity: sha512-zRcVVPFUYWa3kNnjaZGXSu3xkKV1zXy8M4nO/pElzQhFweb7PPtluDLQtKArEOGmjXoRjnUZ29NjOiF0eCDkcQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.50.0': - resolution: {integrity: sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==} + '@typescript-eslint/scope-manager@8.55.0': + resolution: {integrity: sha512-fVu5Omrd3jeqeQLiB9f1YsuK/iHFOwb04bCtY4BSCLgjNbOD33ZdV6KyEqplHr+IlpgT0QTZ/iJ+wT7hvTx49Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.50.0': - resolution: {integrity: sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==} + '@typescript-eslint/tsconfig-utils@8.55.0': + resolution: {integrity: sha512-1R9cXqY7RQd7WuqSN47PK9EDpgFUK3VqdmbYrvWJZYDd0cavROGn+74ktWBlmJ13NXUQKlZ/iAEQHI/V0kKe0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.50.0': - resolution: {integrity: sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==} + '@typescript-eslint/type-utils@8.55.0': + resolution: {integrity: sha512-x1iH2unH4qAt6I37I2CGlsNs+B9WGxurP2uyZLRz6UJoZWDBx9cJL1xVN/FiOmHEONEg6RIufdvyT0TEYIgC5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.50.0': - resolution: {integrity: sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==} + '@typescript-eslint/types@8.55.0': + resolution: {integrity: sha512-ujT0Je8GI5BJWi+/mMoR0wxwVEQaxM+pi30xuMiJETlX80OPovb2p9E8ss87gnSVtYXtJoU9U1Cowcr6w2FE0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.50.0': - resolution: {integrity: sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==} + '@typescript-eslint/typescript-estree@8.55.0': + resolution: {integrity: sha512-EwrH67bSWdx/3aRQhCoxDaHM+CrZjotc2UCCpEDVqfCE+7OjKAGWNY2HsCSTEVvWH2clYQK8pdeLp42EVs+xQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.50.0': - resolution: {integrity: sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==} + '@typescript-eslint/utils@8.55.0': + resolution: {integrity: sha512-BqZEsnPGdYpgyEIkDC1BadNY8oMwckftxBT+C8W0g1iKPdeqKZBtTfnvcq0nf60u7MkjFO8RBvpRGZBPw4L2ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.50.0': - resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} + '@typescript-eslint/visitor-keys@8.55.0': + resolution: {integrity: sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -3923,16 +3880,19 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - algoliasearch@5.40.1: - resolution: {integrity: sha512-iUNxcXUNg9085TJx0HJLjqtDE0r1RZ0GOGrt8KNQqQT5ugu8lZsHuMUYW/e0lHhq6xBvmktU9Bw4CXP9VQeKrg==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + algoliasearch@5.46.2: + resolution: {integrity: sha512-qqAXW9QvKf2tTyhpDA4qXv1IfBwD2eduSW6tUEBFIfCeE9gn9HQ9I5+MaKoenRuHrzk5sQoNh1/iof8mY7uD6Q==} engines: {node: '>= 14.0.0'} amdefine@1.0.1: resolution: {integrity: sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==} engines: {node: '>=0.4.2'} - angular-eslint@21.1.0: - resolution: {integrity: sha512-qXpIEBNYpfgpBaFblnyFegVSQjWCVUdCXTHvMcvtNtmMgtPwIDKvG8wuJo5BbQ/MNt2d8npmnRUaS2ddzdCzww==} + angular-eslint@21.2.0: + resolution: {integrity: sha512-pERqqHIMwD34UT0FoHSNTt4V332vHiAzgkY0rgdUaqSamS94IzbF02EfFxygr53UogQQOXhpLbSSDMOyovB3TA==} peerDependencies: '@angular/cli': '>= 21.0.0 < 22.0.0' eslint: ^8.57.0 || ^9.0.0 @@ -3950,8 +3910,8 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} ansi-html-community@0.0.8: @@ -4058,8 +4018,8 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -4094,8 +4054,8 @@ packages: resolution: {integrity: sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - babel-plugin-polyfill-corejs2@0.4.14: - resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + babel-plugin-polyfill-corejs2@0.4.15: + resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -4104,8 +4064,8 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.5: - resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + babel-plugin-polyfill-regenerator@0.6.6: + resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -4123,6 +4083,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.2: + resolution: {integrity: sha512-x0K50QvKQ97fdEz2kPehIerj+YTeptKF9hyYkKf6egnwmMWAkADiO0QCzSp0R5xN8FTZgYaBfSaue46Ej62nMg==} + engines: {node: 20 || >=22} + base64-arraybuffer@1.0.2: resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} engines: {node: '>= 0.6.0'} @@ -4134,8 +4098,8 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - baseline-browser-mapping@2.9.10: - resolution: {integrity: sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==} + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true basic-auth@2.0.1: @@ -4166,8 +4130,8 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@2.2.1: - resolution: {integrity: sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} bonjour-service@1.3.0: @@ -4186,6 +4150,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4230,8 +4198,8 @@ packages: resolution: {integrity: sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==} engines: {node: ^20.17.0 || >=22.9.0} - cache-manager@7.2.7: - resolution: {integrity: sha512-TKeeb9nSybk1e9E5yAiPVJ6YKdX9FYhwqqy8fBfVKAFVTJYZUNmeIvwjURW6+UikNsO6l2ta27thYgo/oumDsw==} + 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==} @@ -4260,8 +4228,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001761: - resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + caniuse-lite@1.0.30001770: + resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} canvg@3.0.11: resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} @@ -4297,6 +4265,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + chownr@3.0.0: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} @@ -4331,8 +4303,8 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} - cli-spinners@3.3.0: - resolution: {integrity: sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ==} + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} engines: {node: '>=18.20'} cli-table3@0.6.5: @@ -4403,8 +4375,8 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - commander@14.0.2: - resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} commander@2.20.3: @@ -4505,11 +4477,11 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.47.0: - resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} - core-js@3.47.0: - resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + core-js@3.48.0: + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -4518,6 +4490,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + cosmiconfig@8.3.6: resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} engines: {node: '>=14'} @@ -4539,10 +4515,6 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cron@4.3.5: - resolution: {integrity: sha512-hKPP7fq1+OfyCqoePkKfVq7tNAdFwiQORr4lZUHwrf0tebC65fYEeWgOrXOL6prn1/fegGOdTfrM6e34PJfksg==} - engines: {node: '>=18.x'} - cron@4.4.0: resolution: {integrity: sha512-fkdfq+b+AHI4cKdhZlppHveI/mgz2qpiYxcm+t5E5TsxX7QrLS1VE0+7GENEk9z0EeGPcpSciGv6ez24duWhwQ==} engines: {node: '>=18.x'} @@ -4754,15 +4726,6 @@ packages: supports-color: optional: true - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -4800,8 +4763,8 @@ packages: resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.4.0: - resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} defaults@1.0.4: @@ -4842,11 +4805,6 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -4892,18 +4850,18 @@ packages: dot@2.0.0-beta.1: resolution: {integrity: sha512-kxM7fSnNQTXOmaeGuBSXM8O3fEsBb7XSDBllkGbRwa0lJSJTxxDE/4eSNGLKZUmlFw0f1vJ5qSV2BljrgQtgIA==} - dotenv-expand@12.0.1: - resolution: {integrity: sha512-LaKRbou8gt0RNID/9RoI+J2rvXsBRPMV7p+ElHlPhcSARbCPDYcYG2s1TIzAfWv4YSgyY5taidWzzs31lNV3yQ==} - engines: {node: '>=12'} - - dotenv@16.4.7: - resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} engines: {node: '>=12'} dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -4923,8 +4881,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.267: - resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -4960,21 +4918,25 @@ packages: end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - engine.io-client@6.6.3: - resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + 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.4: - resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==} + engine.io@6.6.5: + resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==} engines: {node: '>=10.2.0'} enhanced-resolve@5.18.4: resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} engines: {node: '>=10.13.0'} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -4983,6 +4945,10 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -5013,9 +4979,6 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} @@ -5038,26 +5001,11 @@ packages: es6-shim@0.35.8: resolution: {integrity: sha512-Twf7I2v4/1tLoIXMT8HlqaBSS5H2wQTs2wx3MNYCI8K1R1/clXyCazrcVCPm/FuO9cyV8+leEaZOWD5C253NDg==} - esbuild-wasm@0.26.0: - resolution: {integrity: sha512-9rZuermDo9ZbWvKBv/vDRaRciCpR4L3rEbZLDs5kDq3TrCHRQZaQipQeV9wK/btpLBzNUBujTrd1uorDxbL/GA==} - engines: {node: '>=18'} - hasBin: true - esbuild-wasm@0.27.2: resolution: {integrity: sha512-eUTnl8eh+v8UZIZh4MrMOKDAc8Lm7+NqP3pyuTORGFY1s/o9WoiJgKnwXy+te2J3hX7iRbFSHEyig7GsPeeJyw==} engines: {node: '>=18'} hasBin: true - esbuild@0.25.12: - resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.26.0: - resolution: {integrity: sha512-3Hq7jri+tRrVWha+ZeIVhl4qJRha/XjRNSopvTsOaCvfPHrflTYTcUFcEjMKdxofsXXsdc4zjg5NOTnL4Gl57Q==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -5145,25 +5093,28 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-jest@29.6.0: - resolution: {integrity: sha512-cvBVq9GSsL3+zht0GY+s6BH0carLzAiUu5NMgvLAOQ6hwq4gTBeT9wpRZb5tUZBQJZyuPOZWiWzFPPOEQ4yNkw==} + eslint-plugin-jest@29.15.0: + resolution: {integrity: sha512-ZCGr7vTH2WSo2hrK5oM2RULFmMruQ7W3cX7YfwoTiPfzTGTFBMmrVIz45jZHd++cGKj/kWf02li/RhTGcANJSA==} engines: {node: ^20.12.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@typescript-eslint/eslint-plugin': ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 jest: '*' + typescript: '>=4.8.4 <6.0.0' peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true jest: optional: true + 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.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + eslint-plugin-prettier@5.5.5: + resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -5188,8 +5139,8 @@ packages: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-scope@9.0.0: - resolution: {integrity: sha512-+Yh0LeQKq+mW/tQArNj67tljR3L1HajDTQPuZOEwC00oBdoIDQrr89yBgjAlzAwRrY/5zDkM3v99iGHwz9y0dw==} + eslint-scope@9.1.0: + resolution: {integrity: sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: @@ -5262,8 +5213,8 @@ packages: eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} @@ -5296,8 +5247,8 @@ packages: exponential-backoff@3.1.3: resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + express-rate-limit@8.2.1: + resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==} engines: {node: '>= 16'} peerDependencies: express: '>= 4.11' @@ -5306,10 +5257,6 @@ packages: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} - express@5.1.0: - resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} - engines: {node: '>= 18'} - express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} @@ -5343,8 +5290,8 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} faye-websocket@0.11.4: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} @@ -5369,8 +5316,8 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - file-type@21.1.0: - resolution: {integrity: sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==} + file-type@21.3.0: + resolution: {integrity: sha512-8kPJMIGz1Yt/aPEwOsrR97ZyZaD1Iqm8PClb1nYFclUCkBi0Ma5IsYNQzvSFS9ib51lWyIw5mIT9rWzI/xjpzA==} engines: {node: '>=20'} fill-range@7.1.1: @@ -5444,8 +5391,8 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} @@ -5558,27 +5505,27 @@ packages: glob@10.5.0: resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - hasBin: true - - glob@11.1.0: - resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} - engines: {node: 20 || >=22} + 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 hasBin: true glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} + glob@13.0.3: + resolution: {integrity: sha512-/g3B0mC+4x724v1TgtBlBtt2hPi/EWptsIAmXUx9Z2rvBYleQcsrmaOzd5LyL50jf/Soi83ZDJmw2+XqvH/EeA==} + engines: {node: 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported + 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@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@16.5.0: - resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + globals@17.3.0: + resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==} engines: {node: '>=18'} globalthis@1.0.4: @@ -5623,16 +5570,20 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hashery@1.3.0: - resolution: {integrity: sha512-fWltioiy5zsSAs9ouEnvhsVJeAXRybGCNNv0lvzpzNOSDbULXRy7ivFWwCCv4I5Am6kSo75hmbsCduOoc2/K4w==} + hashery@1.4.0: + resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==} engines: {node: '>=20'} hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hookified@1.14.0: - resolution: {integrity: sha512-pi1ynXIMFx/uIIwpWJ/5CEtOHLGtnUB0WhGeeYT+fKcQ+WCQbm3/rrkAXnpfph++PgepNqPdTC2WTj8A6k6zoQ==} + hono@4.11.9: + resolution: {integrity: sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==} + engines: {node: '>=16.9.0'} + + hookified@1.15.1: + resolution: {integrity: sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==} hosted-git-info@9.0.2: resolution: {integrity: sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==} @@ -5655,8 +5606,8 @@ packages: resolution: {integrity: sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==} engines: {node: '>=8.0.0'} - htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} http-auth-connect@1.0.6: resolution: {integrity: sha512-yaO0QSCPqGCjPrl3qEEHjJP+lwZ6gMpXLuCBE06eWwcXomkI5TARtu0kxf9teFuBj6iaV3Ybr15jaWUvbzNzHw==} @@ -5672,8 +5623,8 @@ packages: http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} - http-errors@1.6.3: - resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} http-errors@2.0.1: @@ -5720,8 +5671,8 @@ packages: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} - i18next@25.5.3: - resolution: {integrity: sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==} + i18next@25.7.4: + resolution: {integrity: sha512-hRkpEblXXcXSNbw8mBNq9042OEetgyB/ahc/X17uV/khPwzV+uB8RHceHh3qavyrkPJvmXFKXME2Sy1E0KjAfw==} peerDependencies: typescript: ^5 peerDependenciesMeta: @@ -5736,8 +5687,8 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - iconv-lite@0.7.1: - resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} icss-utils@5.1.0: @@ -5789,16 +5740,9 @@ packages: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - inherits@2.0.3: - resolution: {integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==} - inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@5.0.0: - resolution: {integrity: sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==} - engines: {node: ^18.17.0 || >=20.5.0} - ini@6.0.0: resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -5814,6 +5758,10 @@ packages: iobuffer@5.4.0: resolution: {integrity: sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==} + ip-address@10.0.1: + resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==} + engines: {node: '>= 12'} + ip-address@10.1.0: resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} engines: {node: '>= 12'} @@ -5906,6 +5854,10 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -5989,10 +5941,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -6016,8 +5964,8 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} isarray@1.0.0: @@ -6029,14 +5977,17 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - isexe@3.1.1: - resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} - engines: {node: '>=16'} + isexe@4.0.0: + resolution: {integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==} + engines: {node: '>=20'} isobject@3.0.1: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} + isomorphic.js@0.2.5: + resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -6064,8 +6015,8 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + jackspeak@4.2.3: + resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==} engines: {node: 20 || >=22} jest-canvas-mock@2.5.2: @@ -6282,6 +6233,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-schema@0.4.0: resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} @@ -6316,8 +6270,8 @@ packages: resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} engines: {node: '>=12', npm: '>=6'} - jspdf@3.0.4: - resolution: {integrity: sha512-dc6oQ8y37rRcHn316s4ngz/nOjayLF/FFxBF4V9zamQKRqXxyiH1zagkCdktdWhtoQId5K20xt1lB90XzkB+hQ==} + jspdf@4.1.0: + resolution: {integrity: sha512-xd1d/XRkwqnsq6FP3zH1Q+Ejqn2ULIJeDZ+FTKpaabVpZREjsJKRJwuokTNgdqOU+fl55KgbvgZ1pRTSWCP2kQ==} jwa@2.0.1: resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} @@ -6334,8 +6288,8 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - keyv@5.5.5: - resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} + keyv@5.6.0: + resolution: {integrity: sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==} kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} @@ -6373,6 +6327,11 @@ packages: lex-parser@0.1.4: resolution: {integrity: sha512-DuAEISsr1H4LOpmFLkyMc8YStiRWZCO8hMsoXAXSbgyfvs2WQhSt0+/FBv3ZU/JBFZMGcE+FWzEBSzwUU7U27w==} + lib0@0.2.117: + resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==} + engines: {node: '>=16'} + hasBin: true + libphonenumber-js@1.12.31: resolution: {integrity: sha512-Z3IhgVgrqO1S5xPYM3K5XwbkDasU67/Vys4heW+lfSBALcUZjeIIzI8zCLifY+OCzSq+fpDdywMDa7z+4srJPQ==} @@ -6394,8 +6353,8 @@ packages: resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} engines: {node: '>=20.0.0'} - lmdb@3.4.3: - resolution: {integrity: sha512-GWV1kVi6uhrXWqe+3NXWO73OYe8fto6q8JMo0HOpk1vf8nEyFWgo4CSNJpIFzsOxOrysVUlcO48qRbQfmKd1gA==} + lmdb@3.4.4: + resolution: {integrity: sha512-+Y2DqovevLkb6DrSQ6SXTYLEd6kvlRbhsxzgJrk7BUfOVA/mt21ak6pFDZDKxiAczHMWxrb02kXBTSTIA0O94A==} hasBin: true load-esm@1.0.3: @@ -6455,17 +6414,13 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} - log-symbols@7.0.1: resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} engines: {node: '>=18'} @@ -6484,8 +6439,8 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.4: - resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -6505,8 +6460,8 @@ packages: magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} make-dir@2.1.0: resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} @@ -6554,8 +6509,10 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - memfs@4.51.1: - resolution: {integrity: sha512-Eyt3XrufitN2ZL9c/uIRMyDwXanLI88h/L3MoWqNY747ha3dMR9dWqp8cRT5ntjZ0U1TNuq4U91ZXK0sMBjYOQ==} + memfs@4.56.10: + resolution: {integrity: sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==} + peerDependencies: + tslib: '2' merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -6622,8 +6579,8 @@ packages: minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + minimatch@10.2.0: + resolution: {integrity: sha512-ugkC31VaVg9cF0DFVoADH12k6061zNZkZON+aX8AWsR9GhPcErkcMBceb6znR8wLERM2AkkOxy2nWRLpT9Jq5w==} engines: {node: 20 || >=22} minimatch@3.1.2: @@ -6640,8 +6597,8 @@ packages: resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} engines: {node: '>=16 || 14 >=14.17'} - minipass-fetch@5.0.0: - resolution: {integrity: sha512-fiCdUALipqgPWrOVTz9fw0XhcazULXOSU6ie40DDbX1F49p1dBrSRBuswndTx1x3vEb/g0FT7vC4c4C2u/mh3A==} + minipass-fetch@5.0.1: + resolution: {integrity: sha512-yHK8pb0iCGat0lDrs/D6RZmCdaBT64tULXjdxjSMAqoDi18Q3qKEUTHypHQZQd9+FYpIS+lkvpq6C/R6SbUeRw==} engines: {node: ^20.17.0 || >=22.9.0} minipass-flush@1.0.5: @@ -6652,8 +6609,8 @@ packages: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} - minipass-sized@1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + minipass-sized@2.0.0: + resolution: {integrity: sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==} engines: {node: '>=8'} minipass@3.3.6: @@ -6782,8 +6739,8 @@ packages: resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} hasBin: true - node-gyp@12.1.0: - resolution: {integrity: sha512-W+RYA8jBnhSr2vrTtlPYPc1K+CSjGpVDRZxcqJcERZ8ND3A1ThWPHRwctTx3qC3oW99jt726jhdz3Y6ky87J4g==} + node-gyp@12.2.0: + resolution: {integrity: sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -6806,28 +6763,20 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - npm-bundled@4.0.0: - resolution: {integrity: sha512-IxaQZDMsqfQ2Lz37VvyyEtKLe8FsRZuysmedy/N06TU1RyVppYKXrO4xIhR0F+7ubIBox6Q7nir6fQI3ej39iA==} - engines: {node: ^18.17.0 || >=20.5.0} + npm-bundled@5.0.0: + resolution: {integrity: sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==} + engines: {node: ^20.17.0 || >=22.9.0} npm-install-checks@8.0.0: resolution: {integrity: sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==} engines: {node: ^20.17.0 || >=22.9.0} - npm-normalize-package-bin@4.0.0: - resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} - engines: {node: ^18.17.0 || >=20.5.0} - npm-normalize-package-bin@5.0.0: resolution: {integrity: sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==} engines: {node: ^20.17.0 || >=22.9.0} - npm-package-arg@13.0.1: - resolution: {integrity: sha512-6zqls5xFvJbgFjB1B2U6yITtyGBjDBORB7suI4zA4T/sZ1OmkMFlaQSNB/4K0LtXNA1t4OprAFxPisadK5O2ag==} + npm-package-arg@13.0.2: + resolution: {integrity: sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==} engines: {node: ^20.17.0 || >=22.9.0} npm-packlist@10.0.3: @@ -6914,6 +6863,10 @@ packages: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + open@11.0.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} + open@8.4.0: resolution: {integrity: sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==} engines: {node: '>=12'} @@ -6930,16 +6883,12 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} - ora@9.0.0: resolution: {integrity: sha512-m0pg2zscbYgWbqRR6ABga5c3sZdEon7bSgjnlXC64kxtxLOyjRcbbUkLj7HFyy/FTD+P2xdBWu8snGhYI0jc4A==} engines: {node: '>=20'} - ordered-binary@1.6.0: - resolution: {integrity: sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==} + ordered-binary@1.6.1: + resolution: {integrity: sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==} os-name@4.0.1: resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} @@ -6980,8 +6929,8 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - pacote@21.0.3: - resolution: {integrity: sha512-itdFlanxO0nmQv4ORsvA9K1wv40IPfB9OmWqfaJWvoJ30VKyHsqNgDVeG+TVhI7Gk7XW8slUy7cA9r6dF5qohw==} + pacote@21.0.4: + resolution: {integrity: sha512-RplP/pDW0NNNDh3pnaoIWYPvNenS7UqMbXyvMqJczosiFWTeGGwJC2NQBLqKf4rGLFfwCOnntw1aEp9Jiqm1MA==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -7064,30 +7013,30 @@ packages: performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - pg-cloudflare@1.2.7: - resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} + pg-cloudflare@1.3.0: + resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} - pg-connection-string@2.9.1: - resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==} + pg-connection-string@2.11.0: + resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.10.1: - resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==} + pg-pool@3.11.0: + resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} peerDependencies: pg: '>=8.0' - pg-protocol@1.10.3: - resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==} + pg-protocol@1.11.0: + resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.16.3: - resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==} + pg@8.18.0: + resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -7121,8 +7070,8 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} - piscina@5.1.3: - resolution: {integrity: sha512-0u3N7H4+hbr40KjuVn2uNhOcthu/9usKhnw5vT3J7ply79v3D3M8naI00el9Klcy16x557VsEkkUQaHCWFXC/g==} + piscina@5.1.4: + resolution: {integrity: sha512-7uU4ZnKeQq22t9AsmHGD2w4OYQGonwFnTypDypaWi7Qr2EvQIFVtG8J5D/3bE7W123Wdc9+v4CZDu5hJXVCtBg==} engines: {node: '>=20.x'} pkce-challenge@5.0.1: @@ -7133,13 +7082,13 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - playwright-core@1.57.0: - resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} engines: {node: '>=18'} hasBin: true - playwright@1.57.0: - resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} engines: {node: '>=18'} hasBin: true @@ -7221,6 +7170,10 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + powershell-utils@0.1.0: + 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==} @@ -7228,12 +7181,12 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + prettier-linter-helpers@1.0.1: + resolution: {integrity: sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==} engines: {node: '>=6.0.0'} - prettier@3.7.4: - resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + prettier@3.8.1: + resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} engines: {node: '>=14'} hasBin: true @@ -7245,10 +7198,6 @@ packages: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} - proc-log@5.0.0: - resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} - engines: {node: ^18.17.0 || >=20.5.0} - proc-log@6.1.0: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -7288,8 +7237,16 @@ packages: qrcode-generator@1.5.2: resolution: {integrity: sha512-pItrW0Z9HnDBnFmgiNrY1uxRdri32Uh9EjNYLPVC2zZ3ZRIIEqBoDgm4DkvDwNNDHTK7FNkmr8zAa77BYc9xNw==} - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + engines: {node: '>=0.6'} + + 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'} queue-microtask@1.2.3: @@ -7331,6 +7288,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + redis@5.10.0: resolution: {integrity: sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==} engines: {node: '>= 18'} @@ -7436,21 +7397,26 @@ packages: resolution: {integrity: sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==} engines: {node: '>= 0.8.15'} - rimraf@6.1.2: - resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==} + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} hasBin: true robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rolldown@1.0.0-beta.47: - resolution: {integrity: sha512-Mid74GckX1OeFAOYz9KuXeWYhq3xkXbMziYIC+ULVdUzPTG9y70OBSBQDQn9hQP8u/AfhuYw1R0BSg15nBI4Dg==} + rolldown@1.0.0-beta.58: + resolution: {integrity: sha512-v1FCjMZCan7f+xGAHBi+mqiE4MlH7I+SXEHSQSJoMOGNNB2UYtvMiejsq9YuUOiZjNeUeV/a21nSFbrUR+4ZCQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.54.0: - resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -7498,8 +7464,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass-loader@16.0.5: - resolution: {integrity: sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==} + sass-loader@16.0.6: + resolution: {integrity: sha512-sglGzId5gmlfxNs4gK2U3h7HlVRfx278YK6Ono5lwzuvi1jxig80YiuHkaDBVsYIKFhx8wN7XSCI0M2IDS/3qA==} engines: {node: '>= 18.12.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -7519,13 +7485,14 @@ packages: webpack: optional: true - sass@1.93.2: - resolution: {integrity: sha512-t+YPtOQHpGW1QWsh1CHQ5cPIr9lbbGZLZnbihP/D/qZj/yuV68m8qarcV17nvkOX81BCrvzAlq2klCQFZghyTg==} + sass@1.97.1: + resolution: {integrity: sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==} engines: {node: '>=14.0.0'} hasBin: true - sax@1.4.3: - resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} @@ -7559,6 +7526,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@0.19.2: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} @@ -7570,8 +7542,8 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serve-index@1.9.1: - resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} + serve-index@1.9.2: + resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} engines: {node: '>= 0.8.0'} serve-static@1.16.3: @@ -7594,9 +7566,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - setprototypeof@1.1.0: - resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -7644,8 +7613,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sigstore@4.0.0: - resolution: {integrity: sha512-Gw/FgHtrLM9WP8P5lLcSGh9OQcrTruWCELAiS48ik1QbL0cH+dfjomiRTUE9zzz+D1N6rOLkwXUvVmXZAsNE0Q==} + sigstore@4.1.0: + resolution: {integrity: sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==} engines: {node: ^20.17.0 || >=22.9.0} sirv@3.0.2: @@ -7664,19 +7633,19 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - socket.io-adapter@2.5.5: - resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + socket.io-adapter@2.5.6: + resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==} - socket.io-client@4.8.1: - resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + socket.io-client@4.8.3: + resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} - socket.io-parser@4.2.4: - resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + socket.io-parser@4.2.5: + resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==} engines: {node: '>=10.0.0'} - socket.io@4.8.1: - resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} + socket.io@4.8.3: + resolution: {integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==} engines: {node: '>=10.2.0'} sockjs@0.3.24: @@ -7755,12 +7724,8 @@ packages: resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==} engines: {node: '>=14'} - ssri@12.0.0: - resolution: {integrity: sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==} - engines: {node: ^18.17.0 || >=20.5.0} - - ssri@13.0.0: - resolution: {integrity: sha512-yizwGBpbCn4YomB2lzhZqrHLJoqFGXihNbib3ozhqF/cIp5ue+xSmOQrjNasEE62hFxsCcg/V/z23t4n8jMEng==} + ssri@13.0.1: + resolution: {integrity: sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==} engines: {node: ^20.17.0 || >=22.9.0} stable-hash-x@0.2.0: @@ -7814,8 +7779,8 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.0: - resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + string-width@8.1.1: + resolution: {integrity: sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==} engines: {node: '>=20'} string.prototype.trim@1.2.10: @@ -7864,12 +7829,12 @@ packages: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} - superagent@10.2.3: - resolution: {integrity: sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==} + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} engines: {node: '>=14.18.0'} - supertest@7.1.4: - resolution: {integrity: sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==} + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} engines: {node: '>=14.18.0'} supports-color@7.2.0: @@ -7898,20 +7863,20 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - tablesort@5.6.0: - resolution: {integrity: sha512-cZZXK3G089PbpxH8N7vN7Z21SEKqXAaCiSVOmZdR/v7z8TFCsF/OFr0rzjhQuFlQQHy9uQtW9P2oQFJzJFGVrg==} - engines: {node: '>= 16', npm: '>= 8'} + tablesort@5.7.0: + resolution: {integrity: sha512-irnN1HPD08466v6DHKR1+gqZ2be2+QZBDIGTM1DFGoWywY+d38bFtfsuUqBbMGkqaMyYE1uPxE7p0AM5cmbRSA==} + engines: {node: '>= 22', npm: '>= 10'} tapable@2.3.0: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tar@7.5.2: - resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} + tar@7.5.9: + resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} terser-webpack-plugin@5.3.16: @@ -7930,16 +7895,16 @@ packages: uglify-js: optional: true - terser@5.44.0: - resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} - engines: {node: '>=10'} - hasBin: true - terser@5.44.1: resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} engines: {node: '>=10'} hasBin: true + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -7985,8 +7950,8 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - token-types@6.1.1: - resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} totalist@3.0.1: @@ -8015,8 +7980,8 @@ packages: resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==} engines: {node: '>=6'} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} peerDependencies: typescript: '>=4.8.4' @@ -8194,8 +8159,8 @@ packages: typeorm-aurora-data-api-driver: optional: true - typescript-eslint@8.50.0: - resolution: {integrity: sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==} + typescript-eslint@8.55.0: + resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -8229,8 +8194,12 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@7.16.0: - resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} + undici@7.20.0: + resolution: {integrity: sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==} + engines: {node: '>=20.18.1'} + + undici@7.22.0: + resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} engines: {node: '>=20.18.1'} unicode-canonical-property-names-ecmascript@2.0.1: @@ -8312,9 +8281,9 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - validate-npm-package-name@6.0.2: - resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} - engines: {node: ^18.17.0 || >=20.5.0} + validate-npm-package-name@7.0.2: + 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==} @@ -8347,8 +8316,8 @@ packages: '@egjs/hammerjs': ^2.0.0 component-emitter: ^1.3.0 || ^2.0.0 - vite@7.2.2: - resolution: {integrity: sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8387,8 +8356,8 @@ packages: yaml: optional: true - vite@7.3.0: - resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -8434,8 +8403,12 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - watchpack@2.4.4: - resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + watchpack@2.5.0: + resolution: {integrity: sha512-e6vZvY6xboSwLz2GD36c16+O/2Z6fKvIf4pOXptw2rY9MVwE/TXc6RGqxD3I3x0a28lwBY7DE+76uTPSsBrrCA==} + engines: {node: '>=10.13.0'} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} engines: {node: '>=10.13.0'} wbuf@1.7.3: @@ -8481,8 +8454,8 @@ packages: resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} engines: {node: '>=6'} - webpack-sources@3.3.3: - resolution: {integrity: sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==} + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} engines: {node: '>=10.13.0'} webpack-subresource-integrity@5.1.0: @@ -8495,8 +8468,8 @@ packages: html-webpack-plugin: optional: true - webpack@5.103.0: - resolution: {integrity: sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==} + webpack@5.104.1: + resolution: {integrity: sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -8505,8 +8478,8 @@ packages: webpack-cli: optional: true - webpack@5.104.0: - resolution: {integrity: sha512-5DeICTX8BVgNp6afSPYXAFjskIgWGlygQH58bcozPOXgo2r/6xx39Y1+cULZ3gTxUYQP88jmwLj2anu4Xaq84g==} + webpack@5.105.0: + resolution: {integrity: sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -8526,6 +8499,7 @@ packages: whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@4.0.0: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} @@ -8556,13 +8530,8 @@ packages: engines: {node: '>= 8'} hasBin: true - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true - - which@6.0.0: - resolution: {integrity: sha512-f+gEpIKMR9faW/JgAgPK1D7mekkFoqbmiwvNzuhsHetni20QSgzg9Vhn0g2JSJkkfehQnqdUAx7/e15qS1lPxg==} + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -8603,8 +8572,8 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -8615,8 +8584,8 @@ packages: utf-8-validate: optional: true - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -8631,6 +8600,10 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -8646,6 +8619,18 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} + y-protocols@1.0.7: + resolution: {integrity: sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.0.0 + + y-websocket@3.0.0: + resolution: {integrity: sha512-mUHy7AzkOZ834T/7piqtlA8Yk6AchqKqcrCXjKW8J1w2lPtRDjz8W5/CvXz9higKAHgKRKqpI3T33YkRFLkPtg==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + peerDependencies: + yjs: ^13.5.6 + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -8676,6 +8661,10 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yjs@13.6.29: + resolution: {integrity: sha512-kHqDPdltoXH+X4w1lVmMtddE3Oeqq48nM40FD5ojTd8xYhQpzIDcfE2keMSU5bAgRPJBe225WTUdyUgj1DtbiQ==} + engines: {node: '>=16.0.0', npm: '>=8.0.0'} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -8692,13 +8681,13 @@ packages: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} - zod-to-json-schema@3.25.0: - resolution: {integrity: sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==} + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} peerDependencies: zod: ^3.25 || ^4 - zod@4.1.13: - resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} + zod@4.3.5: + resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} zone.js@0.16.0: resolution: {integrity: sha512-LqLPpIQANebrlxY6jKcYKdgN5DTXyyHAKnnWWjE5pPfEQ4n7j5zn7mOEEpwNZVKGqx3kKKmvplEmoBrvpgROTA==} @@ -8707,129 +8696,129 @@ snapshots: '@aduh95/viz.js@3.4.0': {} - '@ai-sdk/gateway@2.0.23(zod@4.1.13)': + '@ai-sdk/gateway@2.0.23(zod@4.3.5)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.1.13) + '@ai-sdk/provider-utils': 3.0.19(zod@4.3.5) '@vercel/oidc': 3.0.5 - zod: 4.1.13 + zod: 4.3.5 - '@ai-sdk/openai-compatible@1.0.29(zod@4.1.13)': + '@ai-sdk/openai-compatible@1.0.29(zod@4.3.5)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.1.13) - zod: 4.1.13 + '@ai-sdk/provider-utils': 3.0.19(zod@4.3.5) + zod: 4.3.5 - '@ai-sdk/openai@2.0.88(zod@4.1.13)': + '@ai-sdk/openai@2.0.88(zod@4.3.5)': dependencies: '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.1.13) - zod: 4.1.13 + '@ai-sdk/provider-utils': 3.0.19(zod@4.3.5) + zod: 4.3.5 - '@ai-sdk/provider-utils@3.0.19(zod@4.1.13)': + '@ai-sdk/provider-utils@3.0.19(zod@4.3.5)': dependencies: '@ai-sdk/provider': 2.0.0 '@standard-schema/spec': 1.1.0 eventsource-parser: 3.0.6 - zod: 4.1.13 + zod: 4.3.5 '@ai-sdk/provider@2.0.0': dependencies: json-schema: 0.4.0 - '@algolia/abtesting@1.6.1': + '@algolia/abtesting@1.12.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-abtesting@5.40.1': + '@algolia/client-abtesting@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-analytics@5.40.1': + '@algolia/client-analytics@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-common@5.40.1': {} + '@algolia/client-common@5.46.2': {} - '@algolia/client-insights@5.40.1': + '@algolia/client-insights@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-personalization@5.40.1': + '@algolia/client-personalization@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-query-suggestions@5.40.1': + '@algolia/client-query-suggestions@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/client-search@5.40.1': + '@algolia/client-search@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/ingestion@1.40.1': + '@algolia/ingestion@1.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/monitoring@1.40.1': + '@algolia/monitoring@1.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/recommend@5.40.1': + '@algolia/recommend@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + '@algolia/client-common': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 - '@algolia/requester-browser-xhr@5.40.1': + '@algolia/requester-browser-xhr@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 + '@algolia/client-common': 5.46.2 - '@algolia/requester-fetch@5.40.1': + '@algolia/requester-fetch@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 + '@algolia/client-common': 5.46.2 - '@algolia/requester-node-http@5.40.1': + '@algolia/requester-node-http@5.46.2': dependencies: - '@algolia/client-common': 5.40.1 + '@algolia/client-common': 5.46.2 '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 - '@angular-builders/common@5.0.0-beta.1(@types/node@25.0.3)(chokidar@4.0.3)(typescript@5.9.3)': + '@angular-builders/common@5.0.3(@types/node@25.2.3)(chokidar@5.0.0)(typescript@5.9.3)': dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - ts-node: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) tsconfig-paths: 4.2.0 transitivePeerDependencies: - '@swc/core' @@ -8838,18 +8827,18 @@ snapshots: - chokidar - typescript - '@angular-builders/jest@21.0.0-beta.1(634017c93eb9ec4294b09b831a21836c)': - dependencies: - '@angular-builders/common': 5.0.0-beta.1(@types/node@25.0.3)(chokidar@4.0.3)(typescript@5.9.3) - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) - '@angular-devkit/build-angular': 21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.0.3)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3) - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser-dynamic': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))) - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) - jest-preset-angular: 16.0.0(b4775edd0d85938ea7ee883bbb12d4b1) - lodash: 4.17.21 + '@angular-builders/jest@21.0.3(ff520fd63e6ac660c8d0c8706a71b20b)': + dependencies: + '@angular-builders/common': 5.0.3(@types/node@25.2.3)(chokidar@5.0.0)(typescript@5.9.3) + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + '@angular-devkit/build-angular': 21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.2.3)(chokidar@5.0.0)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular/compiler-cli': 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser-dynamic': 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))) + jest: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + jest-preset-angular: 16.0.0(128486741484e87e44937e8ea7d8f148) + lodash: 4.17.23 transitivePeerDependencies: - '@angular/platform-browser' - '@babel/core' @@ -8864,76 +8853,76 @@ snapshots: - jsdom - typescript - '@angular-devkit/architect@0.2100.4(chokidar@4.0.3)': + '@angular-devkit/architect@0.2101.4(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.0.3)(chokidar@4.0.3)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3)': + '@angular-devkit/build-angular@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.2.3)(chokidar@5.0.0)(jest-environment-jsdom@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(jiti@2.6.1)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) - '@angular-devkit/build-webpack': 0.2100.4(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.104.0(esbuild@0.26.0)))(webpack@5.104.0(esbuild@0.26.0)) - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular/build': 21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.0.3)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.0)(tslib@2.8.1)(typescript@5.9.3) - '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) - '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + '@angular-devkit/build-webpack': 0.2101.4(chokidar@5.0.0)(webpack-dev-server@5.2.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)))(webpack@5.105.0(esbuild@0.27.2)) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular/build': 21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3) + '@angular/compiler-cli': 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.4) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-runtime': 7.28.3(@babel/core@7.28.4) - '@babel/preset-env': 7.28.3(@babel/core@7.28.4) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-runtime': 7.28.5(@babel/core@7.28.5) + '@babel/preset-env': 7.28.5(@babel/core@7.28.5) '@babel/runtime': 7.28.4 '@discoveryjs/json-ext': 0.6.3 - '@ngtools/webpack': 21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.104.0(esbuild@0.26.0)) + '@ngtools/webpack': 21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(esbuild@0.27.2)) ansi-colors: 4.1.3 - autoprefixer: 10.4.21(postcss@8.5.6) - babel-loader: 10.0.0(@babel/core@7.28.4)(webpack@5.104.0(esbuild@0.26.0)) + autoprefixer: 10.4.23(postcss@8.5.6) + babel-loader: 10.0.0(@babel/core@7.28.5)(webpack@5.105.0(esbuild@0.27.2)) browserslist: 4.28.1 - copy-webpack-plugin: 13.0.1(webpack@5.104.0(esbuild@0.26.0)) - css-loader: 7.1.2(webpack@5.104.0(esbuild@0.26.0)) - esbuild-wasm: 0.26.0 + copy-webpack-plugin: 13.0.1(webpack@5.105.0(esbuild@0.27.2)) + css-loader: 7.1.2(webpack@5.105.0(esbuild@0.27.2)) + esbuild-wasm: 0.27.2 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.0(less@4.4.2)(webpack@5.104.0(esbuild@0.26.0)) - license-webpack-plugin: 4.0.2(webpack@5.104.0(esbuild@0.26.0)) + less-loader: 12.3.0(less@4.4.2)(webpack@5.105.0(esbuild@0.27.2)) + license-webpack-plugin: 4.0.2(webpack@5.105.0(esbuild@0.27.2)) loader-utils: 3.3.1 - mini-css-extract-plugin: 2.9.4(webpack@5.104.0(esbuild@0.26.0)) - open: 10.2.0 + mini-css-extract-plugin: 2.9.4(webpack@5.105.0(esbuild@0.27.2)) + open: 11.0.0 ora: 9.0.0 picomatch: 4.0.3 - piscina: 5.1.3 + piscina: 5.1.4 postcss: 8.5.6 - postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.0(esbuild@0.26.0)) + postcss-loader: 8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.27.2)) resolve-url-loader: 5.0.0 rxjs: 7.8.2 - sass: 1.93.2 - sass-loader: 16.0.5(sass@1.93.2)(webpack@5.104.0(esbuild@0.26.0)) + sass: 1.97.1 + sass-loader: 16.0.6(sass@1.97.1)(webpack@5.105.0(esbuild@0.27.2)) semver: 7.7.3 - source-map-loader: 5.0.0(webpack@5.104.0(esbuild@0.26.0)) + source-map-loader: 5.0.0(webpack@5.105.0(esbuild@0.27.2)) source-map-support: 0.5.21 - terser: 5.44.0 + terser: 5.44.1 tinyglobby: 0.2.15 tree-kill: 1.2.2 tslib: 2.8.1 typescript: 5.9.3 - webpack: 5.104.0(esbuild@0.26.0) - webpack-dev-middleware: 7.4.5(webpack@5.104.0(esbuild@0.26.0)) - webpack-dev-server: 5.2.2(webpack@5.104.0(esbuild@0.26.0)) + webpack: 5.105.0(esbuild@0.27.2) + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) + webpack-dev-server: 5.2.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) webpack-merge: 6.0.1 - webpack-subresource-integrity: 5.1.0(webpack@5.104.0(esbuild@0.26.0)) + webpack-subresource-integrity: 5.1.0(webpack@5.105.0(esbuild@0.27.2)) optionalDependencies: - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) - esbuild: 0.26.0 - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) + esbuild: 0.27.2 + jest: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-environment-jsdom: 30.2.0 transitivePeerDependencies: - '@angular/compiler' @@ -8958,12 +8947,12 @@ snapshots: - webpack-cli - yaml - '@angular-devkit/build-webpack@0.2100.4(chokidar@4.0.3)(webpack-dev-server@5.2.2(webpack@5.104.0(esbuild@0.26.0)))(webpack@5.104.0(esbuild@0.26.0))': + '@angular-devkit/build-webpack@0.2101.4(chokidar@5.0.0)(webpack-dev-server@5.2.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)))(webpack@5.105.0(esbuild@0.27.2))': dependencies: - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) rxjs: 7.8.2 - webpack: 5.104.0(esbuild@0.26.0) - webpack-dev-server: 5.2.2(webpack@5.104.0(esbuild@0.26.0)) + webpack: 5.105.0(esbuild@0.27.2) + webpack-dev-server: 5.2.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) transitivePeerDependencies: - chokidar @@ -8989,7 +8978,7 @@ snapshots: optionalDependencies: chokidar: 4.0.3 - '@angular-devkit/core@20.3.4(chokidar@4.0.3)': + '@angular-devkit/core@21.1.0(chokidar@5.0.0)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -8998,9 +8987,9 @@ snapshots: rxjs: 7.8.2 source-map: 0.7.6 optionalDependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 - '@angular-devkit/core@21.0.4(chokidar@4.0.3)': + '@angular-devkit/core@21.1.4(chokidar@5.0.0)': dependencies: ajv: 8.17.1 ajv-formats: 3.0.1(ajv@8.17.1) @@ -9009,13 +8998,13 @@ snapshots: rxjs: 7.8.2 source-map: 0.7.6 optionalDependencies: - chokidar: 4.0.3 + chokidar: 5.0.0 - '@angular-devkit/schematics-cli@19.2.19(@types/node@25.0.3)(chokidar@4.0.3)': + '@angular-devkit/schematics-cli@19.2.19(@types/node@25.2.3)(chokidar@4.0.3)': dependencies: '@angular-devkit/core': 19.2.19(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) - '@inquirer/prompts': 7.3.2(@types/node@25.0.3) + '@inquirer/prompts': 7.3.2(@types/node@25.2.3) ansi-colors: 4.1.3 symbol-observable: 4.0.0 yargs-parser: 21.1.1 @@ -9043,66 +9032,66 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/schematics@20.3.4(chokidar@4.0.3)': + '@angular-devkit/schematics@21.1.0(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 20.3.4(chokidar@4.0.3) + '@angular-devkit/core': 21.1.0(chokidar@5.0.0) jsonc-parser: 3.3.1 - magic-string: 0.30.17 - ora: 8.2.0 + magic-string: 0.30.21 + ora: 9.0.0 rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-devkit/schematics@21.0.4(chokidar@4.0.3)': + '@angular-devkit/schematics@21.1.4(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) jsonc-parser: 3.3.1 - magic-string: 0.30.19 + magic-string: 0.30.21 ora: 9.0.0 rxjs: 7.8.2 transitivePeerDependencies: - chokidar - '@angular-eslint/builder@21.1.0(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/builder@21.2.0(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular/cli': 21.0.4(@types/node@25.0.3)(chokidar@4.0.3) + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular/cli': 21.1.4(@types/node@25.2.3)(chokidar@5.0.0) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - chokidar - '@angular-eslint/bundled-angular-compiler@21.1.0': {} + '@angular-eslint/bundled-angular-compiler@21.2.0': {} - '@angular-eslint/eslint-plugin-template@21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/eslint-plugin-template@21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 21.1.0 - '@angular-eslint/template-parser': 21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/utils': 21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/bundled-angular-compiler': 21.2.0 + '@angular-eslint/template-parser': 21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/utils': 21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) aria-query: 5.3.2 axobject-query: 4.1.0 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 - '@angular-eslint/eslint-plugin@21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/eslint-plugin@21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 21.1.0 - '@angular-eslint/utils': 21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/bundled-angular-compiler': 21.2.0 + '@angular-eslint/utils': 21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 - '@angular-eslint/schematics@21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/schematics@21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.4(chokidar@4.0.3) - '@angular-eslint/eslint-plugin': 21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin-template': 21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular/cli': 21.0.4(@types/node@25.0.3)(chokidar@4.0.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular-devkit/schematics': 21.1.4(chokidar@5.0.0) + '@angular-eslint/eslint-plugin': 21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular/cli': 21.1.4(@types/node@25.2.3)(chokidar@5.0.0) ignore: 7.0.5 semver: 7.7.3 strip-json-comments: 3.1.1 @@ -9114,63 +9103,63 @@ snapshots: - eslint - typescript - '@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 21.1.0 + '@angular-eslint/bundled-angular-compiler': 21.2.0 eslint: 9.39.2(jiti@2.6.1) - eslint-scope: 9.0.0 + eslint-scope: 9.1.0 typescript: 5.9.3 - '@angular-eslint/utils@21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@angular-eslint/utils@21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@angular-eslint/bundled-angular-compiler': 21.1.0 - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/bundled-angular-compiler': 21.2.0 + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 - '@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': + '@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) tslib: 2.8.1 - '@angular/build@21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.0.3)(chokidar@4.0.3)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.0)(tslib@2.8.1)(typescript@5.9.3)': + '@angular/build@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@types/node@25.2.3)(chokidar@5.0.0)(jiti@2.6.1)(less@4.4.2)(postcss@8.5.6)(terser@5.44.1)(tslib@2.8.1)(typescript@5.9.3)': dependencies: '@ampproject/remapping': 2.3.0 - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) - '@angular/compiler': 21.0.6 - '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) - '@babel/core': 7.28.4 + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + '@angular/compiler': 21.1.4 + '@angular/compiler-cli': 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) + '@babel/core': 7.28.5 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-split-export-declaration': 7.24.7 - '@inquirer/confirm': 5.1.19(@types/node@25.0.3) - '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.2.2(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0)) + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@vitejs/plugin-basic-ssl': 2.1.0(vite@7.3.0(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.44.1)) beasties: 0.3.5 browserslist: 4.28.1 - esbuild: 0.26.0 + esbuild: 0.27.2 https-proxy-agent: 7.0.6 istanbul-lib-instrument: 6.0.3 jsonc-parser: 3.3.1 listr2: 9.0.5 - magic-string: 0.30.19 + magic-string: 0.30.21 mrmime: 2.0.1 parse5-html-rewriting-stream: 8.0.0 picomatch: 4.0.3 - piscina: 5.1.3 - rolldown: 1.0.0-beta.47 - sass: 1.93.2 + piscina: 5.1.4 + rolldown: 1.0.0-beta.58 + sass: 1.97.1 semver: 7.7.3 source-map-support: 0.5.21 tinyglobby: 0.2.15 tslib: 2.8.1 typescript: 5.9.3 - undici: 7.16.0 - vite: 7.2.2(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0) - watchpack: 2.4.4 + undici: 7.20.0 + vite: 7.3.0(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.44.1) + watchpack: 2.5.0 optionalDependencies: - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) less: 4.4.2 - lmdb: 3.4.3 + lmdb: 3.4.4 postcss: 8.5.6 transitivePeerDependencies: - '@types/node' @@ -9185,56 +9174,57 @@ snapshots: - tsx - yaml - '@angular/cdk@21.0.5(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2)': + '@angular/cdk@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) parse5: 8.0.0 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3)': + '@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0)': dependencies: - '@angular-devkit/architect': 0.2100.4(chokidar@4.0.3) - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.4(chokidar@4.0.3) - '@inquirer/prompts': 7.9.0(@types/node@25.0.3) - '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.9.0(@types/node@25.0.3))(@types/node@25.0.3)(listr2@9.0.5) - '@modelcontextprotocol/sdk': 1.24.0(zod@4.1.13) - '@schematics/angular': 21.0.4(chokidar@4.0.3) + '@angular-devkit/architect': 0.2101.4(chokidar@5.0.0) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular-devkit/schematics': 21.1.4(chokidar@5.0.0) + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) + '@listr2/prompt-adapter-inquirer': 3.0.5(@inquirer/prompts@7.10.1(@types/node@25.2.3))(@types/node@25.2.3)(listr2@9.0.5) + '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.5) + '@schematics/angular': 21.1.4(chokidar@5.0.0) '@yarnpkg/lockfile': 1.1.0 - algoliasearch: 5.40.1 - ini: 5.0.0 + algoliasearch: 5.46.2 + ini: 6.0.0 jsonc-parser: 3.3.1 listr2: 9.0.5 - npm-package-arg: 13.0.1 - pacote: 21.0.3 + npm-package-arg: 13.0.2 + pacote: 21.0.4 parse5-html-rewriting-stream: 8.0.0 resolve: 1.22.11 semver: 7.7.3 yargs: 18.0.0 - zod: 4.1.13 + zod: 4.3.5 transitivePeerDependencies: - '@cfworker/json-schema' - '@types/node' - chokidar - supports-color - '@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2)': + '@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2)': dependencies: - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3)': + '@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3)': dependencies: - '@angular/compiler': 21.0.6 - '@babel/core': 7.28.4 + '@angular/compiler': 21.1.4 + '@babel/core': 7.28.5 '@jridgewell/sourcemap-codec': 1.5.5 - chokidar: 4.0.3 + chokidar: 5.0.0 convert-source-map: 1.9.0 reflect-metadata: 0.2.2 - semver: 7.7.3 + semver: 7.7.4 tslib: 2.8.1 yargs: 18.0.0 optionalDependencies: @@ -9242,60 +9232,60 @@ snapshots: transitivePeerDependencies: - supports-color - '@angular/compiler@21.0.6': + '@angular/compiler@21.1.4': dependencies: tslib: 2.8.1 - '@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)': + '@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)': dependencies: rxjs: 7.8.2 tslib: 2.8.1 optionalDependencies: - '@angular/compiler': 21.0.6 + '@angular/compiler': 21.1.4 zone.js: 0.16.0 - '@angular/forms@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': + '@angular/forms@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) '@standard-schema/spec': 1.1.0 rxjs: 7.8.2 tslib: 2.8.1 - '@angular/language-service@21.0.6': {} + '@angular/language-service@21.1.4': {} - '@angular/material@21.0.5(fa41ef7fb045ee86255967d043d95fbe)': + '@angular/material@21.1.4(243a22586a6c3cb85765d3835a5d9640)': dependencies: - '@angular/cdk': 21.0.5(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/forms': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/cdk': 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/forms': 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) rxjs: 7.8.2 tslib: 2.8.1 - '@angular/platform-browser-dynamic@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))': + '@angular/platform-browser-dynamic@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/compiler': 21.0.6 - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/compiler': 21.1.4 + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) tslib: 2.8.1 - '@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': + '@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) tslib: 2.8.1 optionalDependencies: - '@angular/animations': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/animations': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) - '@angular/router@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': + '@angular/router@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) rxjs: 7.8.2 tslib: 2.8.1 @@ -9309,25 +9299,25 @@ snapshots: '@csstools/css-tokenizer': 3.0.4 lru-cache: 10.4.3 - '@babel/code-frame@7.27.1': + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.0': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -9337,17 +9327,37 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.28.5': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -9357,59 +9367,90 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/generator@7.28.5': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.5 + '@babel/compat-data': 7.29.0 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.4)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.5) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.4)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.4)': + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 debug: 4.4.3 lodash.debounce: 4.0.8 resolve: 1.22.11 @@ -9420,70 +9461,97 @@ snapshots: '@babel/helper-member-expression-to-functions@7.28.5': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.4)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.6 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.3 - '@babel/traverse': 7.28.5 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.4)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-split-export-declaration@7.24.7': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@babel/helper-string-parser@7.27.1': {} @@ -9491,655 +9559,1151 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.28.3': + '@babel/helper-wrap-function@7.28.6': dependencies: - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helpers@7.28.4': + '@babel/helpers@7.28.6': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.28.5': + '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.4)': - dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.4)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color - '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.4)': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.4)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.4) - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/template': 7.27.2 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.4)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.5) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.4(@babel/core@7.28.4)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) - '@babel/traverse': 7.28.5 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.4) + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.5(@babel/core@7.28.4)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - transitivePeerDependencies: - - supports-color + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.4)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.28.4(@babel/core@7.28.4)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-runtime@7.28.3(@babel/core@7.28.4)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.4) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) - semver: 6.3.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.4) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.28.3(@babel/core@7.28.4)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.4 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.4) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.4) - '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.4) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.4) - '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-block-scoping': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.4) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.28.4) - '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.4) - '@babel/plugin-transform-exponentiation-operator': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-logical-assignment-operators': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-object-rest-spread': 7.28.4(@babel/core@7.28.4) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-optional-chaining': 7.28.5(@babel/core@7.28.4) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.4) - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-regenerator': 7.28.4(@babel/core@7.28.4) - '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.4) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.4) - babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.4) - babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.4) - babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.47.0 - semver: 6.3.1 + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.4)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.6)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/types': 7.28.5 - esutils: 2.0.3 - - '@babel/runtime@7.28.4': {} + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/template@7.27.2': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.5)': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color - '@babel/traverse@7.28.5': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - debug: 4.4.3 + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/types@7.28.5': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.28.5)': dependencies: - '@babel/helper-string-parser': 7.27.1 + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color - '@bcoe/v8-coverage@0.2.3': {} - - '@borewit/text-codec@0.1.1': {} - - '@cacheable/utils@2.3.2': + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.28.6)': dependencies: - hashery: 1.3.0 - keyv: 5.5.5 - + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@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-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.6)': + 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-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-runtime@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.5) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.5) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.28.5(@babel/core@7.28.5)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.28.5 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.5) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.5) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.28.5) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.5) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.28.5) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.5) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.5) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.28.5) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.5) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.5) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.5) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.5) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.5) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-env@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.6) + '@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-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) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.28.6) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.6) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.6) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.6) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.6) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/runtime@7.28.4': {} + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@borewit/text-codec@0.2.1': {} + + '@cacheable/utils@2.3.4': + dependencies: + hashery: 1.4.0 + keyv: 5.6.0 + '@colors/colors@1.5.0': optional: true - '@compodoc/compodoc@1.1.32(@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.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))': dependencies: - '@angular-devkit/schematics': 20.3.4(chokidar@4.0.3) - '@babel/core': 7.28.4 - '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.4) - '@babel/preset-env': 7.28.3(@babel/core@7.28.4) + '@angular-devkit/schematics': 21.1.0(chokidar@5.0.0) + '@babel/core': 7.28.6 + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/preset-env': 7.28.6(@babel/core@7.28.6) '@compodoc/live-server': 1.2.3 '@compodoc/ngd-transformer': 2.1.3 '@polka/send-type': 0.5.2 - body-parser: 2.2.1 + body-parser: 2.2.2 bootstrap.native: 5.1.6 cheerio: 1.1.2 - chokidar: 4.0.3 + chokidar: 5.0.0 colors: 1.4.0 - commander: 14.0.2 + commander: 14.0.3 cosmiconfig: 9.0.0(typescript@5.9.3) decache: 4.6.2 es6-shim: 0.35.8 fancy-log: 2.0.0 fast-glob: 3.3.3 fs-extra: 11.3.3 - glob: 11.1.0 + glob: 13.0.3 handlebars: 4.7.8 html-entities: 2.6.0 - i18next: 25.5.3(typescript@5.9.3) + i18next: 25.7.4(typescript@5.9.3) json5: 2.2.3 - lodash: 4.17.21 + lodash: 4.17.23 loglevel: 1.9.2 loglevel-plugin-prefix: 0.8.4 lunr: 2.3.9 @@ -10151,10 +10715,10 @@ snapshots: picocolors: 1.1.1 polka: 0.5.2 prismjs: 1.30.0 - semver: 7.7.3 + semver: 7.7.4 sirv: 3.0.2 svg-pan-zoom: 3.6.2 - tablesort: 5.6.0 + 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)) @@ -10172,7 +10736,7 @@ snapshots: chokidar: 3.6.0 colors: 1.4.0 connect: 3.7.0 - cors: 2.8.5 + cors: 2.8.6 event-stream: 4.0.1 faye-websocket: 0.11.4 http-auth: 4.1.9 @@ -10182,7 +10746,7 @@ snapshots: open: 8.4.0 proxy-middleware: 0.15.0 send: 1.2.1 - serve-index: 1.9.1 + serve-index: 1.9.2 transitivePeerDependencies: - supports-color @@ -10235,247 +10799,102 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.1': + '@emnapi/core@1.8.1': dependencies: + '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.12': + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.26.0': + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 optional: true '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.25.12': - optional: true - - '@esbuild/android-arm64@0.26.0': - optional: true - '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.25.12': - optional: true - - '@esbuild/android-arm@0.26.0': - optional: true - '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.25.12': - optional: true - - '@esbuild/android-x64@0.26.0': - optional: true - '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.25.12': - optional: true - - '@esbuild/darwin-arm64@0.26.0': - optional: true - '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.25.12': - optional: true - - '@esbuild/darwin-x64@0.26.0': - optional: true - '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.25.12': - optional: true - - '@esbuild/freebsd-arm64@0.26.0': - optional: true - '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.25.12': - optional: true - - '@esbuild/freebsd-x64@0.26.0': - optional: true - '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.25.12': - optional: true - - '@esbuild/linux-arm64@0.26.0': - optional: true - '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.25.12': - optional: true - - '@esbuild/linux-arm@0.26.0': - optional: true - '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.25.12': - optional: true - - '@esbuild/linux-ia32@0.26.0': - optional: true - '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.25.12': - optional: true - - '@esbuild/linux-loong64@0.26.0': - optional: true - '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.25.12': - optional: true - - '@esbuild/linux-mips64el@0.26.0': - optional: true - '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.25.12': - optional: true - - '@esbuild/linux-ppc64@0.26.0': - optional: true - '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.25.12': - optional: true - - '@esbuild/linux-riscv64@0.26.0': - optional: true - '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.25.12': - optional: true - - '@esbuild/linux-s390x@0.26.0': - optional: true - '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.25.12': - optional: true - - '@esbuild/linux-x64@0.26.0': - optional: true - '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.25.12': - optional: true - - '@esbuild/netbsd-arm64@0.26.0': - optional: true - '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.12': - optional: true - - '@esbuild/netbsd-x64@0.26.0': - optional: true - '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.25.12': - optional: true - - '@esbuild/openbsd-arm64@0.26.0': - optional: true - '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.12': - optional: true - - '@esbuild/openbsd-x64@0.26.0': - optional: true - '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.25.12': - optional: true - - '@esbuild/openharmony-arm64@0.26.0': - optional: true - '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.25.12': - optional: true - - '@esbuild/sunos-x64@0.26.0': - optional: true - '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.25.12': - optional: true - - '@esbuild/win32-arm64@0.26.0': - optional: true - '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.25.12': - optional: true - - '@esbuild/win32-ia32@0.26.0': - optional: true - '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.25.12': - optional: true - - '@esbuild/win32-x64@0.26.0': - optional: true - '@esbuild/win32-x64@0.27.2': optional: true @@ -10484,11 +10903,16 @@ snapshots: eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.0.0(eslint@9.39.2(jiti@2.6.1))': + '@eslint/compat@2.0.2(eslint@9.39.2(jiti@2.6.1))': dependencies: - '@eslint/core': 1.0.0 + '@eslint/core': 1.1.0 optionalDependencies: eslint: 9.39.2(jiti@2.6.1) @@ -10508,7 +10932,7 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@1.0.0': + '@eslint/core@1.1.0': dependencies: '@types/json-schema': 7.0.15 @@ -10535,27 +10959,31 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 - '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': + '@fortawesome/angular-fontawesome@4.0.0(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@fortawesome/fontawesome-svg-core': 7.1.0 + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@fortawesome/fontawesome-svg-core': 7.2.0 tslib: 2.8.1 - '@fortawesome/fontawesome-common-types@7.1.0': {} + '@fortawesome/fontawesome-common-types@7.2.0': {} - '@fortawesome/fontawesome-svg-core@7.1.0': + '@fortawesome/fontawesome-svg-core@7.2.0': dependencies: - '@fortawesome/fontawesome-common-types': 7.1.0 + '@fortawesome/fontawesome-common-types': 7.2.0 - '@fortawesome/free-brands-svg-icons@7.1.0': + '@fortawesome/free-brands-svg-icons@7.2.0': dependencies: - '@fortawesome/fontawesome-common-types': 7.1.0 + '@fortawesome/fontawesome-common-types': 7.2.0 - '@fortawesome/free-solid-svg-icons@7.1.0': + '@fortawesome/free-solid-svg-icons@7.2.0': dependencies: - '@fortawesome/fontawesome-common-types': 7.1.0 + '@fortawesome/fontawesome-common-types': 7.2.0 + + '@golevelup/ts-jest@1.2.1': {} - '@golevelup/ts-jest@1.1.1': {} + '@hono/node-server@1.19.9(hono@4.11.9)': + dependencies: + hono: 4.11.9 '@humanfs/core@0.19.1': {} @@ -10570,171 +10998,143 @@ snapshots: '@inquirer/ansi@1.0.2': {} - '@inquirer/checkbox@4.3.2(@types/node@25.0.3)': + '@inquirer/checkbox@4.3.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 - - '@inquirer/confirm@5.1.19(@types/node@25.0.3)': - dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) - optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/confirm@5.1.21(@types/node@25.0.3)': + '@inquirer/confirm@5.1.21(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/core@10.3.2(@types/node@25.0.3)': + '@inquirer/core@10.3.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) 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.0.3 + '@types/node': 25.2.3 - '@inquirer/editor@4.2.23(@types/node@25.0.3)': + '@inquirer/editor@4.2.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/external-editor': 1.0.3(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/external-editor': 1.0.3(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/expand@4.0.23(@types/node@25.0.3)': + '@inquirer/expand@4.0.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/external-editor@1.0.3(@types/node@25.0.3)': + '@inquirer/external-editor@1.0.3(@types/node@25.2.3)': dependencies: chardet: 2.1.1 - iconv-lite: 0.7.1 + iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@inquirer/figures@1.0.15': {} - '@inquirer/input@4.3.1(@types/node@25.0.3)': + '@inquirer/input@4.3.1(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/number@3.0.23(@types/node@25.0.3)': + '@inquirer/number@3.0.23(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/password@4.0.23(@types/node@25.0.3)': + '@inquirer/password@4.0.23(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 - - '@inquirer/prompts@7.10.1(@types/node@25.0.3)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.0.3) - '@inquirer/confirm': 5.1.21(@types/node@25.0.3) - '@inquirer/editor': 4.2.23(@types/node@25.0.3) - '@inquirer/expand': 4.0.23(@types/node@25.0.3) - '@inquirer/input': 4.3.1(@types/node@25.0.3) - '@inquirer/number': 3.0.23(@types/node@25.0.3) - '@inquirer/password': 4.0.23(@types/node@25.0.3) - '@inquirer/rawlist': 4.1.11(@types/node@25.0.3) - '@inquirer/search': 3.2.2(@types/node@25.0.3) - '@inquirer/select': 4.4.2(@types/node@25.0.3) + '@types/node': 25.2.3 + + '@inquirer/prompts@7.10.1(@types/node@25.2.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.2.3) + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@inquirer/editor': 4.2.23(@types/node@25.2.3) + '@inquirer/expand': 4.0.23(@types/node@25.2.3) + '@inquirer/input': 4.3.1(@types/node@25.2.3) + '@inquirer/number': 3.0.23(@types/node@25.2.3) + '@inquirer/password': 4.0.23(@types/node@25.2.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.2.3) + '@inquirer/search': 3.2.2(@types/node@25.2.3) + '@inquirer/select': 4.4.2(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 - - '@inquirer/prompts@7.3.2(@types/node@25.0.3)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.0.3) - '@inquirer/confirm': 5.1.21(@types/node@25.0.3) - '@inquirer/editor': 4.2.23(@types/node@25.0.3) - '@inquirer/expand': 4.0.23(@types/node@25.0.3) - '@inquirer/input': 4.3.1(@types/node@25.0.3) - '@inquirer/number': 3.0.23(@types/node@25.0.3) - '@inquirer/password': 4.0.23(@types/node@25.0.3) - '@inquirer/rawlist': 4.1.11(@types/node@25.0.3) - '@inquirer/search': 3.2.2(@types/node@25.0.3) - '@inquirer/select': 4.4.2(@types/node@25.0.3) + '@types/node': 25.2.3 + + '@inquirer/prompts@7.3.2(@types/node@25.2.3)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.2.3) + '@inquirer/confirm': 5.1.21(@types/node@25.2.3) + '@inquirer/editor': 4.2.23(@types/node@25.2.3) + '@inquirer/expand': 4.0.23(@types/node@25.2.3) + '@inquirer/input': 4.3.1(@types/node@25.2.3) + '@inquirer/number': 3.0.23(@types/node@25.2.3) + '@inquirer/password': 4.0.23(@types/node@25.2.3) + '@inquirer/rawlist': 4.1.11(@types/node@25.2.3) + '@inquirer/search': 3.2.2(@types/node@25.2.3) + '@inquirer/select': 4.4.2(@types/node@25.2.3) optionalDependencies: - '@types/node': 25.0.3 - - '@inquirer/prompts@7.9.0(@types/node@25.0.3)': - dependencies: - '@inquirer/checkbox': 4.3.2(@types/node@25.0.3) - '@inquirer/confirm': 5.1.21(@types/node@25.0.3) - '@inquirer/editor': 4.2.23(@types/node@25.0.3) - '@inquirer/expand': 4.0.23(@types/node@25.0.3) - '@inquirer/input': 4.3.1(@types/node@25.0.3) - '@inquirer/number': 3.0.23(@types/node@25.0.3) - '@inquirer/password': 4.0.23(@types/node@25.0.3) - '@inquirer/rawlist': 4.1.11(@types/node@25.0.3) - '@inquirer/search': 3.2.2(@types/node@25.0.3) - '@inquirer/select': 4.4.2(@types/node@25.0.3) - optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/rawlist@4.1.11(@types/node@25.0.3)': + '@inquirer/rawlist@4.1.11(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/search@3.2.2(@types/node@25.0.3)': + '@inquirer/search@3.2.2(@types/node@25.2.3)': dependencies: - '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/select@4.4.2(@types/node@25.0.3)': + '@inquirer/select@4.4.2(@types/node@25.2.3)': dependencies: '@inquirer/ansi': 1.0.2 - '@inquirer/core': 10.3.2(@types/node@25.0.3) + '@inquirer/core': 10.3.2(@types/node@25.2.3) '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@inquirer/type@3.0.10(@types/node@25.0.3)': + '@inquirer/type@3.0.10(@types/node@25.2.3)': optionalDependencies: - '@types/node': 25.0.3 - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 + '@types/node': 25.2.3 '@isaacs/cliui@8.0.2': dependencies: @@ -10745,6 +11145,8 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@isaacs/cliui@9.0.0': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -10762,13 +11164,13 @@ snapshots: '@jest/console@30.2.0': dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 jest-message-util: 30.2.0 jest-util: 30.2.0 slash: 3.0.0 - '@jest/core@30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3))': + '@jest/core@30.2.0(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3))': dependencies: '@jest/console': 30.2.0 '@jest/pattern': 30.0.1 @@ -10776,14 +11178,14 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 4.3.1 exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-haste-map: 30.2.0 jest-message-util: 30.2.0 jest-regex-util: 30.0.1 @@ -10812,7 +11214,7 @@ snapshots: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-mock: 30.2.0 jest-util: 30.2.0 jsdom: 26.1.0 @@ -10821,7 +11223,7 @@ snapshots: dependencies: '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-mock: 30.2.0 '@jest/expect-utils@30.2.0': @@ -10839,7 +11241,7 @@ snapshots: dependencies: '@jest/types': 30.2.0 '@sinonjs/fake-timers': 13.0.5 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-message-util: 30.2.0 jest-mock: 30.2.0 jest-util: 30.2.0 @@ -10857,7 +11259,7 @@ snapshots: '@jest/pattern@30.0.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-regex-util: 30.0.1 '@jest/reporters@30.2.0': @@ -10868,7 +11270,7 @@ snapshots: '@jest/transform': 30.2.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 collect-v8-coverage: 1.0.3 exit-x: 0.2.2 @@ -10921,7 +11323,7 @@ snapshots: '@jest/transform@30.2.0': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/types': 30.2.0 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 @@ -10945,7 +11347,7 @@ snapshots: '@jest/schemas': 30.0.5 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/yargs': 17.0.35 chalk: 4.1.2 @@ -10982,14 +11384,82 @@ snapshots: dependencies: tslib: 2.8.1 + '@jsonjoy.com/base64@17.67.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + '@jsonjoy.com/buffers@1.2.1(tslib@2.8.1)': dependencies: tslib: 2.8.1 + '@jsonjoy.com/buffers@17.67.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + '@jsonjoy.com/codegen@1.0.0(tslib@2.8.1)': dependencies: tslib: 2.8.1 + '@jsonjoy.com/codegen@17.67.0(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/fs-core@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + + '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + + '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(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 + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) @@ -11002,71 +11472,97 @@ snapshots: tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/json-pack@17.67.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/base64': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/codegen': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/json-pointer': 17.67.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) + hyperdyperid: 1.2.0 + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/json-pointer@1.0.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/json-pointer@17.67.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/util': 17.67.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/util@1.9.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 1.2.1(tslib@2.8.1) '@jsonjoy.com/codegen': 1.0.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/util@17.67.0(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 17.67.0(tslib@2.8.1) + '@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.9.0(@types/node@25.0.3))(@types/node@25.0.3)(listr2@9.0.5)': + '@listr2/prompt-adapter-inquirer@3.0.5(@inquirer/prompts@7.10.1(@types/node@25.2.3))(@types/node@25.2.3)(listr2@9.0.5)': dependencies: - '@inquirer/prompts': 7.9.0(@types/node@25.0.3) - '@inquirer/type': 3.0.10(@types/node@25.0.3) + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) + '@inquirer/type': 3.0.10(@types/node@25.2.3) listr2: 9.0.5 transitivePeerDependencies: - '@types/node' - '@lmdb/lmdb-darwin-arm64@3.4.3': + '@lmdb/lmdb-darwin-arm64@3.4.4': optional: true - '@lmdb/lmdb-darwin-x64@3.4.3': + '@lmdb/lmdb-darwin-x64@3.4.4': optional: true - '@lmdb/lmdb-linux-arm64@3.4.3': + '@lmdb/lmdb-linux-arm64@3.4.4': optional: true - '@lmdb/lmdb-linux-arm@3.4.3': + '@lmdb/lmdb-linux-arm@3.4.4': optional: true - '@lmdb/lmdb-linux-x64@3.4.3': + '@lmdb/lmdb-linux-x64@3.4.4': optional: true - '@lmdb/lmdb-win32-arm64@3.4.3': + '@lmdb/lmdb-win32-arm64@3.4.4': optional: true - '@lmdb/lmdb-win32-x64@3.4.3': + '@lmdb/lmdb-win32-x64@3.4.4': optional: true '@lukeed/csprng@1.1.0': {} '@material-design-icons/font@0.14.15': {} - '@modelcontextprotocol/sdk@1.24.0(zod@4.1.13)': + '@modelcontextprotocol/sdk@1.26.0(zod@4.3.5)': dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + '@hono/node-server': 1.19.9(hono@4.11.9) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) content-type: 1.0.5 - cors: 2.8.5 + cors: 2.8.6 cross-spawn: 7.0.6 eventsource: 3.0.7 eventsource-parser: 3.0.6 express: 5.2.1 - express-rate-limit: 7.5.1(express@5.2.1) + express-rate-limit: 8.2.1(express@5.2.1) + hono: 4.11.9 jose: 6.1.3 + json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 raw-body: 3.0.2 - zod: 4.1.13 - zod-to-json-schema: 3.25.0(zod@4.1.13) + zod: 4.3.5 + zod-to-json-schema: 3.25.1(zod@4.3.5) transitivePeerDependencies: - supports-color @@ -11167,40 +11663,40 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.0': + '@napi-rs/wasm-runtime@1.1.1': dependencies: - '@emnapi/core': 1.7.1 - '@emnapi/runtime': 1.7.1 + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs/cache-manager@3.0.1(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(cache-manager@7.2.7)(keyv@5.5.5)(rxjs@7.8.2)': + '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(cache-manager@7.2.8)(keyv@5.6.0)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cache-manager: 7.2.7 - keyv: 5.5.5 + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(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.14(@types/node@25.0.3)': + '@nestjs/cli@11.0.16(@types/node@25.2.3)': dependencies: '@angular-devkit/core': 19.2.19(chokidar@4.0.3) '@angular-devkit/schematics': 19.2.19(chokidar@4.0.3) - '@angular-devkit/schematics-cli': 19.2.19(@types/node@25.0.3)(chokidar@4.0.3) - '@inquirer/prompts': 7.10.1(@types/node@25.0.3) + '@angular-devkit/schematics-cli': 19.2.19(@types/node@25.2.3)(chokidar@4.0.3) + '@inquirer/prompts': 7.10.1(@types/node@25.2.3) '@nestjs/schematics': 11.0.9(chokidar@4.0.3)(typescript@5.9.3) ansis: 4.2.0 chokidar: 4.0.3 cli-table3: 0.6.5 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.103.0) + fork-ts-checker-webpack-plugin: 9.1.0(typescript@5.9.3)(webpack@5.104.1) glob: 13.0.0 node-emoji: 1.11.0 ora: 5.4.1 tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.2.0 typescript: 5.9.3 - webpack: 5.103.0 + webpack: 5.104.1 webpack-node-externals: 3.0.0 transitivePeerDependencies: - '@types/node' @@ -11208,9 +11704,9 @@ snapshots: - uglify-js - webpack-cli - '@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - file-type: 21.1.0 + file-type: 21.3.0 iterare: 1.2.1 load-esm: 1.0.3 reflect-metadata: 0.2.2 @@ -11222,17 +11718,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/config@4.0.2(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': + '@nestjs/config@4.0.3(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - dotenv: 16.4.7 - dotenv-expand: 12.0.1 - lodash: 4.17.21 + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + dotenv: 17.2.3 + dotenv-expand: 12.0.3 + lodash: 4.17.23 rxjs: 7.8.2 - '@nestjs/core@11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/core@11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nuxt/opencollective': 0.4.1 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -11242,38 +11738,38 @@ snapshots: tslib: 2.8.1 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) - '@nestjs/websockets': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-socket.io@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/platform-express': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) + '@nestjs/websockets': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-socket.io@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/platform-express@11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)': + '@nestjs/platform-express@11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cors: 2.8.5 - express: 5.1.0 + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cors: 2.8.6 + express: 5.2.1 multer: 2.0.2 path-to-regexp: 8.3.0 tslib: 2.8.1 transitivePeerDependencies: - supports-color - '@nestjs/platform-socket.io@11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.9)(rxjs@7.8.2)': + '@nestjs/platform-socket.io@11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.13)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/websockets': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-socket.io@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/websockets': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-socket.io@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) rxjs: 7.8.2 - socket.io: 4.8.1 + socket.io: 4.8.3 tslib: 2.8.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@nestjs/schedule@6.1.0(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)': + '@nestjs/schedule@6.1.1(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) - cron: 4.3.5 + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(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)': dependencies: @@ -11286,58 +11782,58 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/serve-static@5.0.4(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(express@5.2.1)': + '@nestjs/serve-static@5.0.4(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(express@5.2.1)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) path-to-regexp: 8.3.0 optionalDependencies: express: 5.2.1 - '@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)': + '@nestjs/testing@11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-express@11.1.13)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 optionalDependencies: - '@nestjs/platform-express': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9) + '@nestjs/platform-express': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13) - '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.16.3)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))': + '@nestjs/typeorm@11.0.0(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(pg@8.18.0)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2) reflect-metadata: 0.2.2 rxjs: 7.8.2 - typeorm: 0.3.28(pg@8.16.3)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + typeorm: 0.3.28(pg@8.18.0)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) - '@nestjs/websockets@11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-socket.io@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)': + '@nestjs/websockets@11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.13)(@nestjs/platform-socket.io@11.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.2)': dependencies: - '@nestjs/common': 11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) - '@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/common': 11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.13)(@nestjs/websockets@11.1.13)(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.9(@nestjs/common@11.1.9(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.9)(rxjs@7.8.2) + '@nestjs/platform-socket.io': 11.1.13(@nestjs/common@11.1.13(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/websockets@11.1.13)(rxjs@7.8.2) - '@ngtools/webpack@21.0.4(@angular/compiler-cli@21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.104.0(esbuild@0.26.0))': + '@ngtools/webpack@21.1.4(@angular/compiler-cli@21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3))(typescript@5.9.3)(webpack@5.105.0(esbuild@0.27.2))': dependencies: - '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) + '@angular/compiler-cli': 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) typescript: 5.9.3 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) - '@ngx-translate/core@17.0.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': + '@ngx-translate/core@17.0.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) tslib: 2.8.1 - '@ngx-translate/http-loader@17.0.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))': + '@ngx-translate/http-loader@17.0.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))': dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) tslib: 2.8.1 '@noble/hashes@1.8.0': {} @@ -11352,57 +11848,53 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + fastq: 1.20.1 '@npmcli/agent@4.0.0': dependencies: agent-base: 7.1.4 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 - lru-cache: 11.2.4 + lru-cache: 11.2.6 socks-proxy-agent: 8.0.5 transitivePeerDependencies: - supports-color '@npmcli/fs@5.0.0': dependencies: - semver: 7.7.3 + semver: 7.7.4 '@npmcli/git@7.0.1': dependencies: '@npmcli/promise-spawn': 9.0.1 ini: 6.0.0 - lru-cache: 11.2.4 + lru-cache: 11.2.6 npm-pick-manifest: 11.0.3 proc-log: 6.1.0 promise-retry: 2.0.1 - semver: 7.7.3 - which: 6.0.0 + semver: 7.7.4 + which: 6.0.1 - '@npmcli/installed-package-contents@3.0.0': + '@npmcli/installed-package-contents@4.0.0': dependencies: - npm-bundled: 4.0.0 - npm-normalize-package-bin: 4.0.0 + npm-bundled: 5.0.0 + npm-normalize-package-bin: 5.0.0 '@npmcli/node-gyp@5.0.0': {} '@npmcli/package-json@7.0.4': dependencies: '@npmcli/git': 7.0.1 - glob: 13.0.0 + glob: 13.0.3 hosted-git-info: 9.0.2 json-parse-even-better-errors: 5.0.0 proc-log: 6.1.0 - semver: 7.7.3 + semver: 7.7.4 validate-npm-package-license: 3.0.4 - '@npmcli/promise-spawn@8.0.3': - dependencies: - which: 5.0.0 - '@npmcli/promise-spawn@9.0.1': dependencies: - which: 6.0.0 + which: 6.0.1 '@npmcli/redact@4.0.0': {} @@ -11411,9 +11903,9 @@ snapshots: '@npmcli/node-gyp': 5.0.0 '@npmcli/package-json': 7.0.4 '@npmcli/promise-spawn': 9.0.1 - node-gyp: 12.1.0 + node-gyp: 12.2.0 proc-log: 6.1.0 - which: 6.0.0 + which: 6.0.1 transitivePeerDependencies: - supports-color @@ -11421,240 +11913,321 @@ snapshots: dependencies: consola: 3.4.2 - '@nx/nx-darwin-arm64@22.3.3': + '@nx/nx-darwin-arm64@22.5.1': optional: true - '@nx/nx-darwin-x64@22.3.3': + '@nx/nx-darwin-x64@22.5.1': optional: true - '@nx/nx-linux-x64-gnu@22.3.3': + '@nx/nx-linux-x64-gnu@22.5.1': optional: true - '@nx/nx-win32-x64-msvc@22.3.3': + '@nx/nx-win32-x64-msvc@22.5.1': optional: true '@opentelemetry/api@1.9.0': {} - '@oxc-project/types@0.96.0': {} + '@oxc-project/types@0.106.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: '@noble/hashes': 1.8.0 - '@parcel/watcher-android-arm64@2.5.1': + '@parcel/watcher-android-arm64@2.5.6': optional: true - '@parcel/watcher-darwin-arm64@2.5.1': + '@parcel/watcher-darwin-arm64@2.5.6': optional: true - '@parcel/watcher-darwin-x64@2.5.1': + '@parcel/watcher-darwin-x64@2.5.6': optional: true - '@parcel/watcher-freebsd-x64@2.5.1': + '@parcel/watcher-freebsd-x64@2.5.6': optional: true - '@parcel/watcher-linux-arm-glibc@2.5.1': + '@parcel/watcher-linux-arm-glibc@2.5.6': optional: true - '@parcel/watcher-linux-arm-musl@2.5.1': + '@parcel/watcher-linux-arm-musl@2.5.6': optional: true - '@parcel/watcher-linux-arm64-glibc@2.5.1': + '@parcel/watcher-linux-arm64-glibc@2.5.6': optional: true - '@parcel/watcher-linux-arm64-musl@2.5.1': + '@parcel/watcher-linux-arm64-musl@2.5.6': optional: true - '@parcel/watcher-linux-x64-glibc@2.5.1': + '@parcel/watcher-linux-x64-glibc@2.5.6': optional: true - '@parcel/watcher-linux-x64-musl@2.5.1': + '@parcel/watcher-linux-x64-musl@2.5.6': optional: true - '@parcel/watcher-win32-arm64@2.5.1': + '@parcel/watcher-win32-arm64@2.5.6': optional: true - '@parcel/watcher-win32-ia32@2.5.1': + '@parcel/watcher-win32-ia32@2.5.6': optional: true - '@parcel/watcher-win32-x64@2.5.1': + '@parcel/watcher-win32-x64@2.5.6': optional: true - '@parcel/watcher@2.5.1': + '@parcel/watcher@2.5.6': dependencies: - detect-libc: 1.0.3 + detect-libc: 2.1.2 is-glob: 4.0.3 - micromatch: 4.0.8 node-addon-api: 7.1.1 + picomatch: 4.0.3 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.1 - '@parcel/watcher-darwin-arm64': 2.5.1 - '@parcel/watcher-darwin-x64': 2.5.1 - '@parcel/watcher-freebsd-x64': 2.5.1 - '@parcel/watcher-linux-arm-glibc': 2.5.1 - '@parcel/watcher-linux-arm-musl': 2.5.1 - '@parcel/watcher-linux-arm64-glibc': 2.5.1 - '@parcel/watcher-linux-arm64-musl': 2.5.1 - '@parcel/watcher-linux-x64-glibc': 2.5.1 - '@parcel/watcher-linux-x64-musl': 2.5.1 - '@parcel/watcher-win32-arm64': 2.5.1 - '@parcel/watcher-win32-ia32': 2.5.1 - '@parcel/watcher-win32-x64': 2.5.1 + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + + '@polka/send-type@0.5.2': {} + + '@polka/url@0.5.0': {} + + '@polka/url@1.0.0-next.29': {} + + '@redis/bloom@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/client@5.10.0': + dependencies: + cluster-key-slot: 1.1.2 + + '@redis/json@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/search@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@redis/time-series@5.10.0(@redis/client@5.10.0)': + dependencies: + '@redis/client': 5.10.0 + + '@rolldown/binding-android-arm64@1.0.0-beta.58': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-beta.58': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.58': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.58': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.58': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.58': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.58': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.58': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.58': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-beta.58': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.58': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.58': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.58': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.58': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true + + '@rollup/rollup-android-arm-eabi@4.57.1': optional: true - '@pkgjs/parseargs@0.11.0': + '@rollup/rollup-android-arm64@4.55.1': optional: true - '@pkgr/core@0.2.9': {} + '@rollup/rollup-android-arm64@4.57.1': + optional: true - '@playwright/test@1.57.0': - dependencies: - playwright: 1.57.0 + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true - '@polka/send-type@0.5.2': {} + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true - '@polka/url@0.5.0': {} + '@rollup/rollup-darwin-x64@4.55.1': + optional: true - '@polka/url@1.0.0-next.29': {} + '@rollup/rollup-darwin-x64@4.57.1': + optional: true - '@redis/bloom@5.10.0(@redis/client@5.10.0)': - dependencies: - '@redis/client': 5.10.0 + '@rollup/rollup-freebsd-arm64@4.55.1': + optional: true - '@redis/client@5.10.0': - dependencies: - cluster-key-slot: 1.1.2 + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true - '@redis/json@5.10.0(@redis/client@5.10.0)': - dependencies: - '@redis/client': 5.10.0 + '@rollup/rollup-freebsd-x64@4.55.1': + optional: true - '@redis/search@5.10.0(@redis/client@5.10.0)': - dependencies: - '@redis/client': 5.10.0 + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true - '@redis/time-series@5.10.0(@redis/client@5.10.0)': - dependencies: - '@redis/client': 5.10.0 + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + optional: true - '@rolldown/binding-android-arm64@1.0.0-beta.47': + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-beta.47': + '@rollup/rollup-linux-arm-musleabihf@4.55.1': optional: true - '@rolldown/binding-darwin-x64@1.0.0-beta.47': + '@rollup/rollup-linux-arm-musleabihf@4.57.1': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-beta.47': + '@rollup/rollup-linux-arm64-gnu@4.55.1': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.47': + '@rollup/rollup-linux-arm64-gnu@4.57.1': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.47': + '@rollup/rollup-linux-arm64-musl@4.55.1': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.47': + '@rollup/rollup-linux-arm64-musl@4.57.1': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.47': + '@rollup/rollup-linux-loong64-gnu@4.55.1': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-beta.47': + '@rollup/rollup-linux-loong64-gnu@4.57.1': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-beta.47': + '@rollup/rollup-linux-loong64-musl@4.55.1': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-beta.47': - dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@rollup/rollup-linux-loong64-musl@4.57.1': optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.47': + '@rollup/rollup-linux-ppc64-gnu@4.55.1': optional: true - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.47': + '@rollup/rollup-linux-ppc64-gnu@4.57.1': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.47': + '@rollup/rollup-linux-ppc64-musl@4.55.1': optional: true - '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.54.0': + '@rollup/rollup-linux-riscv64-gnu@4.55.1': optional: true - '@rollup/rollup-android-arm64@4.54.0': + '@rollup/rollup-linux-riscv64-gnu@4.57.1': optional: true - '@rollup/rollup-darwin-arm64@4.54.0': + '@rollup/rollup-linux-riscv64-musl@4.55.1': optional: true - '@rollup/rollup-darwin-x64@4.54.0': + '@rollup/rollup-linux-riscv64-musl@4.57.1': optional: true - '@rollup/rollup-freebsd-arm64@4.54.0': + '@rollup/rollup-linux-s390x-gnu@4.55.1': optional: true - '@rollup/rollup-freebsd-x64@4.54.0': + '@rollup/rollup-linux-s390x-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + '@rollup/rollup-linux-x64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.54.0': + '@rollup/rollup-linux-x64-gnu@4.57.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.54.0': + '@rollup/rollup-linux-x64-musl@4.55.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.54.0': + '@rollup/rollup-linux-x64-musl@4.57.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.54.0': + '@rollup/rollup-openbsd-x64@4.55.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.54.0': + '@rollup/rollup-openbsd-x64@4.57.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.54.0': + '@rollup/rollup-openharmony-arm64@4.55.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.54.0': + '@rollup/rollup-openharmony-arm64@4.57.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.54.0': + '@rollup/rollup-win32-arm64-msvc@4.55.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.54.0': + '@rollup/rollup-win32-arm64-msvc@4.57.1': optional: true - '@rollup/rollup-linux-x64-musl@4.54.0': + '@rollup/rollup-win32-ia32-msvc@4.55.1': optional: true - '@rollup/rollup-openharmony-arm64@4.54.0': + '@rollup/rollup-win32-ia32-msvc@4.57.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.54.0': + '@rollup/rollup-win32-x64-gnu@4.55.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.54.0': + '@rollup/rollup-win32-x64-gnu@4.57.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.54.0': + '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.54.0': + '@rollup/rollup-win32-x64-msvc@4.57.1': optional: true '@rtsao/scc@1.1.0': {} - '@schematics/angular@21.0.4(chokidar@4.0.3)': + '@schematics/angular@21.1.4(chokidar@5.0.0)': dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.4(chokidar@4.0.3) + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular-devkit/schematics': 21.1.4(chokidar@5.0.0) jsonc-parser: 3.3.1 transitivePeerDependencies: - chokidar @@ -11663,32 +12236,32 @@ snapshots: dependencies: '@sigstore/protobuf-specs': 0.5.0 - '@sigstore/core@3.0.0': {} + '@sigstore/core@3.1.0': {} '@sigstore/protobuf-specs@0.5.0': {} - '@sigstore/sign@4.0.1': + '@sigstore/sign@4.1.0': dependencies: '@sigstore/bundle': 4.0.0 - '@sigstore/core': 3.0.0 + '@sigstore/core': 3.1.0 '@sigstore/protobuf-specs': 0.5.0 make-fetch-happen: 15.0.3 - proc-log: 5.0.0 + proc-log: 6.1.0 promise-retry: 2.0.1 transitivePeerDependencies: - supports-color - '@sigstore/tuf@4.0.0': + '@sigstore/tuf@4.0.1': dependencies: '@sigstore/protobuf-specs': 0.5.0 tuf-js: 4.1.0 transitivePeerDependencies: - supports-color - '@sigstore/verify@3.0.0': + '@sigstore/verify@3.1.0': dependencies: '@sigstore/bundle': 4.0.0 - '@sigstore/core': 3.0.0 + '@sigstore/core': 3.1.0 '@sigstore/protobuf-specs': 0.5.0 '@sinclair/typebox@0.34.41': {} @@ -11707,17 +12280,17 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.6.1(eslint@9.39.2(jiti@2.6.1))': + '@stylistic/eslint-plugin@5.8.0(eslint@9.39.2(jiti@2.6.1))': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/types': 8.50.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/types': 8.55.0 eslint: 9.39.2(jiti@2.6.1) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 picomatch: 4.0.3 - '@thednp/event-listener@2.0.10': {} + '@thednp/event-listener@2.0.12': {} '@thednp/position-observer@1.1.0': dependencies: @@ -11725,11 +12298,10 @@ snapshots: '@thednp/shorty@2.0.11': {} - '@tokenizer/inflate@0.3.1': + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3 - fflate: 0.8.2 - token-types: 6.1.1 + token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -11737,7 +12309,7 @@ snapshots: '@ts-morph/common@0.28.1': dependencies: - minimatch: 10.1.1 + minimatch: 10.2.0 path-browserify: 1.0.1 tinyglobby: 0.2.15 @@ -11754,7 +12326,7 @@ snapshots: '@tufjs/models@4.1.0': dependencies: '@tufjs/canonical-json': 2.0.0 - minimatch: 10.1.1 + minimatch: 10.2.0 '@tybys/wasm-util@0.10.1': dependencies: @@ -11763,46 +12335,46 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.5 + '@babel/types': 7.29.0 '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/bonjour@3.5.13': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/cache-manager@5.0.0': dependencies: - cache-manager: 7.2.7 + cache-manager: 7.2.8 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 4.19.7 - '@types/node': 25.0.3 + '@types/express-serve-static-core': 5.1.1 + '@types/node': 25.2.3 '@types/connect@3.4.38': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/cookie-parser@1.4.10(@types/express@5.0.6)': dependencies: @@ -11812,7 +12384,7 @@ snapshots: '@types/cors@2.8.19': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/cron@2.4.3': dependencies: @@ -11945,18 +12517,20 @@ snapshots: '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.8': {} - '@types/express-serve-static-core@4.19.7': + '@types/express-serve-static-core@4.19.8': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 - '@types/express-serve-static-core@5.1.0': + '@types/express-serve-static-core@5.1.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/qs': 6.14.0 '@types/range-parser': 1.2.7 '@types/send': 1.2.1 @@ -11964,14 +12538,14 @@ snapshots: '@types/express@4.17.25': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 4.19.7 + '@types/express-serve-static-core': 4.19.8 '@types/qs': 6.14.0 '@types/serve-static': 1.15.10 '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.0 + '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 '@types/geojson@7946.0.16': {} @@ -11982,7 +12556,7 @@ snapshots: '@types/http-proxy@1.17.17': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/istanbul-lib-coverage@2.0.6': {} @@ -12001,7 +12575,7 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -12012,7 +12586,7 @@ snapshots: '@types/jsonwebtoken@9.0.10': dependencies: '@types/ms': 2.1.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/luxon@3.7.1': {} @@ -12026,9 +12600,9 @@ snapshots: '@types/node-forge@1.3.14': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 - '@types/node@25.0.3': + '@types/node@25.2.3': dependencies: undici-types: 7.16.0 @@ -12046,30 +12620,30 @@ snapshots: '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/send@1.2.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/serve-index@1.9.4': dependencies: - '@types/express': 4.17.25 + '@types/express': 5.0.6 '@types/serve-static@1.15.10': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/send': 0.17.6 '@types/serve-static@2.2.0': dependencies: '@types/http-errors': 2.0.5 - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/sockjs@0.3.36': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/stack-utils@2.0.3': {} @@ -12077,7 +12651,7 @@ snapshots: dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 25.0.3 + '@types/node': 25.2.3 form-data: 4.0.5 '@types/supertest@6.0.3': @@ -12098,7 +12672,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@types/yargs-parser@21.0.3': {} @@ -12106,95 +12680,95 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/type-utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.0 + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/type-utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 eslint: 9.39.2(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.50.0 + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.50.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.50.0': + '@typescript-eslint/scope-manager@8.55.0': dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 - '@typescript-eslint/tsconfig-utils@8.50.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.55.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.50.0': {} + '@typescript-eslint/types@8.55.0': {} - '@typescript-eslint/typescript-estree@8.50.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.55.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.50.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3) - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/visitor-keys': 8.50.0 + '@typescript-eslint/project-service': 8.55.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.55.0(typescript@5.9.3) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/visitor-keys': 8.55.0 debug: 4.4.3 minimatch: 9.0.5 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 - ts-api-utils: 2.1.0(typescript@5.9.3) + ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': + '@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.50.0 - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.55.0 + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.50.0': + '@typescript-eslint/visitor-keys@8.55.0': dependencies: - '@typescript-eslint/types': 8.50.0 + '@typescript-eslint/types': 8.55.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -12260,9 +12834,9 @@ snapshots: '@vercel/oidc@3.0.5': {} - '@vitejs/plugin-basic-ssl@2.1.0(vite@7.2.2(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0))': + '@vitejs/plugin-basic-ssl@2.1.0(vite@7.3.0(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.44.1))': dependencies: - vite: 7.2.2(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0) + vite: 7.3.0(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.44.1) '@webassemblyjs/ast@1.14.1': dependencies: @@ -12383,29 +12957,33 @@ snapshots: agent-base@7.1.4: {} - ai@5.0.116(zod@4.1.13): + ai@5.0.116(zod@4.3.5): dependencies: - '@ai-sdk/gateway': 2.0.23(zod@4.1.13) + '@ai-sdk/gateway': 2.0.23(zod@4.3.5) '@ai-sdk/provider': 2.0.0 - '@ai-sdk/provider-utils': 3.0.19(zod@4.1.13) + '@ai-sdk/provider-utils': 3.0.19(zod@4.3.5) '@opentelemetry/api': 1.9.0 - zod: 4.1.13 + zod: 4.3.5 - ajv-formats@2.1.1(ajv@8.17.1): + ajv-formats@2.1.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.17.1): + ajv-keywords@5.1.0(ajv@8.18.0): dependencies: - ajv: 8.17.1 + ajv: 8.18.0 fast-deep-equal: 3.1.3 ajv@6.12.6: @@ -12422,41 +13000,48 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - algoliasearch@5.40.1: - dependencies: - '@algolia/abtesting': 1.6.1 - '@algolia/client-abtesting': 5.40.1 - '@algolia/client-analytics': 5.40.1 - '@algolia/client-common': 5.40.1 - '@algolia/client-insights': 5.40.1 - '@algolia/client-personalization': 5.40.1 - '@algolia/client-query-suggestions': 5.40.1 - '@algolia/client-search': 5.40.1 - '@algolia/ingestion': 1.40.1 - '@algolia/monitoring': 1.40.1 - '@algolia/recommend': 5.40.1 - '@algolia/requester-browser-xhr': 5.40.1 - '@algolia/requester-fetch': 5.40.1 - '@algolia/requester-node-http': 5.40.1 + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch@5.46.2: + dependencies: + '@algolia/abtesting': 1.12.2 + '@algolia/client-abtesting': 5.46.2 + '@algolia/client-analytics': 5.46.2 + '@algolia/client-common': 5.46.2 + '@algolia/client-insights': 5.46.2 + '@algolia/client-personalization': 5.46.2 + '@algolia/client-query-suggestions': 5.46.2 + '@algolia/client-search': 5.46.2 + '@algolia/ingestion': 1.46.2 + '@algolia/monitoring': 1.46.2 + '@algolia/recommend': 5.46.2 + '@algolia/requester-browser-xhr': 5.46.2 + '@algolia/requester-fetch': 5.46.2 + '@algolia/requester-node-http': 5.46.2 amdefine@1.0.1: optional: true - angular-eslint@21.1.0(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript-eslint@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3): - dependencies: - '@angular-devkit/core': 21.0.4(chokidar@4.0.3) - '@angular-devkit/schematics': 21.0.4(chokidar@4.0.3) - '@angular-eslint/builder': 21.1.0(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin': 21.1.0(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/eslint-plugin-template': 21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/schematics': 21.1.0(@angular-eslint/template-parser@21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.0.4(@types/node@25.0.3)(chokidar@4.0.3))(@typescript-eslint/types@8.50.0)(@typescript-eslint/utils@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@4.0.3)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular-eslint/template-parser': 21.1.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@angular/cli': 21.0.4(@types/node@25.0.3)(chokidar@4.0.3) - '@typescript-eslint/types': 8.50.0 - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + angular-eslint@21.2.0(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(typescript@5.9.3): + dependencies: + '@angular-devkit/core': 21.1.4(chokidar@5.0.0) + '@angular-devkit/schematics': 21.1.4(chokidar@5.0.0) + '@angular-eslint/builder': 21.2.0(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin': 21.2.0(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/eslint-plugin-template': 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/schematics': 21.2.0(@angular-eslint/template-parser@21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(@angular/cli@21.1.4(@types/node@25.2.3)(chokidar@5.0.0))(@typescript-eslint/types@8.55.0)(@typescript-eslint/utils@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(chokidar@5.0.0)(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular-eslint/template-parser': 21.2.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@angular/cli': 21.1.4(@types/node@25.2.3)(chokidar@5.0.0) + '@typescript-eslint/types': 8.55.0 + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 - typescript-eslint: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + typescript-eslint: 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - chokidar - supports-color @@ -12473,7 +13058,7 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@7.2.0: + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -12580,12 +13165,11 @@ snapshots: asynckit@0.4.0: {} - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.4.23(postcss@8.5.6): dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001761 - fraction.js: 4.3.7 - normalize-range: 0.1.2 + caniuse-lite: 1.0.30001770 + fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 @@ -12596,28 +13180,28 @@ snapshots: axobject-query@4.1.0: {} - babel-jest@30.2.0(@babel/core@7.28.5): + babel-jest@30.2.0(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.5) + babel-preset-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-loader@10.0.0(@babel/core@7.28.4)(webpack@5.104.0(esbuild@0.26.0)): + babel-loader@10.0.0(@babel/core@7.28.5)(webpack@5.105.0(esbuild@0.27.2)): dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 find-up: 5.0.0 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) babel-plugin-istanbul@7.0.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 6.0.3 @@ -12629,57 +13213,85 @@ snapshots: dependencies: '@types/babel__core': 7.20.5 - babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.4): + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.28.5): dependencies: - '@babel/compat-data': 7.28.5 - '@babel/core': 7.28.4 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) + '@babel/compat-data': 7.29.0 + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.5) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.4): + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.28.6): dependencies: - '@babel/core': 7.28.4 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) - core-js-compat: 3.47.0 + '@babel/compat-data': 7.29.0 + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.4): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.5): dependencies: - '@babel/core': 7.28.4 - '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.4) + '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.5) + core-js-compat: 3.48.0 transitivePeerDependencies: - supports-color - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5): + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.6): dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5) - - babel-preset-jest@30.2.0(@babel/core@7.28.5): + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.28.5): dependencies: '@babel/core': 7.28.5 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.5) + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 babel-plugin-jest-hoist: 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) balanced-match@1.0.2: {} + balanced-match@4.0.2: + dependencies: + jackspeak: 4.2.3 + base64-arraybuffer@1.0.2: optional: true @@ -12687,7 +13299,7 @@ snapshots: base64id@2.0.0: {} - baseline-browser-mapping@2.9.10: {} + baseline-browser-mapping@2.9.19: {} basic-auth@2.0.1: dependencies: @@ -12703,7 +13315,7 @@ snapshots: css-what: 7.0.0 dom-serializer: 2.0.0 domhandler: 5.0.3 - htmlparser2: 10.0.0 + htmlparser2: 10.1.0 picocolors: 1.1.1 postcss: 8.5.6 postcss-media-query-parser: 0.2.3 @@ -12728,22 +13340,22 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.14.0 + qs: 6.14.2 raw-body: 2.5.3 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - body-parser@2.2.1: + body-parser@2.2.2: dependencies: bytes: 3.1.2 content-type: 1.0.5 debug: 4.4.3 http-errors: 2.0.1 - iconv-lite: 0.7.1 + iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.14.0 + qs: 6.15.0 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: @@ -12758,7 +13370,7 @@ snapshots: bootstrap.native@5.1.6: dependencies: - '@thednp/event-listener': 2.0.10 + '@thednp/event-listener': 2.0.12 '@thednp/position-observer': 1.1.0 '@thednp/shorty': 2.0.11 @@ -12771,15 +13383,19 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.9.10 - caniuse-lite: 1.0.30001761 - electron-to-chromium: 1.5.267 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.286 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -12819,20 +13435,20 @@ snapshots: dependencies: '@npmcli/fs': 5.0.0 fs-minipass: 3.0.3 - glob: 13.0.0 - lru-cache: 11.2.4 + glob: 13.0.3 + lru-cache: 11.2.6 minipass: 7.1.2 minipass-collect: 2.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 p-map: 7.0.4 - ssri: 13.0.0 + ssri: 13.0.1 unique-filename: 5.0.0 - cache-manager@7.2.7: + cache-manager@7.2.8: dependencies: - '@cacheable/utils': 2.3.2 - keyv: 5.5.5 + '@cacheable/utils': 2.3.4 + keyv: 5.6.0 call-bind-apply-helpers@1.0.2: dependencies: @@ -12859,13 +13475,13 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001761: {} + caniuse-lite@1.0.30001770: {} canvg@3.0.11: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 '@types/raf': 3.4.3 - core-js: 3.47.0 + core-js: 3.48.0 raf: 3.4.1 regenerator-runtime: 0.13.11 rgbcolor: 1.0.1 @@ -12900,11 +13516,11 @@ snapshots: domhandler: 5.0.3 domutils: 3.2.2 encoding-sniffer: 0.2.1 - htmlparser2: 10.0.0 + htmlparser2: 10.1.0 parse5: 7.3.0 parse5-htmlparser2-tree-adapter: 7.1.0 parse5-parser-stream: 7.1.2 - undici: 7.16.0 + undici: 7.22.0 whatwg-mimetype: 4.0.0 chokidar@3.6.0: @@ -12923,6 +13539,10 @@ snapshots: dependencies: readdirp: 4.1.2 + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + chownr@3.0.0: {} chrome-trace-event@1.0.4: {} @@ -12951,7 +13571,7 @@ snapshots: cli-spinners@2.9.2: {} - cli-spinners@3.3.0: {} + cli-spinners@3.4.0: {} cli-table3@0.6.5: dependencies: @@ -12962,7 +13582,7 @@ snapshots: cli-truncate@5.1.1: dependencies: slice-ansi: 7.1.2 - string-width: 8.1.0 + string-width: 8.1.1 cli-width@4.1.0: {} @@ -13012,7 +13632,7 @@ snapshots: dependencies: delayed-stream: 1.0.0 - commander@14.0.2: {} + commander@14.0.3: {} commander@2.20.3: {} @@ -13106,20 +13726,20 @@ snapshots: dependencies: is-what: 3.14.1 - copy-webpack-plugin@13.0.1(webpack@5.104.0(esbuild@0.26.0)): + copy-webpack-plugin@13.0.1(webpack@5.105.0(esbuild@0.27.2)): dependencies: glob-parent: 6.0.2 normalize-path: 3.0.0 schema-utils: 4.3.3 serialize-javascript: 6.0.2 tinyglobby: 0.2.15 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) - core-js-compat@3.47.0: + core-js-compat@3.48.0: dependencies: browserslist: 4.28.1 - core-js@3.47.0: + core-js@3.48.0: optional: true core-util-is@1.0.3: {} @@ -13129,6 +13749,11 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 @@ -13149,11 +13774,6 @@ snapshots: create-require@1.1.1: {} - cron@4.3.5: - dependencies: - '@types/luxon': 3.7.1 - luxon: 3.7.2 - cron@4.4.0: dependencies: '@types/luxon': 3.7.1 @@ -13170,7 +13790,7 @@ snapshots: utrie: 1.0.2 optional: true - css-loader@7.1.2(webpack@5.104.0(esbuild@0.26.0)): + css-loader@7.1.2(webpack@5.105.0(esbuild@0.27.2)): dependencies: icss-utils: 5.1.0(postcss@8.5.6) postcss: 8.5.6 @@ -13181,7 +13801,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.7.3 optionalDependencies: - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) css-select@5.2.2: dependencies: @@ -13397,10 +14017,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.7: - dependencies: - ms: 2.1.3 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -13421,7 +14037,7 @@ snapshots: default-browser-id@5.0.1: {} - default-browser@5.4.0: + default-browser@5.5.0: dependencies: bundle-name: 4.1.0 default-browser-id: 5.0.1 @@ -13458,9 +14074,6 @@ snapshots: destroy@1.2.0: {} - detect-libc@1.0.3: - optional: true - detect-libc@2.1.2: optional: true @@ -13507,14 +14120,14 @@ snapshots: dot@2.0.0-beta.1: {} - dotenv-expand@12.0.1: + dotenv-expand@12.0.3: dependencies: - dotenv: 16.4.7 - - dotenv@16.4.7: {} + dotenv: 16.6.1 dotenv@16.6.1: {} + dotenv@17.2.3: {} + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -13533,7 +14146,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.267: {} + electron-to-chromium@1.5.286: {} emittery@0.13.1: {} @@ -13563,12 +14176,12 @@ snapshots: dependencies: once: 1.4.0 - engine.io-client@6.6.3: + engine.io-client@6.6.4: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.17.1 + ws: 8.18.3 xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -13577,17 +14190,17 @@ snapshots: engine.io-parser@5.2.3: {} - engine.io@6.6.4: + engine.io@6.6.5: dependencies: '@types/cors': 2.8.19 - '@types/node': 25.0.3 + '@types/node': 25.2.3 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 - cors: 2.8.5 - debug: 4.3.7 + cors: 2.8.6 + debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.17.1 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - supports-color @@ -13598,10 +14211,17 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + enhanced-resolve@5.19.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@4.5.0: {} entities@6.0.1: {} + entities@7.0.1: {} + env-paths@2.2.1: {} environment@1.1.0: {} @@ -13678,8 +14298,6 @@ snapshots: es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} - es-module-lexer@2.0.0: {} es-object-atoms@1.1.1: @@ -13705,68 +14323,8 @@ snapshots: es6-shim@0.35.8: {} - esbuild-wasm@0.26.0: {} - esbuild-wasm@0.27.2: {} - esbuild@0.25.12: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.12 - '@esbuild/android-arm': 0.25.12 - '@esbuild/android-arm64': 0.25.12 - '@esbuild/android-x64': 0.25.12 - '@esbuild/darwin-arm64': 0.25.12 - '@esbuild/darwin-x64': 0.25.12 - '@esbuild/freebsd-arm64': 0.25.12 - '@esbuild/freebsd-x64': 0.25.12 - '@esbuild/linux-arm': 0.25.12 - '@esbuild/linux-arm64': 0.25.12 - '@esbuild/linux-ia32': 0.25.12 - '@esbuild/linux-loong64': 0.25.12 - '@esbuild/linux-mips64el': 0.25.12 - '@esbuild/linux-ppc64': 0.25.12 - '@esbuild/linux-riscv64': 0.25.12 - '@esbuild/linux-s390x': 0.25.12 - '@esbuild/linux-x64': 0.25.12 - '@esbuild/netbsd-arm64': 0.25.12 - '@esbuild/netbsd-x64': 0.25.12 - '@esbuild/openbsd-arm64': 0.25.12 - '@esbuild/openbsd-x64': 0.25.12 - '@esbuild/openharmony-arm64': 0.25.12 - '@esbuild/sunos-x64': 0.25.12 - '@esbuild/win32-arm64': 0.25.12 - '@esbuild/win32-ia32': 0.25.12 - '@esbuild/win32-x64': 0.25.12 - - esbuild@0.26.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.26.0 - '@esbuild/android-arm': 0.26.0 - '@esbuild/android-arm64': 0.26.0 - '@esbuild/android-x64': 0.26.0 - '@esbuild/darwin-arm64': 0.26.0 - '@esbuild/darwin-x64': 0.26.0 - '@esbuild/freebsd-arm64': 0.26.0 - '@esbuild/freebsd-x64': 0.26.0 - '@esbuild/linux-arm': 0.26.0 - '@esbuild/linux-arm64': 0.26.0 - '@esbuild/linux-ia32': 0.26.0 - '@esbuild/linux-loong64': 0.26.0 - '@esbuild/linux-mips64el': 0.26.0 - '@esbuild/linux-ppc64': 0.26.0 - '@esbuild/linux-riscv64': 0.26.0 - '@esbuild/linux-s390x': 0.26.0 - '@esbuild/linux-x64': 0.26.0 - '@esbuild/netbsd-arm64': 0.26.0 - '@esbuild/netbsd-x64': 0.26.0 - '@esbuild/openbsd-arm64': 0.26.0 - '@esbuild/openbsd-x64': 0.26.0 - '@esbuild/openharmony-arm64': 0.26.0 - '@esbuild/sunos-x64': 0.26.0 - '@esbuild/win32-arm64': 0.26.0 - '@esbuild/win32-ia32': 0.26.0 - '@esbuild/win32-x64': 0.26.0 - esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -13842,22 +14400,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(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@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13868,7 +14426,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@4.4.4)(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -13880,33 +14438,33 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@29.6.0(@typescript-eslint/eslint-plugin@8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3): + eslint-plugin-jest@29.15.0(@typescript-eslint/eslint-plugin@8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + jest: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) + typescript: 5.9.3 transitivePeerDependencies: - supports-color - - typescript eslint-plugin-nestjs@1.2.3: dependencies: tslib: 1.14.1 - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.7.4): + eslint-plugin-prettier@5.5.5(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))(prettier@3.8.1): dependencies: eslint: 9.39.2(jiti@2.6.1) - prettier: 3.7.4 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 + prettier: 3.8.1 + prettier-linter-helpers: 1.0.1 + synckit: 0.11.12 optionalDependencies: '@types/eslint': 9.6.1 eslint-config-prettier: 10.1.8(eslint@9.39.2(jiti@2.6.1)) @@ -13925,8 +14483,10 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@9.0.0: + eslint-scope@9.1.0: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 @@ -14017,7 +14577,7 @@ snapshots: eventemitter3@4.0.7: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} events@3.3.0: {} @@ -14064,9 +14624,10 @@ snapshots: exponential-backoff@3.1.3: {} - express-rate-limit@7.5.1(express@5.2.1): + express-rate-limit@8.2.1(express@5.2.1): dependencies: express: 5.2.1 + ip-address: 10.0.1 express@4.22.1: dependencies: @@ -14091,7 +14652,7 @@ snapshots: parseurl: 1.3.3 path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.14.0 + qs: 6.14.2 range-parser: 1.2.1 safe-buffer: 5.2.1 send: 0.19.2 @@ -14104,42 +14665,10 @@ snapshots: transitivePeerDependencies: - supports-color - express@5.1.0: - dependencies: - accepts: 2.0.0 - body-parser: 2.2.1 - content-disposition: 1.0.1 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.14.0 - range-parser: 1.2.1 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - express@5.2.1: dependencies: accepts: 2.0.0 - body-parser: 2.2.1 + body-parser: 2.2.2 content-disposition: 1.0.1 content-type: 1.0.5 cookie: 0.7.2 @@ -14158,7 +14687,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.0 + qs: 6.15.0 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -14199,7 +14728,7 @@ snapshots: fast-uri@3.1.0: {} - fastq@1.19.1: + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -14221,11 +14750,11 @@ snapshots: dependencies: flat-cache: 4.0.1 - file-type@21.1.0: + file-type@21.3.0: dependencies: - '@tokenizer/inflate': 0.3.1 + '@tokenizer/inflate': 0.4.1 strtok3: 10.3.4 - token-types: 6.1.1 + token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color @@ -14301,9 +14830,9 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.103.0): + fork-ts-checker-webpack-plugin@9.1.0(typescript@5.9.3)(webpack@5.104.1): dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 chalk: 4.1.2 chokidar: 4.0.3 cosmiconfig: 8.3.6(typescript@5.9.3) @@ -14313,10 +14842,10 @@ snapshots: minimatch: 3.1.2 node-abort-controller: 3.1.1 schema-utils: 3.3.0 - semver: 7.7.3 + semver: 7.7.4 tapable: 2.3.0 typescript: 5.9.3 - webpack: 5.103.0 + webpack: 5.104.1 form-data@4.0.5: dependencies: @@ -14334,7 +14863,7 @@ snapshots: forwarded@0.2.0: {} - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} fresh@0.5.2: {} @@ -14448,18 +14977,15 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - glob@11.1.0: + glob@13.0.0: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.1.1 + minimatch: 10.2.0 minipass: 7.1.2 - package-json-from-dist: 1.0.1 path-scurry: 2.0.1 - glob@13.0.0: + glob@13.0.3: dependencies: - minimatch: 10.1.1 + minimatch: 10.2.0 minipass: 7.1.2 path-scurry: 2.0.1 @@ -14474,7 +15000,7 @@ snapshots: globals@14.0.0: {} - globals@16.5.0: {} + globals@17.3.0: {} globalthis@1.0.4: dependencies: @@ -14514,19 +15040,21 @@ snapshots: dependencies: has-symbols: 1.1.0 - hashery@1.3.0: + hashery@1.4.0: dependencies: - hookified: 1.14.0 + hookified: 1.15.1 hasown@2.0.2: dependencies: function-bind: 1.1.2 - hookified@1.14.0: {} + hono@4.11.9: {} + + hookified@1.15.1: {} hosted-git-info@9.0.2: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.6 hpack.js@2.1.6: dependencies: @@ -14549,12 +15077,12 @@ snapshots: text-segmentation: 1.0.3 optional: true - htmlparser2@10.0.0: + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.2.2 - entities: 6.0.1 + entities: 7.0.1 http-auth-connect@1.0.6: {} @@ -14569,12 +15097,13 @@ snapshots: http-deceiver@1.2.7: {} - http-errors@1.6.3: + http-errors@1.8.1: dependencies: depd: 1.1.2 - inherits: 2.0.3 - setprototypeof: 1.1.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 statuses: 1.5.0 + toidentifier: 1.0.1 http-errors@2.0.1: dependencies: @@ -14637,9 +15166,9 @@ snapshots: hyperdyperid@1.2.0: {} - i18next@25.5.3(typescript@5.9.3): + i18next@25.7.4(typescript@5.9.3): dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 optionalDependencies: typescript: 5.9.3 @@ -14651,7 +15180,7 @@ snapshots: dependencies: safer-buffer: 2.1.2 - iconv-lite@0.7.1: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -14663,7 +15192,7 @@ snapshots: ignore-walk@8.0.0: dependencies: - minimatch: 10.1.1 + minimatch: 10.2.0 ignore@5.3.2: {} @@ -14693,12 +15222,8 @@ snapshots: once: 1.4.0 wrappy: 1.0.2 - inherits@2.0.3: {} - inherits@2.0.4: {} - ini@5.0.0: {} - ini@6.0.0: {} internal-slot@1.1.0: @@ -14711,6 +15236,8 @@ snapshots: iobuffer@5.4.0: {} + ip-address@10.0.1: {} + ip-address@10.1.0: {} ipaddr.js@1.9.1: {} @@ -14797,6 +15324,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -14862,8 +15391,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-unicode-supported@1.3.0: {} - is-unicode-supported@2.1.0: {} is-weakmap@2.0.2: {} @@ -14883,7 +15410,7 @@ snapshots: dependencies: is-docker: 2.2.1 - is-wsl@3.1.0: + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -14893,16 +15420,18 @@ snapshots: isexe@2.0.0: {} - isexe@3.1.1: {} + isexe@4.0.0: {} isobject@3.0.1: {} + isomorphic.js@0.2.5: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.5 + '@babel/core': 7.28.5 + '@babel/parser': 7.29.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 7.7.3 @@ -14936,9 +15465,9 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jackspeak@4.1.1: + jackspeak@4.2.3: dependencies: - '@isaacs/cliui': 8.0.2 + '@isaacs/cliui': 9.0.0 jest-canvas-mock@2.5.2: dependencies: @@ -14957,7 +15486,7 @@ snapshots: '@jest/expect': 30.2.0 '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 co: 4.6.0 dedent: 1.7.1 @@ -14977,15 +15506,15 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest-cli@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest-config: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-util: 30.2.0 jest-validate: 30.2.0 yargs: 17.7.2 @@ -14996,14 +15525,14 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest-config@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 '@jest/pattern': 30.0.1 '@jest/test-sequencer': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) + babel-jest: 30.2.0(@babel/core@7.29.0) chalk: 4.1.2 ci-info: 4.3.1 deepmerge: 4.3.1 @@ -15023,8 +15552,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 25.0.3 - ts-node: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + '@types/node': 25.2.3 + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -15053,7 +15582,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/environment-jsdom-abstract': 30.2.0(jsdom@26.1.0) '@types/jsdom': 21.1.7 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jsdom: 26.1.0 transitivePeerDependencies: - bufferutil @@ -15065,7 +15594,7 @@ snapshots: '@jest/environment': 30.2.0 '@jest/fake-timers': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-mock: 30.2.0 jest-util: 30.2.0 jest-validate: 30.2.0 @@ -15073,7 +15602,7 @@ snapshots: jest-haste-map@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -15099,7 +15628,7 @@ snapshots: jest-message-util@30.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 '@jest/types': 30.2.0 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -15112,27 +15641,27 @@ snapshots: jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 jest-util: 30.2.0 jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): optionalDependencies: jest-resolve: 30.2.0 - jest-preset-angular@16.0.0(b4775edd0d85938ea7ee883bbb12d4b1): + jest-preset-angular@16.0.0(128486741484e87e44937e8ea7d8f148): dependencies: - '@angular/compiler-cli': 21.0.6(@angular/compiler@21.0.6)(typescript@5.9.3) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) - '@angular/platform-browser-dynamic': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.0.6)(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))) + '@angular/compiler-cli': 21.1.4(@angular/compiler@21.1.4)(typescript@5.9.3) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/platform-browser-dynamic': 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/compiler@21.1.4)(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))) '@jest/environment-jsdom-abstract': 30.2.0(jsdom@26.1.0) bs-logger: 0.2.6 esbuild-wasm: 0.27.2 - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) jest-util: 30.2.0 jsdom: 26.1.0 pretty-format: 30.2.0 - ts-jest: 29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3) + ts-jest: 29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3) typescript: 5.9.3 optionalDependencies: esbuild: 0.27.2 @@ -15170,7 +15699,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 @@ -15199,7 +15728,7 @@ snapshots: '@jest/test-result': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 cjs-module-lexer: 2.1.1 collect-v8-coverage: 1.0.3 @@ -15219,17 +15748,17 @@ snapshots: jest-snapshot@30.2.0: dependencies: - '@babel/core': 7.28.5 - '@babel/generator': 7.28.5 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0) '@babel/types': 7.28.5 '@jest/expect-utils': 30.2.0 '@jest/get-type': 30.1.0 '@jest/snapshot-utils': 30.2.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 expect: 30.2.0 graceful-fs: 4.2.11 @@ -15238,15 +15767,15 @@ snapshots: jest-message-util: 30.2.0 jest-util: 30.2.0 pretty-format: 30.2.0 - semver: 7.7.3 - synckit: 0.11.11 + semver: 7.7.4 + synckit: 0.11.12 transitivePeerDependencies: - supports-color jest-util@30.2.0: dependencies: '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 chalk: 4.1.2 ci-info: 4.3.1 graceful-fs: 4.2.11 @@ -15265,7 +15794,7 @@ snapshots: dependencies: '@jest/test-result': 30.2.0 '@jest/types': 30.2.0 - '@types/node': 25.0.3 + '@types/node': 25.2.3 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -15274,24 +15803,24 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@30.2.0: dependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 '@ungap/structured-clone': 1.3.0 jest-util: 30.2.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: - '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + '@jest/core': 30.2.0(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) '@jest/types': 30.2.0 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest-cli: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -15369,6 +15898,8 @@ snapshots: json-schema-traverse@1.0.0: {} + json-schema-typed@8.0.2: {} + json-schema@0.4.0: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -15407,14 +15938,14 @@ snapshots: ms: 2.1.3 semver: 7.7.3 - jspdf@3.0.4: + jspdf@4.1.0: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.28.6 fast-png: 6.4.0 fflate: 0.8.2 optionalDependencies: canvg: 3.0.11 - core-js: 3.47.0 + core-js: 3.48.0 dompurify: 3.3.1 html2canvas: 1.4.1 @@ -15439,7 +15970,7 @@ snapshots: dependencies: json-buffer: 3.0.1 - keyv@5.5.5: + keyv@5.6.0: dependencies: '@keyv/serialize': 1.1.1 @@ -15450,11 +15981,11 @@ snapshots: picocolors: 1.1.1 shell-quote: 1.8.3 - less-loader@12.3.0(less@4.4.2)(webpack@5.104.0(esbuild@0.26.0)): + less-loader@12.3.0(less@4.4.2)(webpack@5.105.0(esbuild@0.27.2)): dependencies: less: 4.4.2 optionalDependencies: - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) less@4.4.2: dependencies: @@ -15479,13 +16010,17 @@ snapshots: lex-parser@0.1.4: {} + lib0@0.2.117: + dependencies: + isomorphic.js: 0.2.5 + libphonenumber-js@1.12.31: {} - license-webpack-plugin@4.0.2(webpack@5.104.0(esbuild@0.26.0)): + license-webpack-plugin@4.0.2(webpack@5.105.0(esbuild@0.27.2)): dependencies: - webpack-sources: 3.3.3 + webpack-sources: 3.3.4 optionalDependencies: - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) lie@3.1.1: dependencies: @@ -15497,26 +16032,26 @@ snapshots: dependencies: cli-truncate: 5.1.1 colorette: 2.0.20 - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 wrap-ansi: 9.0.2 - lmdb@3.4.3: + lmdb@3.4.4: dependencies: msgpackr: 1.11.8 node-addon-api: 6.1.0 node-gyp-build-optional-packages: 5.2.2 - ordered-binary: 1.6.0 + ordered-binary: 1.6.1 weak-lru-cache: 1.2.2 optionalDependencies: - '@lmdb/lmdb-darwin-arm64': 3.4.3 - '@lmdb/lmdb-darwin-x64': 3.4.3 - '@lmdb/lmdb-linux-arm': 3.4.3 - '@lmdb/lmdb-linux-arm64': 3.4.3 - '@lmdb/lmdb-linux-x64': 3.4.3 - '@lmdb/lmdb-win32-arm64': 3.4.3 - '@lmdb/lmdb-win32-x64': 3.4.3 + '@lmdb/lmdb-darwin-arm64': 3.4.4 + '@lmdb/lmdb-darwin-x64': 3.4.4 + '@lmdb/lmdb-linux-arm': 3.4.4 + '@lmdb/lmdb-linux-arm64': 3.4.4 + '@lmdb/lmdb-linux-x64': 3.4.4 + '@lmdb/lmdb-win32-arm64': 3.4.4 + '@lmdb/lmdb-win32-x64': 3.4.4 optional: true load-esm@1.0.3: {} @@ -15563,18 +16098,13 @@ snapshots: lodash.once@4.1.1: {} - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - log-symbols@7.0.1: dependencies: is-unicode-supported: 2.1.0 @@ -15582,7 +16112,7 @@ snapshots: log-update@6.1.0: dependencies: - ansi-escapes: 7.2.0 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 strip-ansi: 7.1.2 @@ -15594,7 +16124,7 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.4: {} + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -15610,7 +16140,7 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -15622,7 +16152,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.3 + semver: 7.7.4 make-error@1.3.6: {} @@ -15632,13 +16162,13 @@ snapshots: cacache: 20.0.3 http-cache-semantics: 4.2.0 minipass: 7.1.2 - minipass-fetch: 5.0.0 + minipass-fetch: 5.0.1 minipass-flush: 1.0.5 minipass-pipeline: 1.2.4 negotiator: 1.0.0 proc-log: 6.1.0 promise-retry: 2.0.1 - ssri: 13.0.0 + ssri: 13.0.1 transitivePeerDependencies: - supports-color @@ -15664,8 +16194,16 @@ snapshots: dependencies: fs-monkey: 1.1.0 - memfs@4.51.1: + memfs@4.56.10(tslib@2.8.1): dependencies: + '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.56.10(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) @@ -15708,17 +16246,17 @@ snapshots: mimic-function@5.0.1: {} - mini-css-extract-plugin@2.9.4(webpack@5.104.0(esbuild@0.26.0)): + mini-css-extract-plugin@2.9.4(webpack@5.105.0(esbuild@0.27.2)): dependencies: schema-utils: 4.3.3 tapable: 2.3.0 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) minimalistic-assert@1.0.1: {} - minimatch@10.1.1: + minimatch@10.2.0: dependencies: - '@isaacs/brace-expansion': 5.0.0 + brace-expansion: 5.0.2 minimatch@3.1.2: dependencies: @@ -15734,10 +16272,10 @@ snapshots: dependencies: minipass: 7.1.2 - minipass-fetch@5.0.0: + minipass-fetch@5.0.1: dependencies: minipass: 7.1.2 - minipass-sized: 1.0.3 + minipass-sized: 2.0.0 minizlib: 3.1.0 optionalDependencies: encoding: 0.1.13 @@ -15750,9 +16288,9 @@ snapshots: dependencies: minipass: 3.3.6 - minipass-sized@1.0.3: + minipass-sized@2.0.0: dependencies: - minipass: 3.3.6 + minipass: 7.1.2 minipass@3.3.6: dependencies: @@ -15833,7 +16371,7 @@ snapshots: needle@3.3.1: dependencies: iconv-lite: 0.6.3 - sax: 1.4.3 + sax: 1.4.4 optional: true negotiator@0.6.3: {} @@ -15846,18 +16384,18 @@ snapshots: neotraverse@0.6.18: {} - ngx-color-picker@20.1.1(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/forms@21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)): + ngx-color-picker@20.1.1(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/forms@21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2)): dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/forms': 21.0.6(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/forms': 21.1.4(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(rxjs@7.8.2) tslib: 2.8.1 - ngx-toastr@19.1.0(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))): + ngx-toastr@19.1.0(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(@angular/platform-browser@21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))): dependencies: - '@angular/common': 21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) - '@angular/core': 21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0) - '@angular/platform-browser': 21.0.6(@angular/animations@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.0.6(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.0.6(@angular/compiler@21.0.6)(rxjs@7.8.2)(zone.js@0.16.0)) + '@angular/common': 21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2) + '@angular/core': 21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0) + '@angular/platform-browser': 21.1.4(@angular/animations@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)))(@angular/common@21.1.4(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0))(rxjs@7.8.2))(@angular/core@21.1.4(@angular/compiler@21.1.4)(rxjs@7.8.2)(zone.js@0.16.0)) tslib: 2.8.1 node-abort-controller@3.1.1: {} @@ -15870,7 +16408,7 @@ snapshots: node-emoji@1.11.0: dependencies: - lodash: 4.17.21 + lodash: 4.17.23 node-forge@1.3.3: {} @@ -15879,7 +16417,7 @@ snapshots: detect-libc: 2.1.2 optional: true - node-gyp@12.1.0: + node-gyp@12.2.0: dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 @@ -15887,10 +16425,10 @@ snapshots: make-fetch-happen: 15.0.3 nopt: 9.0.0 proc-log: 6.1.0 - semver: 7.7.3 - tar: 7.5.2 + semver: 7.7.4 + tar: 7.5.9 tinyglobby: 0.2.15 - which: 6.0.0 + which: 6.0.1 transitivePeerDependencies: - supports-color @@ -15909,26 +16447,22 @@ snapshots: normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - - npm-bundled@4.0.0: + npm-bundled@5.0.0: dependencies: - npm-normalize-package-bin: 4.0.0 + npm-normalize-package-bin: 5.0.0 npm-install-checks@8.0.0: dependencies: - semver: 7.7.3 - - npm-normalize-package-bin@4.0.0: {} + semver: 7.7.4 npm-normalize-package-bin@5.0.0: {} - npm-package-arg@13.0.1: + npm-package-arg@13.0.2: dependencies: hosted-git-info: 9.0.2 - proc-log: 5.0.0 + proc-log: 6.1.0 semver: 7.7.3 - validate-npm-package-name: 6.0.2 + validate-npm-package-name: 7.0.2 npm-packlist@10.0.3: dependencies: @@ -15939,8 +16473,8 @@ snapshots: dependencies: npm-install-checks: 8.0.0 npm-normalize-package-bin: 5.0.0 - npm-package-arg: 13.0.1 - semver: 7.7.3 + npm-package-arg: 13.0.2 + semver: 7.7.4 npm-registry-fetch@19.1.1: dependencies: @@ -15948,9 +16482,9 @@ snapshots: jsonparse: 1.3.1 make-fetch-happen: 15.0.3 minipass: 7.1.2 - minipass-fetch: 5.0.0 + minipass-fetch: 5.0.1 minizlib: 3.1.0 - npm-package-arg: 13.0.1 + npm-package-arg: 13.0.2 proc-log: 6.1.0 transitivePeerDependencies: - supports-color @@ -16028,11 +16562,20 @@ snapshots: open@10.2.0: dependencies: - default-browser: 5.4.0 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 is-inside-container: 1.0.0 wsl-utils: 0.1.0 + open@11.0.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 + open@8.4.0: dependencies: define-lazy-prop: 2.0.0 @@ -16062,31 +16605,19 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.2 - ora@9.0.0: dependencies: chalk: 5.6.2 cli-cursor: 5.0.0 - cli-spinners: 3.3.0 + cli-spinners: 3.4.0 is-interactive: 2.0.0 is-unicode-supported: 2.1.0 log-symbols: 7.0.1 stdin-discarder: 0.2.2 - string-width: 8.1.0 + string-width: 8.1.1 strip-ansi: 7.1.2 - ordered-binary@1.6.0: + ordered-binary@1.6.1: optional: true os-name@4.0.1: @@ -16128,25 +16659,25 @@ snapshots: package-json-from-dist@1.0.1: {} - pacote@21.0.3: + pacote@21.0.4: dependencies: '@npmcli/git': 7.0.1 - '@npmcli/installed-package-contents': 3.0.0 + '@npmcli/installed-package-contents': 4.0.0 '@npmcli/package-json': 7.0.4 - '@npmcli/promise-spawn': 8.0.3 + '@npmcli/promise-spawn': 9.0.1 '@npmcli/run-script': 10.0.3 cacache: 20.0.3 fs-minipass: 3.0.3 minipass: 7.1.2 - npm-package-arg: 13.0.1 + npm-package-arg: 13.0.2 npm-packlist: 10.0.3 npm-pick-manifest: 11.0.3 npm-registry-fetch: 19.1.1 - proc-log: 5.0.0 + proc-log: 6.1.0 promise-retry: 2.0.1 - sigstore: 4.0.0 - ssri: 12.0.0 - tar: 7.5.2 + sigstore: 4.1.0 + ssri: 13.0.1 + tar: 7.5.9 transitivePeerDependencies: - supports-color @@ -16158,7 +16689,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -16211,7 +16742,7 @@ snapshots: path-scurry@2.0.1: dependencies: - lru-cache: 11.2.4 + lru-cache: 11.2.6 minipass: 7.1.2 path-to-regexp@0.1.12: {} @@ -16227,18 +16758,18 @@ snapshots: performance-now@2.1.0: optional: true - pg-cloudflare@1.2.7: + pg-cloudflare@1.3.0: optional: true - pg-connection-string@2.9.1: {} + pg-connection-string@2.11.0: {} pg-int8@1.0.1: {} - pg-pool@3.10.1(pg@8.16.3): + pg-pool@3.11.0(pg@8.18.0): dependencies: - pg: 8.16.3 + pg: 8.18.0 - pg-protocol@1.10.3: {} + pg-protocol@1.11.0: {} pg-types@2.2.0: dependencies: @@ -16248,15 +16779,15 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.16.3: + pg@8.18.0: dependencies: - pg-connection-string: 2.9.1 - pg-pool: 3.10.1(pg@8.16.3) - pg-protocol: 1.10.3 + pg-connection-string: 2.11.0 + pg-pool: 3.11.0(pg@8.18.0) + pg-protocol: 1.11.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: - pg-cloudflare: 1.2.7 + pg-cloudflare: 1.3.0 pgpass@1.0.5: dependencies: @@ -16275,7 +16806,7 @@ snapshots: pirates@4.0.7: {} - piscina@5.1.3: + piscina@5.1.4: optionalDependencies: '@napi-rs/nice': 1.1.1 @@ -16285,11 +16816,11 @@ snapshots: dependencies: find-up: 4.1.0 - playwright-core@1.57.0: {} + playwright-core@1.58.2: {} - playwright@1.57.0: + playwright@1.58.2: dependencies: - playwright-core: 1.57.0 + playwright-core: 1.58.2 optionalDependencies: fsevents: 2.3.2 @@ -16302,14 +16833,14 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.104.0(esbuild@0.26.0)): + postcss-loader@8.2.0(postcss@8.5.6)(typescript@5.9.3)(webpack@5.105.0(esbuild@0.27.2)): dependencies: cosmiconfig: 9.0.0(typescript@5.9.3) jiti: 2.6.1 postcss: 8.5.6 semver: 7.7.3 optionalDependencies: - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) transitivePeerDependencies: - typescript @@ -16359,6 +16890,8 @@ snapshots: dependencies: xtend: 4.0.2 + powershell-utils@0.1.0: {} + pq@0.0.3: dependencies: async: 3.2.6 @@ -16366,11 +16899,11 @@ snapshots: prelude-ls@1.2.1: {} - prettier-linter-helpers@1.0.0: + prettier-linter-helpers@1.0.1: dependencies: fast-diff: 1.3.0 - prettier@3.7.4: {} + prettier@3.8.1: {} pretty-format@30.2.0: dependencies: @@ -16380,8 +16913,6 @@ snapshots: prismjs@1.30.0: {} - proc-log@5.0.0: {} - proc-log@6.1.0: {} process-nextick-args@2.0.1: {} @@ -16416,7 +16947,15 @@ snapshots: qrcode-generator@1.5.2: {} - qs@6.14.0: + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -16444,7 +16983,7 @@ snapshots: dependencies: bytes: 3.1.2 http-errors: 2.0.1 - iconv-lite: 0.7.1 + iconv-lite: 0.7.2 unpipe: 1.0.0 react-is@18.3.1: {} @@ -16471,6 +17010,8 @@ snapshots: readdirp@4.1.2: {} + readdirp@5.0.0: {} + redis@5.10.0: dependencies: '@redis/bloom': 5.10.0(@redis/client@5.10.0) @@ -16580,59 +17121,92 @@ snapshots: rgbcolor@1.0.1: optional: true - rimraf@6.1.2: + rimraf@6.1.3: dependencies: - glob: 13.0.0 + glob: 13.0.3 package-json-from-dist: 1.0.1 robust-predicates@3.0.2: {} - rolldown@1.0.0-beta.47: + rolldown@1.0.0-beta.58: dependencies: - '@oxc-project/types': 0.96.0 - '@rolldown/pluginutils': 1.0.0-beta.47 + '@oxc-project/types': 0.106.0 + '@rolldown/pluginutils': 1.0.0-beta.58 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-beta.47 - '@rolldown/binding-darwin-arm64': 1.0.0-beta.47 - '@rolldown/binding-darwin-x64': 1.0.0-beta.47 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.47 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.47 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.47 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.47 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.47 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.47 - '@rolldown/binding-openharmony-arm64': 1.0.0-beta.47 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.47 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.47 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.47 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.47 - - rollup@4.54.0: + '@rolldown/binding-android-arm64': 1.0.0-beta.58 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.58 + '@rolldown/binding-darwin-x64': 1.0.0-beta.58 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.58 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.58 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.58 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.58 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.58 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.58 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.58 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.58 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.58 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.58 + + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.54.0 - '@rollup/rollup-android-arm64': 4.54.0 - '@rollup/rollup-darwin-arm64': 4.54.0 - '@rollup/rollup-darwin-x64': 4.54.0 - '@rollup/rollup-freebsd-arm64': 4.54.0 - '@rollup/rollup-freebsd-x64': 4.54.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 - '@rollup/rollup-linux-arm-musleabihf': 4.54.0 - '@rollup/rollup-linux-arm64-gnu': 4.54.0 - '@rollup/rollup-linux-arm64-musl': 4.54.0 - '@rollup/rollup-linux-loong64-gnu': 4.54.0 - '@rollup/rollup-linux-ppc64-gnu': 4.54.0 - '@rollup/rollup-linux-riscv64-gnu': 4.54.0 - '@rollup/rollup-linux-riscv64-musl': 4.54.0 - '@rollup/rollup-linux-s390x-gnu': 4.54.0 - '@rollup/rollup-linux-x64-gnu': 4.54.0 - '@rollup/rollup-linux-x64-musl': 4.54.0 - '@rollup/rollup-openharmony-arm64': 4.54.0 - '@rollup/rollup-win32-arm64-msvc': 4.54.0 - '@rollup/rollup-win32-ia32-msvc': 4.54.0 - '@rollup/rollup-win32-x64-gnu': 4.54.0 - '@rollup/rollup-win32-x64-msvc': 4.54.0 + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 + fsevents: 2.3.3 + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 fsevents: 2.3.3 router@2.2.0: @@ -16688,22 +17262,22 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@16.0.5(sass@1.93.2)(webpack@5.104.0(esbuild@0.26.0)): + sass-loader@16.0.6(sass@1.97.1)(webpack@5.105.0(esbuild@0.27.2)): dependencies: neo-async: 2.6.2 optionalDependencies: - sass: 1.93.2 - webpack: 5.104.0(esbuild@0.26.0) + sass: 1.97.1 + webpack: 5.105.0(esbuild@0.27.2) - sass@1.93.2: + sass@1.97.1: dependencies: chokidar: 4.0.3 immutable: 5.1.4 source-map-js: 1.2.1 optionalDependencies: - '@parcel/watcher': 2.5.1 + '@parcel/watcher': 2.5.6 - sax@1.4.3: + sax@1.4.4: optional: true saxes@6.0.0: @@ -16719,9 +17293,9 @@ snapshots: schema-utils@4.3.3: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.17.1 - ajv-formats: 2.1.1(ajv@8.17.1) - ajv-keywords: 5.1.0(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) select-hose@2.0.0: {} @@ -16737,6 +17311,8 @@ snapshots: semver@7.7.3: {} + semver@7.7.4: {} + send@0.19.2: dependencies: debug: 2.6.9 @@ -16775,13 +17351,13 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-index@1.9.1: + serve-index@1.9.2: dependencies: accepts: 1.3.8 batch: 0.6.1 debug: 2.6.9 escape-html: 1.0.3 - http-errors: 1.6.3 + http-errors: 1.8.1 mime-types: 2.1.35 parseurl: 1.3.3 transitivePeerDependencies: @@ -16827,8 +17403,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - setprototypeof@1.1.0: {} - setprototypeof@1.2.0: {} sha.js@2.4.12: @@ -16881,14 +17455,14 @@ snapshots: signal-exit@4.1.0: {} - sigstore@4.0.0: + sigstore@4.1.0: dependencies: '@sigstore/bundle': 4.0.0 - '@sigstore/core': 3.0.0 + '@sigstore/core': 3.1.0 '@sigstore/protobuf-specs': 0.5.0 - '@sigstore/sign': 4.0.1 - '@sigstore/tuf': 4.0.0 - '@sigstore/verify': 3.0.0 + '@sigstore/sign': 4.1.0 + '@sigstore/tuf': 4.0.1 + '@sigstore/verify': 3.1.0 transitivePeerDependencies: - supports-color @@ -16907,42 +17481,42 @@ snapshots: smart-buffer@4.2.0: {} - socket.io-adapter@2.5.5: + socket.io-adapter@2.5.6: dependencies: - debug: 4.3.7 - ws: 8.17.1 + debug: 4.4.3 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-client@4.8.1: + socket.io-client@4.8.3: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 - engine.io-client: 6.6.3 - socket.io-parser: 4.2.4 + debug: 4.4.3 + engine.io-client: 6.6.4 + socket.io-parser: 4.2.5 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.4: + socket.io-parser@4.2.5: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 transitivePeerDependencies: - supports-color - socket.io@4.8.1: + socket.io@4.8.3: dependencies: accepts: 1.3.8 base64id: 2.0.0 cors: 2.8.5 - debug: 4.3.7 - engine.io: 6.6.4 - socket.io-adapter: 2.5.5 - socket.io-parser: 4.2.4 + debug: 4.4.3 + engine.io: 6.6.5 + socket.io-adapter: 2.5.6 + socket.io-parser: 4.2.5 transitivePeerDependencies: - bufferutil - supports-color @@ -16969,11 +17543,11 @@ snapshots: source-map-js@1.2.1: {} - source-map-loader@5.0.0(webpack@5.104.0(esbuild@0.26.0)): + source-map-loader@5.0.0(webpack@5.105.0(esbuild@0.27.2)): dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) source-map-support@0.5.13: dependencies: @@ -17041,11 +17615,7 @@ snapshots: sql-highlight@6.1.0: {} - ssri@12.0.0: - dependencies: - minipass: 7.1.2 - - ssri@13.0.0: + ssri@13.0.1: dependencies: minipass: 7.1.2 @@ -17099,7 +17669,7 @@ snapshots: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 - string-width@8.1.0: + string-width@8.1.1: dependencies: get-east-asian-width: 1.4.0 strip-ansi: 7.1.2 @@ -17155,7 +17725,7 @@ snapshots: dependencies: '@tokenizer/token': 0.3.0 - superagent@10.2.3: + superagent@10.3.0: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 @@ -17165,14 +17735,15 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.14.0 + qs: 6.14.1 transitivePeerDependencies: - supports-color - supertest@7.1.4: + supertest@7.2.2: dependencies: + cookie-signature: 1.2.2 methods: 1.1.2 - superagent: 10.2.3 + superagent: 10.3.0 transitivePeerDependencies: - supports-color @@ -17195,15 +17766,15 @@ snapshots: symbol-tree@3.2.4: {} - synckit@0.11.11: + synckit@0.11.12: dependencies: '@pkgr/core': 0.2.9 - tablesort@5.6.0: {} + tablesort@5.7.0: {} tapable@2.3.0: {} - tar@7.5.2: + tar@7.5.9: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -17211,40 +17782,39 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 - terser-webpack-plugin@5.3.16(esbuild@0.26.0)(webpack@5.104.0(esbuild@0.26.0)): + terser-webpack-plugin@5.3.16(esbuild@0.27.2)(webpack@5.105.0(esbuild@0.27.2)): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 - webpack: 5.104.0(esbuild@0.26.0) + terser: 5.46.0 + webpack: 5.105.0(esbuild@0.27.2) optionalDependencies: - esbuild: 0.26.0 + esbuild: 0.27.2 - terser-webpack-plugin@5.3.16(webpack@5.103.0): + terser-webpack-plugin@5.3.16(webpack@5.104.1): dependencies: '@jridgewell/trace-mapping': 0.3.31 jest-worker: 27.5.1 schema-utils: 4.3.3 serialize-javascript: 6.0.2 - terser: 5.44.0 - webpack: 5.103.0 + terser: 5.46.0 + webpack: 5.104.1 - terser@5.44.0: + terser@5.44.1: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 - terser@5.44.1: + terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 acorn: 8.15.0 commander: 2.20.3 source-map-support: 0.5.21 - optional: true test-exclude@6.0.0: dependencies: @@ -17290,9 +17860,9 @@ snapshots: toidentifier@1.0.1: {} - token-types@6.1.1: + token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.1.1 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 @@ -17316,16 +17886,16 @@ snapshots: dependencies: matchit: 1.1.0 - ts-api-utils@2.1.0(typescript@5.9.3): + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 - ts-jest@29.4.6(@babel/core@7.28.5)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.28.5))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)))(typescript@5.9.3): + ts-jest@29.4.6(@babel/core@7.29.0)(@jest/transform@30.2.0)(@jest/types@30.2.0)(babel-jest@30.2.0(@babel/core@7.29.0))(esbuild@0.27.2)(jest-util@30.2.0)(jest@30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)))(typescript@5.9.3): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 handlebars: 4.7.8 - jest: 30.2.0(@types/node@25.0.3)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)) + jest: 30.2.0(@types/node@25.2.3)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)) json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 @@ -17334,14 +17904,14 @@ snapshots: typescript: 5.9.3 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@jest/transform': 30.2.0 '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.5) + babel-jest: 30.2.0(@babel/core@7.29.0) esbuild: 0.27.2 jest-util: 30.2.0 - ts-loader@9.5.4(typescript@5.9.3)(webpack@5.104.0): + ts-loader@9.5.4(typescript@5.9.3)(webpack@5.105.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.18.4 @@ -17349,21 +17919,21 @@ snapshots: semver: 7.7.3 source-map: 0.7.6 typescript: 5.9.3 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) ts-morph@27.0.2: dependencies: '@ts-morph/common': 0.28.1 code-block-writer: 13.0.3 - ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3): + ts-node@10.9.2(@types/node@25.2.3)(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.0.3 + '@types/node': 25.2.3 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -17377,7 +17947,7 @@ snapshots: tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.19.0 tapable: 2.3.0 tsconfig-paths: 4.2.0 @@ -17464,7 +18034,7 @@ snapshots: typedarray@0.0.6: {} - typeorm@0.3.28(pg@8.16.3)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.0.3)(typescript@5.9.3)): + typeorm@0.3.28(pg@8.18.0)(redis@5.10.0)(ts-node@10.9.2(@types/node@25.2.3)(typescript@5.9.3)): dependencies: '@sqltools/formatter': 1.2.5 ansis: 4.2.0 @@ -17482,19 +18052,19 @@ snapshots: uuid: 11.1.0 yargs: 17.7.2 optionalDependencies: - pg: 8.16.3 + pg: 8.18.0 redis: 5.10.0 - ts-node: 10.9.2(@types/node@25.0.3)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@25.2.3)(typescript@5.9.3) transitivePeerDependencies: - babel-plugin-macros - supports-color - typescript-eslint@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.50.0(@typescript-eslint/parser@8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) eslint: 9.39.2(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: @@ -17522,7 +18092,9 @@ snapshots: undici-types@7.16.0: {} - undici@7.16.0: {} + undici@7.20.0: {} + + undici@7.22.0: {} unicode-canonical-property-names-ecmascript@2.0.1: {} @@ -17611,7 +18183,7 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - validate-npm-package-name@6.0.2: {} + validate-npm-package-name@7.0.2: {} validator@13.15.26: {} @@ -17636,37 +18208,37 @@ snapshots: '@egjs/hammerjs': 2.0.17 component-emitter: 1.3.1 - vite@7.2.2(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.0): + vite@7.3.0(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.44.1): dependencies: - esbuild: 0.25.12 + esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.54.0 + rollup: 4.57.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.2 - sass: 1.93.2 - terser: 5.44.0 + sass: 1.97.1 + terser: 5.44.1 - vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(less@4.4.2)(sass@1.93.2)(terser@5.44.1): + vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(less@4.4.2)(sass@1.97.1)(terser@5.46.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.54.0 + rollup: 4.55.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 25.0.3 + '@types/node': 25.2.3 fsevents: 2.3.3 jiti: 2.6.1 less: 4.4.2 - sass: 1.93.2 - terser: 5.44.1 + sass: 1.97.1 + terser: 5.46.0 w3c-xmlserializer@5.0.0: dependencies: @@ -17676,7 +18248,12 @@ snapshots: dependencies: makeerror: 1.0.12 - watchpack@2.4.4: + watchpack@2.5.0: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + watchpack@2.5.1: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 @@ -17694,23 +18271,25 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-dev-middleware@7.4.5(webpack@5.104.0(esbuild@0.26.0)): + webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)): dependencies: colorette: 2.0.20 - memfs: 4.51.1 + memfs: 4.56.10(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.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) + transitivePeerDependencies: + - tslib - webpack-dev-server@5.2.2(webpack@5.104.0(esbuild@0.26.0)): + webpack-dev-server@5.2.2(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 '@types/express': 4.17.25 - '@types/express-serve-static-core': 4.19.7 + '@types/express-serve-static-core': 4.19.8 '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.10 '@types/sockjs': 0.3.36 @@ -17730,17 +18309,18 @@ snapshots: p-retry: 6.2.1 schema-utils: 4.3.3 selfsigned: 2.4.1 - serve-index: 1.9.1 + serve-index: 1.9.2 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.5(webpack@5.104.0(esbuild@0.26.0)) - ws: 8.18.3 + webpack-dev-middleware: 7.4.5(tslib@2.8.1)(webpack@5.105.0(esbuild@0.27.2)) + ws: 8.19.0 optionalDependencies: - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) transitivePeerDependencies: - bufferutil - debug - supports-color + - tslib - utf-8-validate webpack-merge@6.0.1: @@ -17751,14 +18331,14 @@ snapshots: webpack-node-externals@3.0.0: {} - webpack-sources@3.3.3: {} + webpack-sources@3.3.4: {} - webpack-subresource-integrity@5.1.0(webpack@5.104.0(esbuild@0.26.0)): + webpack-subresource-integrity@5.1.0(webpack@5.105.0(esbuild@0.27.2)): dependencies: typed-assert: 1.0.9 - webpack: 5.104.0(esbuild@0.26.0) + webpack: 5.105.0(esbuild@0.27.2) - webpack@5.103.0: + webpack@5.104.1: dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -17770,8 +18350,8 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.15.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.4 - es-module-lexer: 1.7.0 + enhanced-resolve: 5.19.0 + es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -17782,15 +18362,15 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(webpack@5.103.0) - watchpack: 2.4.4 - webpack-sources: 3.3.3 + terser-webpack-plugin: 5.3.16(webpack@5.104.1) + watchpack: 2.5.1 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js - webpack@5.104.0(esbuild@0.26.0): + webpack@5.105.0(esbuild@0.27.2): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.8 @@ -17802,7 +18382,7 @@ snapshots: acorn-import-phases: 1.0.4(acorn@8.15.0) browserslist: 4.28.1 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.18.4 + enhanced-resolve: 5.19.0 es-module-lexer: 2.0.0 eslint-scope: 5.1.1 events: 3.3.0 @@ -17814,9 +18394,9 @@ snapshots: neo-async: 2.6.2 schema-utils: 4.3.3 tapable: 2.3.0 - terser-webpack-plugin: 5.3.16(esbuild@0.26.0)(webpack@5.104.0(esbuild@0.26.0)) - watchpack: 2.4.4 - webpack-sources: 3.3.3 + terser-webpack-plugin: 5.3.16(esbuild@0.27.2)(webpack@5.105.0(esbuild@0.27.2)) + watchpack: 2.5.1 + webpack-sources: 3.3.4 transitivePeerDependencies: - '@swc/core' - esbuild @@ -17886,13 +18466,9 @@ snapshots: dependencies: isexe: 2.0.0 - which@5.0.0: + which@6.0.1: dependencies: - isexe: 3.1.1 - - which@6.0.0: - dependencies: - isexe: 3.1.1 + isexe: 4.0.0 wildcard@2.0.1: {} @@ -17935,13 +18511,18 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 - ws@8.17.1: {} - ws@8.18.3: {} + ws@8.19.0: {} + wsl-utils@0.1.0: dependencies: - is-wsl: 3.1.0 + is-wsl: 3.1.1 + + wsl-utils@0.3.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 xml-name-validator@5.0.0: {} @@ -17951,6 +18532,17 @@ snapshots: xtend@4.0.2: {} + y-protocols@1.0.7(yjs@13.6.29): + dependencies: + lib0: 0.2.117 + yjs: 13.6.29 + + y-websocket@3.0.0(yjs@13.6.29): + dependencies: + lib0: 0.2.117 + y-protocols: 1.0.7(yjs@13.6.29) + yjs: 13.6.29 + y18n@5.0.8: {} yallist@3.1.1: {} @@ -17982,6 +18574,10 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 + yjs@13.6.29: + dependencies: + lib0: 0.2.117 + yn@3.1.1: {} yocto-queue@0.1.0: {} @@ -17990,10 +18586,10 @@ snapshots: yoctocolors@2.1.2: {} - zod-to-json-schema@3.25.0(zod@4.1.13): + zod-to-json-schema@3.25.1(zod@4.3.5): dependencies: - zod: 4.1.13 + zod: 4.3.5 - zod@4.1.13: {} + zod@4.3.5: {} zone.js@0.16.0: {} diff --git a/teammapper-backend/.env.default b/teammapper-backend/.env.default index a09fbdfdd..c437df0e1 100644 --- a/teammapper-backend/.env.default +++ b/teammapper-backend/.env.default @@ -13,3 +13,5 @@ POSTGRES_TEST_PASSWORD= POSTGRES_TEST_DATABASE= DELETE_AFTER_DAYS= + +YJS_ENABLED=true diff --git a/teammapper-backend/README.md b/teammapper-backend/README.md index 676531c9b..0f8085f13 100644 --- a/teammapper-backend/README.md +++ b/teammapper-backend/README.md @@ -49,6 +49,77 @@ $ pnpm run test:e2e $ pnpm run test:cov ``` +## Environment Variables + +Copy `.env.default` to `.env` and configure the variables below. + +### Required + +| Variable | Description | +|---|---| +| `POSTGRES_HOST` | PostgreSQL host | +| `POSTGRES_PORT` | PostgreSQL port | +| `POSTGRES_USER` | PostgreSQL user | +| `POSTGRES_PASSWORD` | PostgreSQL password | +| `POSTGRES_DATABASE` | PostgreSQL database name | + +### Application + +| Variable | Description | Default | +|---|---|---| +| `PORT` | Server port | `3000` | +| `MODE` | `DEV` disables SSL and enables schema sync | `DEV` | +| `DELETE_AFTER_DAYS` | Days before unused maps are deleted | `30` | + +### PostgreSQL (optional) + +| Variable | Description | Default | +|---|---|---| +| `POSTGRES_SSL` | Enable SSL for database connections | `true` | +| `POSTGRES_SSL_REJECT_UNAUTHORIZED` | Reject unauthorized SSL certificates | `true` | +| `POSTGRES_QUERY_TIMEOUT` | Query timeout in ms | `100000` | +| `POSTGRES_STATEMENT_TIMEOUT` | Statement timeout in ms | `100000` | + +### Yjs Real-time Collaboration + +| 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` | +| `WS_PER_IP_MAX_CONNECTIONS` | Maximum WebSocket connections per IP | `50` | +| `WS_PER_IP_RATE_LIMIT` | Maximum connection attempts per IP within the rate window | `10` | +| `WS_PER_IP_RATE_WINDOW_MS` | Sliding window duration for rate limiting in ms | `10000` | + +### AI / LLM Integration + +| Variable | Description | Default | +|---|---|---| +| `AI_LLM_URL` | LLM service URL | - | +| `AI_LLM_TOKEN` | LLM API token | - | +| `AI_LLM_PROVIDER` | LLM provider | `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 | - | + +### Authentication + +| Variable | Description | Default | +|---|---|---| +| `JWT_SECRET` | Secret for signing JWT tokens | - | + +### Testing + +| Variable | Description | Default | +|---|---|---| +| `POSTGRES_TEST_HOST` | Test database host | - | +| `POSTGRES_TEST_PORT` | Test database port | `5432` | +| `POSTGRES_TEST_USER` | Test database user | - | +| `POSTGRES_TEST_PASSWORD` | Test database password | - | +| `POSTGRES_TEST_DATABASE` | Test database name | - | + ## Typeorm For a list of commands check https://github.com/typeorm/typeorm/blob/master/docs/using-cli.md diff --git a/teammapper-backend/package.json b/teammapper-backend/package.json index 60f2e2120..e08803ffe 100644 --- a/teammapper-backend/package.json +++ b/teammapper-backend/package.json @@ -34,72 +34,78 @@ "dependencies": { "@ai-sdk/openai": "2.0.88", "@ai-sdk/openai-compatible": "1.0.29", - "@nestjs/cache-manager": "^3.0.1", - "@nestjs/cli": "^11.0.14", - "@nestjs/common": "^11.1.9", - "@nestjs/config": "4.0.2", - "@nestjs/core": "^11.1.9", - "@nestjs/platform-express": "^11.1.9", - "@nestjs/platform-socket.io": "^11.1.9", - "@nestjs/schedule": "^6.1.0", + "@nestjs/cache-manager": "^3.1.0", + "@nestjs/cli": "^11.0.16", + "@nestjs/common": "^11.1.13", + "@nestjs/config": "4.0.3", + "@nestjs/core": "^11.1.13", + "@nestjs/platform-express": "^11.1.13", + "@nestjs/platform-socket.io": "^11.1.13", + "@nestjs/schedule": "^6.1.1", "@nestjs/serve-static": "^5.0.4", "@nestjs/typeorm": "^11.0.0", - "@nestjs/websockets": "^11.1.9", + "@nestjs/websockets": "^11.1.13", "@types/uuid": "^11.0.0", "ai": "5.0.116", - "cache-manager": "^7.2.7", + "cache-manager": "^7.2.8", "class-validator": "^0.14.3", "cookie-parser": "^1.4.7", "deepmerge": "^4.3.1", "http-proxy-middleware": "3.0.5", "jsonwebtoken": "^9.0.3", - "pg": "^8.16.3", + "lib0": "^0.2.117", + "pg": "^8.18.0", "pq": "^0.0.3", "reflect-metadata": "^0.2.2", - "rimraf": "^6.1.2", + "rimraf": "^6.1.3", "rxjs": "^7.8.2", - "socket.io": "4.8.1", + "socket.io": "4.8.3", "typeorm": "^0.3.28", - "uuid": "11.1.0" + "uuid": "11.1.0", + "ws": "^8.19.0", + "y-protocols": "^1.0.7", + "y-websocket": "^3.0.0", + "yjs": "^13.6.29" }, "devDependencies": { - "@eslint/compat": "^2.0.0", + "@eslint/compat": "^2.0.2", "@eslint/js": "^9.39.2", - "@golevelup/ts-jest": "^1.1.1", + "@golevelup/ts-jest": "^1.2.1", "@jest/globals": "^30.2.0", "@nestjs/schematics": "^11.0.9", - "@nestjs/testing": "^11.1.9", - "@stylistic/eslint-plugin": "^5.6.1", + "@nestjs/testing": "^11.1.13", + "@stylistic/eslint-plugin": "^5.8.0", "@types/cache-manager": "5.0.0", "@types/cookie-parser": "^1.4.10", "@types/cron": "^2.4.3", "@types/express": "^5.0.6", - "@types/express-serve-static-core": "^5.1.0", + "@types/express-serve-static-core": "^5.1.1", "@types/jest": "30.0.0", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^25.0.3", + "@types/node": "^25.2.3", "@types/supertest": "^6.0.3", - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0", + "@types/ws": "^8.18.1", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-import": "2.32.0", - "eslint-plugin-jest": "29.6.0", + "eslint-plugin-jest": "29.15.0", "eslint-plugin-nestjs": "1.2.3", - "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-typeorm": "0.0.19", - "globals": "^16.5.0", + "globals": "^17.3.0", "jest": "30.2.0", - "prettier": "^3.7.4", - "socket.io-client": "^4.8.1", - "supertest": "^7.1.4", + "prettier": "^3.8.1", + "socket.io-client": "^4.8.3", + "supertest": "^7.2.2", "ts-jest": "29.4.6", "ts-loader": "^9.5.4", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.50.0" + "typescript-eslint": "^8.55.0" }, "jest": { "moduleFileExtensions": [ @@ -119,6 +125,9 @@ "" ], "coverageDirectory": "../coverage", - "testEnvironment": "node" + "testEnvironment": "node", + "setupFiles": [ + "./test/jest-setup.ts" + ] } } diff --git a/teammapper-backend/src/config.service.ts b/teammapper-backend/src/config.service.ts index 45290ca73..e8472223f 100644 --- a/teammapper-backend/src/config.service.ts +++ b/teammapper-backend/src/config.service.ts @@ -51,6 +51,45 @@ class ConfigService { return parseInt(this.getValue('DELETE_AFTER_DAYS', false) || '30') } + public isYjsEnabled(): boolean { + const value = this.getValue('YJS_ENABLED', false) + return value?.toLowerCase() === 'true' + } + + public isWsTrustProxy(): boolean { + const value = this.getValue('WS_TRUST_PROXY', false) + return value?.toLowerCase() === 'true' + } + + public isYjsRateLimitingEnabled(): boolean { + const value = this.getValue('FEATURE_YJS_RATE_LIMITING', false) + return value?.toLowerCase() === 'true' + } + + public getWsGlobalMaxConnections(): number { + return this.parsePositiveInt('WS_GLOBAL_MAX_CONNECTIONS', 500) + } + + public getWsPerIpMaxConnections(): number { + return this.parsePositiveInt('WS_PER_IP_MAX_CONNECTIONS', 50) + } + + public getWsPerIpRateLimit(): number { + return this.parsePositiveInt('WS_PER_IP_RATE_LIMIT', 10) + } + + public getWsPerIpRateWindowMs(): number { + return this.parsePositiveInt('WS_PER_IP_RATE_WINDOW_MS', 10000) + } + + private parsePositiveInt(key: string, fallback: number): number { + const raw = this.getValue(key, false) + if (!raw) return fallback + const parsed = parseInt(raw, 10) + if (isNaN(parsed) || parsed < 1) return fallback + return parsed + } + public getLLMConfig(): LLMProps { return { url: this.getValue('AI_LLM_URL', false), diff --git a/teammapper-backend/src/map/controllers/maps.controller.spec.ts b/teammapper-backend/src/map/controllers/maps.controller.spec.ts index f34b52ae5..87c8440a8 100644 --- a/teammapper-backend/src/map/controllers/maps.controller.spec.ts +++ b/teammapper-backend/src/map/controllers/maps.controller.spec.ts @@ -1,6 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing' import MapsController from './maps.controller' import { MapsService } from '../services/maps.service' +import { YjsDocManagerService } from '../services/yjs-doc-manager.service' +import { YjsGateway } from './yjs-gateway.service' import { NotFoundException } from '@nestjs/common' import { MmpMap } from '../entities/mmpMap.entity' import { IMmpClientMap, IMmpClientPrivateMap, Request } from '../types' @@ -33,6 +35,14 @@ describe('MapsController', () => { getMapsOfUser: jest.fn(), }, }, + { + provide: YjsDocManagerService, + useValue: { destroyDoc: jest.fn() }, + }, + { + provide: YjsGateway, + useValue: { closeConnectionsForMap: jest.fn() }, + }, ], }).compile() @@ -131,7 +141,7 @@ describe('MapsController', () => { }) it('should return an empty array when req is undefined', async () => { - const response = await mapsController.findAll(undefined) + const response = await mapsController.findAll() expect(response).toEqual([]) }) }) @@ -142,7 +152,7 @@ describe('MapsController', () => { jest.spyOn(mapsService, 'findMap').mockResolvedValueOnce(existingMap) // We're not interested in testing the repository at this stage, only if the request gets past the admin ID check - jest.spyOn(mapsService, 'deleteMap').mockImplementation(() => {}) + jest.spyOn(mapsService, 'deleteMap').mockResolvedValue(undefined) await mapsController.delete(existingMap.id, { adminId: existingMap.adminId, diff --git a/teammapper-backend/src/map/controllers/maps.controller.ts b/teammapper-backend/src/map/controllers/maps.controller.ts index 409e44b9c..d372cdf4c 100644 --- a/teammapper-backend/src/map/controllers/maps.controller.ts +++ b/teammapper-backend/src/map/controllers/maps.controller.ts @@ -10,6 +10,8 @@ import { Logger, } from '@nestjs/common' import { MapsService } from '../services/maps.service' +import { YjsDocManagerService } from '../services/yjs-doc-manager.service' +import { YjsGateway } from './yjs-gateway.service' import { IMmpClientDeleteRequest, IMmpClientMap, @@ -20,11 +22,16 @@ import { } from '../types' import MalformedUUIDError from '../services/uuid.error' import { EntityNotFoundError } from 'typeorm' +import configService from '../../config.service' @Controller('api/maps') export default class MapsController { private readonly logger = new Logger(MapsController.name) - constructor(private mapsService: MapsService) {} + constructor( + private mapsService: MapsService, + private yjsDocManager: YjsDocManagerService, + private yjsGateway: YjsGateway + ) {} @Get(':id') async findOne(@Param('id') mapId: string): Promise { @@ -59,8 +66,13 @@ export default class MapsController { @Body() body: IMmpClientDeleteRequest ): Promise { const mmpMap = await this.mapsService.findMap(mapId) - if (mmpMap && mmpMap.adminId === body.adminId) - this.mapsService.deleteMap(mapId) + if (mmpMap && mmpMap.adminId === body.adminId) { + if (configService.isYjsEnabled()) { + this.yjsGateway.closeConnectionsForMap(mapId) + this.yjsDocManager.destroyDoc(mapId) + } + await this.mapsService.deleteMap(mapId) + } } @Post() diff --git a/teammapper-backend/src/map/controllers/maps.gateway.ts b/teammapper-backend/src/map/controllers/maps.gateway.ts index 35dc25b92..0a948d4c2 100644 --- a/teammapper-backend/src/map/controllers/maps.gateway.ts +++ b/teammapper-backend/src/map/controllers/maps.gateway.ts @@ -46,7 +46,8 @@ export class MapsGateway implements OnGatewayDisconnect { server: Server private readonly logger = new Logger(MapsService.name) - private readonly CACHE_TTL_MS = 10000 + // 24 hours – entries are cleaned up explicitly on disconnect + private readonly CACHE_TTL_MS = 86_400_000 constructor( private mapsService: MapsService, @@ -142,7 +143,7 @@ export class MapsGateway implements OnGatewayDisconnect { request.mapId ) if (mmpMap && mmpMap.adminId === request.adminId) { - this.mapsService.deleteMap(request.mapId) + await this.mapsService.deleteMap(request.mapId) this.server.to(request.mapId).emit('mapDeleted') return true } @@ -763,7 +764,7 @@ export class MapsGateway implements OnGatewayDisconnect { color: string ): Promise { client.join(mapId) - this.cacheManager.set(client.id, mapId, this.CACHE_TTL_MS) + 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/controllers/yjs-gateway.service.spec.ts b/teammapper-backend/src/map/controllers/yjs-gateway.service.spec.ts new file mode 100644 index 000000000..c3c81dc00 --- /dev/null +++ b/teammapper-backend/src/map/controllers/yjs-gateway.service.spec.ts @@ -0,0 +1,703 @@ +import { YjsGateway } from './yjs-gateway.service' +import { YjsDocManagerService } from '../services/yjs-doc-manager.service' +import { YjsPersistenceService } from '../services/yjs-persistence.service' +import { MapsService } from '../services/maps.service' +import { WsConnectionLimiterService } from '../services/ws-connection-limiter.service' +import { MmpMap } from '../entities/mmpMap.entity' +import { WebSocket } from 'ws' +import * as Y from 'yjs' +import { jest } from '@jest/globals' +import type { IncomingMessage } from 'http' +import type { HttpAdapterHost } from '@nestjs/core' +import { + WS_CLOSE_MISSING_PARAM, + WS_CLOSE_MAP_DELETED, + WS_CLOSE_MAP_NOT_FOUND, + WS_CLOSE_TRY_AGAIN, + CONNECTION_SETUP_TIMEOUT_MS, + encodeSyncStep1Message, + encodeSyncUpdateMessage, +} from '../utils/yjsProtocol' + +jest.mock('../../config.service', () => ({ + __esModule: true, + default: { + isYjsEnabled: jest.fn(() => true), + }, +})) + +// Minimal interface to call private methods in tests +interface ConnectionHandler { + handleConnection(ws: MockWs, req: IncomingMessage): Promise +} + +interface HeartbeatRunner { + runHeartbeat(): void +} + +const createMockMap = (secret: string | null = 'test-secret'): MmpMap => { + const map = new MmpMap() + map.id = 'map-1' + map.name = 'Test Map' + map.modificationSecret = secret + map.options = { fontMaxSize: 28, fontMinSize: 6, fontIncrement: 2 } + return map +} + +interface MockWs { + on: jest.Mock + close: jest.Mock + send: jest.Mock + terminate: jest.Mock + ping: jest.Mock + readyState: number + _triggerClose: () => void + _triggerMessage: (data: Uint8Array) => void + _triggerError: (error: Error) => void + _triggerPong: () => void +} + +type WsEventHandler = (...args: unknown[]) => void + +const createMockWs = (): MockWs => { + const handlers = new Map() + return { + on: jest.fn((event: string, handler: WsEventHandler) => { + if (!handlers.has(event)) handlers.set(event, []) + handlers.get(event)!.push(handler) + }), + close: jest.fn(), + send: jest.fn((_data: Uint8Array | Buffer, cb?: (err?: Error) => void) => { + if (cb) cb() + }), + terminate: jest.fn(), + ping: jest.fn(), + readyState: WebSocket.OPEN, + _triggerClose: () => { + handlers.get('close')?.forEach((h) => h()) + }, + _triggerMessage: (data: Uint8Array) => { + handlers.get('message')?.forEach((h) => h(Buffer.from(data))) + }, + _triggerError: (error: Error) => { + handlers.get('error')?.forEach((h) => h(error)) + }, + _triggerPong: () => { + handlers.get('pong')?.forEach((h) => h()) + }, + } +} + +const createMockRequest = ( + mapId: string | null, + secret: string | null = null, + ip: string = '127.0.0.1' +): IncomingMessage => { + const params = new URLSearchParams() + if (mapId) params.set('mapId', mapId) + if (secret) params.set('secret', secret) + return { + url: `/yjs?${params.toString()}`, + socket: { remoteAddress: ip }, + headers: {}, + } as unknown as IncomingMessage +} + +const connectClient = async ( + gateway: YjsGateway, + ws: MockWs, + req: IncomingMessage +) => { + const handler = gateway as unknown as ConnectionHandler + await handler.handleConnection(ws, req) +} + +describe('YjsGateway', () => { + let gateway: YjsGateway + let mapsService: jest.Mocked + let docManager: jest.Mocked + let limiter: jest.Mocked + let doc: Y.Doc + + beforeEach(() => { + doc = new Y.Doc() + + mapsService = { + findMap: jest.fn(), + } as unknown as jest.Mocked + + docManager = { + getOrCreateDoc: jest + .fn() + .mockResolvedValue(doc), + getDoc: jest.fn().mockReturnValue(doc), + notifyClientCount: jest + .fn() + .mockResolvedValue(undefined), + destroyDoc: jest.fn(), + hasDoc: jest.fn().mockReturnValue(true), + restoreGraceTimer: jest.fn(), + } as unknown as jest.Mocked + + const persistenceService = { + registerDebounce: jest.fn(), + unregisterDebounce: jest.fn(), + } as unknown as jest.Mocked + + limiter = { + checkLimits: jest.fn().mockReturnValue(null), + releaseConnection: jest.fn(), + cleanupExpiredRateWindows: jest.fn(), + getClientIp: jest.fn().mockReturnValue('127.0.0.1'), + reset: jest.fn(), + onModuleDestroy: jest.fn(), + } as unknown as jest.Mocked + + const httpAdapterHost: Pick = { + httpAdapter: { + getHttpServer: jest.fn().mockReturnValue({ on: jest.fn() }), + } as unknown as HttpAdapterHost['httpAdapter'], + } + + gateway = new YjsGateway( + httpAdapterHost as HttpAdapterHost, + docManager, + persistenceService, + mapsService, + limiter + ) + }) + + afterEach(() => { + gateway.onModuleDestroy() + doc.destroy() + jest.restoreAllMocks() + }) + + describe('connection with valid map ID', () => { + it('creates doc and notifies client count', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(docManager.getOrCreateDoc).toHaveBeenCalledWith('map-1') + expect(docManager.notifyClientCount).toHaveBeenCalledWith('map-1', 1) + }) + + it('sends sync message on connection', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(ws.send).toHaveBeenCalled() + }) + }) + + describe('connection with invalid map ID', () => { + it('closes connection when map not found', async () => { + mapsService.findMap.mockResolvedValue(null) + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest('nonexistent')) + + expect(ws.close).toHaveBeenCalledWith( + WS_CLOSE_MAP_NOT_FOUND, + 'Map not found' + ) + }) + + it('does not create doc when map not found', async () => { + mapsService.findMap.mockResolvedValue(null) + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest('nonexistent')) + + expect(docManager.getOrCreateDoc).not.toHaveBeenCalled() + }) + + it('releases limiter slot when map not found', async () => { + mapsService.findMap.mockResolvedValue(null) + limiter.getClientIp.mockReturnValue('10.0.0.1') + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest('nonexistent')) + + expect(limiter.releaseConnection).toHaveBeenCalledWith('10.0.0.1') + }) + }) + + describe('connection with missing mapId parameter', () => { + it('closes connection with error', async () => { + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest(null)) + + expect(ws.close).toHaveBeenCalledWith( + WS_CLOSE_MISSING_PARAM, + 'Missing mapId' + ) + }) + + it('releases limiter slot when mapId missing', async () => { + limiter.getClientIp.mockReturnValue('10.0.0.1') + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest(null)) + + expect(limiter.releaseConnection).toHaveBeenCalledWith('10.0.0.1') + }) + }) + + describe('read-only client cannot write', () => { + it('allows sync step 1 from read-only client', async () => { + mapsService.findMap.mockResolvedValue(createMockMap('secret-123')) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'wrong-secret') + ) + ws.send.mockClear() + + const clientDoc = new Y.Doc() + ws._triggerMessage(encodeSyncStep1Message(clientDoc)) + + expect(ws.send).toHaveBeenCalled() + clientDoc.destroy() + }) + + it('drops sync update messages from read-only client', async () => { + mapsService.findMap.mockResolvedValue(createMockMap('secret-123')) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'wrong-secret') + ) + + doc.getMap('nodes').set('existing', new Y.Map()) + const initialSize = doc.getMap('nodes').size + ws.send.mockClear() + + const clientDoc = new Y.Doc() + clientDoc.getMap('nodes').set('new-node', new Y.Map()) + const update = Y.encodeStateAsUpdate(clientDoc) + ws._triggerMessage(encodeSyncUpdateMessage(update)) + + expect(doc.getMap('nodes').size).toBe(initialSize) + clientDoc.destroy() + }) + }) + + describe('read-write client can write', () => { + it('applies sync messages from read-write client', async () => { + mapsService.findMap.mockResolvedValue(createMockMap('secret-123')) + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest('map-1', 'secret-123')) + + const clientDoc = new Y.Doc() + ws._triggerMessage(encodeSyncStep1Message(clientDoc)) + + expect(ws.send).toHaveBeenCalled() + clientDoc.destroy() + }) + + it('allows writes when map has no modification secret', async () => { + mapsService.findMap.mockResolvedValue(createMockMap(null)) + const ws = createMockWs() + + await connectClient(gateway, ws, createMockRequest('map-1')) + ws.send.mockClear() + + const clientDoc = new Y.Doc() + clientDoc.getMap('nodes').set('new-node', new Y.Map()) + const update = Y.encodeStateAsUpdate(clientDoc) + ws._triggerMessage(encodeSyncUpdateMessage(update)) + + expect(doc.getMap('nodes').has('new-node')).toBe(true) + clientDoc.destroy() + }) + }) + + describe('connection close handler', () => { + it('notifies doc manager with remaining count on disconnect', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + docManager.notifyClientCount.mockClear() + ws._triggerClose() + + // Allow the async notifyClientCount promise to settle + await new Promise((r) => setTimeout(r, 0)) + + expect(docManager.notifyClientCount).toHaveBeenCalledWith('map-1', 0) + }) + + it('passes correct remaining count with multiple clients', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws1 = createMockWs() + const ws2 = createMockWs() + + await connectClient( + gateway, + ws1, + createMockRequest('map-1', 'test-secret') + ) + await connectClient( + gateway, + ws2, + createMockRequest('map-1', 'test-secret') + ) + docManager.notifyClientCount.mockClear() + ws1._triggerClose() + + await new Promise((r) => setTimeout(r, 0)) + + expect(docManager.notifyClientCount).toHaveBeenCalledWith('map-1', 1) + }) + + it('delegates to limiter on connection close', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + limiter.getClientIp.mockReturnValue('10.0.0.1') + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret', '10.0.0.1') + ) + ws._triggerClose() + + expect(limiter.releaseConnection).toHaveBeenCalledWith('10.0.0.1') + }) + }) + + describe('map deletion closes connections', () => { + it('closes all WebSocket connections for a map', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws1 = createMockWs() + const ws2 = createMockWs() + + await connectClient( + gateway, + ws1, + createMockRequest('map-1', 'test-secret') + ) + await connectClient( + gateway, + ws2, + createMockRequest('map-1', 'test-secret') + ) + + gateway.closeConnectionsForMap('map-1') + + expect(ws1.close).toHaveBeenCalledWith( + WS_CLOSE_MAP_DELETED, + 'Map deleted' + ) + expect(ws2.close).toHaveBeenCalledWith( + WS_CLOSE_MAP_DELETED, + 'Map deleted' + ) + }) + + it('does nothing for non-existent map', () => { + gateway.closeConnectionsForMap('nonexistent') + }) + }) + + describe('WebSocket error handling', () => { + it('terminates connection on error event', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + ws._triggerError(new Error('ECONNRESET')) + + expect(ws.terminate).toHaveBeenCalled() + }) + + it('does not crash the server process on error', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(() => ws._triggerError(new Error('write EPIPE'))).not.toThrow() + }) + }) + + describe('ping/pong heartbeat', () => { + const runHeartbeat = (gw: YjsGateway) => + (gw as unknown as HeartbeatRunner).runHeartbeat() + + it('pings on first heartbeat', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + runHeartbeat(gateway) + + expect(ws.ping).toHaveBeenCalledTimes(1) + }) + + it('terminates zombie on second heartbeat without pong', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + runHeartbeat(gateway) + runHeartbeat(gateway) + + expect(ws.terminate).toHaveBeenCalled() + }) + + it('survives after pong response', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + runHeartbeat(gateway) + ws._triggerPong() + runHeartbeat(gateway) + + expect(ws.terminate).not.toHaveBeenCalled() + }) + + it('pings on each heartbeat cycle', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + runHeartbeat(gateway) + ws._triggerPong() + runHeartbeat(gateway) + + expect(ws.ping).toHaveBeenCalledTimes(2) + }) + + it('delegates rate window cleanup to limiter', async () => { + runHeartbeat(gateway) + + expect(limiter.cleanupExpiredRateWindows).toHaveBeenCalled() + }) + + it('clears heartbeat interval on shutdown', () => { + gateway.onModuleInit() + gateway.onModuleDestroy() + + // After destroy, no further heartbeat runs + // (verified by no errors thrown after cleanup) + }) + }) + + describe('connection setup timeout', () => { + beforeEach(() => { + jest.useFakeTimers() + }) + + afterEach(() => { + jest.useRealTimers() + }) + + it('closes with 1013 when setup exceeds timeout', async () => { + mapsService.findMap.mockReturnValue(new Promise(() => {})) + const ws = createMockWs() + + const promise = connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + jest.advanceTimersByTime(CONNECTION_SETUP_TIMEOUT_MS + 1) + await promise + + expect(ws.close).toHaveBeenCalledWith( + WS_CLOSE_TRY_AGAIN, + 'Connection setup timeout' + ) + expect(limiter.releaseConnection).toHaveBeenCalledWith('127.0.0.1') + }) + + it('completes normally when setup finishes within timeout', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(ws.close).not.toHaveBeenCalledWith( + WS_CLOSE_TRY_AGAIN, + expect.any(String) + ) + expect(docManager.getOrCreateDoc).toHaveBeenCalledWith('map-1') + }) + + it('cancels timer on early completion', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + jest.advanceTimersByTime(15_000) + await Promise.resolve() + + expect(ws.close).not.toHaveBeenCalledWith( + WS_CLOSE_TRY_AGAIN, + expect.any(String) + ) + }) + }) + + describe('multiple maps isolation', () => { + it('connections to different maps are independent', async () => { + const map1 = createMockMap() + const map2 = createMockMap() + map2.id = 'map-2' + + const doc2 = new Y.Doc() + mapsService.findMap.mockImplementation(async (id: string) => { + return id === 'map-1' ? map1 : map2 + }) + docManager.getOrCreateDoc.mockImplementation(async (id: string) => { + return id === 'map-1' ? doc : doc2 + }) + + const ws1 = createMockWs() + const ws2 = createMockWs() + + await connectClient( + gateway, + ws1, + createMockRequest('map-1', 'test-secret') + ) + await connectClient( + gateway, + ws2, + createMockRequest('map-2', 'test-secret') + ) + + gateway.closeConnectionsForMap('map-1') + + expect(ws1.close).toHaveBeenCalled() + expect(ws2.close).not.toHaveBeenCalled() + + doc2.destroy() + }) + }) + + describe('notifyClientCount error handling', () => { + it('logs persistence errors on disconnect without crashing', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + // Mock to reject for the close handler call + docManager.notifyClientCount.mockRejectedValue( + new Error('DB connection lost') + ) + + // Close should not throw even when notifyClientCount rejects + expect(() => ws._triggerClose()).not.toThrow() + + // Allow the promise rejection to be caught + await new Promise((r) => setTimeout(r, 0)) + + expect(docManager.notifyClientCount).toHaveBeenCalledWith('map-1', 0) + }) + }) + + describe('grace timer restoration on setup failure', () => { + it('restores grace timer when notifyClientCount throws', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + docManager.notifyClientCount.mockRejectedValue( + new Error('Unexpected error') + ) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(docManager.restoreGraceTimer).toHaveBeenCalledWith( + 'map-1', + expect.any(Number) + ) + }) + + it('does not restore grace timer on successful setup', async () => { + mapsService.findMap.mockResolvedValue(createMockMap()) + const ws = createMockWs() + + await connectClient( + gateway, + ws, + createMockRequest('map-1', 'test-secret') + ) + + expect(docManager.restoreGraceTimer).not.toHaveBeenCalled() + }) + }) +}) diff --git a/teammapper-backend/src/map/controllers/yjs-gateway.service.ts b/teammapper-backend/src/map/controllers/yjs-gateway.service.ts new file mode 100644 index 000000000..703830ba3 --- /dev/null +++ b/teammapper-backend/src/map/controllers/yjs-gateway.service.ts @@ -0,0 +1,553 @@ +import { + Injectable, + Logger, + OnModuleInit, + OnModuleDestroy, +} from '@nestjs/common' +import { HttpAdapterHost } from '@nestjs/core' +import { WebSocketServer, WebSocket } from 'ws' +import type { IncomingMessage } from 'http' +import type { Duplex } from 'stream' +import * as Y from 'yjs' +import * as syncProtocol from 'y-protocols/sync' +import * as awarenessProtocol from 'y-protocols/awareness' +import * as encoding from 'lib0/encoding' +import * as decoding from 'lib0/decoding' +import configService from '../../config.service' +import { YjsDocManagerService } from '../services/yjs-doc-manager.service' +import { YjsPersistenceService } from '../services/yjs-persistence.service' +import { MapsService } from '../services/maps.service' +import { WsConnectionLimiterService } from '../services/ws-connection-limiter.service' +import { + MESSAGE_SYNC, + MESSAGE_AWARENESS, + WS_CLOSE_MISSING_PARAM, + WS_CLOSE_MAP_DELETED, + WS_CLOSE_MAP_NOT_FOUND, + WS_CLOSE_INTERNAL_ERROR, + WS_CLOSE_SERVER_SHUTDOWN, + WS_MAX_PAYLOAD, + HEARTBEAT_INTERVAL_MS, + CONNECTION_SETUP_TIMEOUT_MS, + WS_CLOSE_TRY_AGAIN, + ConnectionMeta, + extractPathname, + parseQueryParams, + checkWriteAccess, + toUint8Array, + encodeSyncStep1Message, + encodeSyncUpdateMessage, + encodeAwarenessMessage, + encodeWriteAccessMessage, + processReadOnlySyncMessage, + parseAwarenessClientIds, +} from '../utils/yjsProtocol' + +@Injectable() +export class YjsGateway implements OnModuleInit, OnModuleDestroy { + private readonly logger = new Logger(YjsGateway.name) + private wss: WebSocketServer | null = null + private heartbeatInterval: ReturnType | null = null + + // Connections per map + private readonly mapConnections = new Map>() + // Metadata per connection + private readonly connectionMeta = new Map() + // Awareness instances per map + private readonly awarenessInstances = new Map< + string, + awarenessProtocol.Awareness + >() + // Doc update handlers per map (for cleanup) + private readonly docUpdateHandlers = new Map< + string, + (update: Uint8Array, origin: unknown) => void + >() + + constructor( + private readonly httpAdapterHost: HttpAdapterHost, + private readonly docManager: YjsDocManagerService, + private readonly persistenceService: YjsPersistenceService, + private readonly mapsService: MapsService, + private readonly limiter: WsConnectionLimiterService + ) {} + + onModuleInit(): void { + if (!configService.isYjsEnabled()) { + this.logger.log('Yjs disabled, skipping WebSocket server setup') + return + } + + const server = this.httpAdapterHost.httpAdapter.getHttpServer() + this.wss = new WebSocketServer({ + noServer: true, + maxPayload: WS_MAX_PAYLOAD, + }) + + server.on( + 'upgrade', + (request: IncomingMessage, socket: Duplex, head: Buffer) => { + if (!extractPathname(request.url).startsWith('/yjs')) return + + const rejection = this.limiter.checkLimits(request) + if (rejection) { + socket.write( + `HTTP/1.1 ${rejection.status} ${rejection.reason}\r\n\r\n` + ) + socket.destroy() + return + } + + this.wss!.handleUpgrade(request, socket, head, (ws) => { + this.wss!.emit('connection', ws, request) + }) + } + ) + + this.wss.on('connection', (ws: WebSocket, req: IncomingMessage) => { + this.handleConnection(ws, req).catch((err) => + this.logger.error( + `Unhandled connection error: ${err instanceof Error ? err.message : String(err)}` + ) + ) + }) + + this.heartbeatInterval = setInterval( + () => this.runHeartbeat(), + HEARTBEAT_INTERVAL_MS + ) + + this.logger.log('Yjs WebSocket server started on /yjs') + } + + onModuleDestroy(): void { + this.cleanup() + } + + // Close all WebSocket connections for a specific map (used on map deletion) + closeConnectionsForMap(mapId: string): void { + const connections = this.mapConnections.get(mapId) + if (!connections) return + + for (const ws of connections) { + ws.close(WS_CLOSE_MAP_DELETED, 'Map deleted') + } + } + + private runHeartbeat(): void { + const zombies: WebSocket[] = [] + + for (const [ws, meta] of this.connectionMeta) { + if (!meta.isAlive) { + zombies.push(ws) + continue + } + + this.connectionMeta.set(ws, { ...meta, isAlive: false }) + ws.ping() + } + + for (const ws of zombies) { + ws.terminate() + } + + this.limiter.cleanupExpiredRateWindows() + } + + private async handleConnection( + ws: WebSocket, + req: IncomingMessage + ): Promise { + const ip = this.limiter.getClientIp(req) + const controller = new AbortController() + const timeoutId = setTimeout( + () => controller.abort(), + CONNECTION_SETUP_TIMEOUT_MS + ) + + try { + await Promise.race([ + this.performConnectionSetup(ws, ip, req, controller.signal), + this.rejectOnAbort(controller.signal), + ]) + } catch { + this.limiter.releaseConnection(ip) + ws.close(WS_CLOSE_TRY_AGAIN, 'Connection setup timeout') + } finally { + clearTimeout(timeoutId) + } + } + + private rejectOnAbort(signal: AbortSignal): Promise { + return new Promise((_, reject) => { + signal.addEventListener( + 'abort', + () => reject(new Error('Connection setup timeout')), + { once: true } + ) + }) + } + + private rejectConnection( + ws: WebSocket, + ip: string, + code: number, + reason: string + ): void { + this.limiter.releaseConnection(ip) + ws.close(code, reason) + } + + private async performConnectionSetup( + ws: WebSocket, + ip: string, + req: IncomingMessage, + signal: AbortSignal + ): Promise { + try { + const { mapId, secret } = parseQueryParams(req.url) + if (!mapId) { + this.rejectConnection(ws, ip, WS_CLOSE_MISSING_PARAM, 'Missing mapId') + return + } + + const map = await this.mapsService.findMap(mapId) + if (signal.aborted) return + if (!map) { + this.rejectConnection(ws, ip, WS_CLOSE_MAP_NOT_FOUND, 'Map not found') + return + } + + const doc = await this.docManager.getOrCreateDoc(mapId) + if (signal.aborted) { + const count = this.mapConnections.get(mapId)?.size ?? 0 + this.docManager.restoreGraceTimer(mapId, count) + return + } + + let setupComplete = false + try { + const writable = checkWriteAccess(map.modificationSecret, secret) + this.trackConnection(ws, mapId, writable, ip) + const count = this.mapConnections.get(mapId)!.size + await this.docManager.notifyClientCount(mapId, count) + this.setupSync(ws, doc, mapId, writable) + setupComplete = true + } finally { + if (!setupComplete) { + const count = this.mapConnections.get(mapId)?.size ?? 0 + this.docManager.restoreGraceTimer(mapId, count) + } + } + } catch (error) { + if (signal.aborted) return + this.logger.error( + `Connection error: ${error instanceof Error ? error.message : String(error)}` + ) + this.rejectConnection(ws, ip, WS_CLOSE_INTERNAL_ERROR, 'Internal error') + } + } + + private trackConnection( + ws: WebSocket, + mapId: string, + writable: boolean, + ip: string + ): void { + if (!this.mapConnections.has(mapId)) { + this.mapConnections.set(mapId, new Set()) + } + this.mapConnections.get(mapId)!.add(ws) + this.connectionMeta.set(ws, { + mapId, + writable, + awarenessClientIds: new Set(), + isAlive: true, + ip, + }) + } + + private getOrCreateAwareness( + mapId: string, + doc: Y.Doc + ): awarenessProtocol.Awareness { + const existing = this.awarenessInstances.get(mapId) + if (existing) return existing + + const awareness = new awarenessProtocol.Awareness(doc) + awareness.setLocalState(null) + this.setupAwarenessBroadcast(mapId, awareness) + this.awarenessInstances.set(mapId, awareness) + return awareness + } + + private setupAwarenessBroadcast( + mapId: string, + awareness: awarenessProtocol.Awareness + ): void { + awareness.on( + 'update', + ({ + added, + updated, + removed, + }: { + added: number[] + updated: number[] + removed: number[] + }) => { + const changedClients = [...added, ...updated, ...removed] + if (changedClients.length === 0) return + + const message = encodeAwarenessMessage(awareness, changedClients) + this.broadcastToMap(mapId, message, null) + } + ) + } + + private setupSync( + ws: WebSocket, + doc: Y.Doc, + mapId: string, + writable: boolean + ): void { + const awareness = this.getOrCreateAwareness(mapId, doc) + + this.ensureDocUpdateHandler(mapId, doc) + this.persistenceService.registerDebounce(mapId, doc) + + ws.on('message', (data: Buffer | ArrayBuffer | Buffer[]) => { + this.handleMessage( + ws, + doc, + awareness, + mapId, + writable, + toUint8Array(data) + ) + }) + + ws.on('pong', () => { + const meta = this.connectionMeta.get(ws) + if (meta) this.connectionMeta.set(ws, { ...meta, isAlive: true }) + }) + + ws.on('error', (error: Error) => { + this.logger.error(`WebSocket error on map ${mapId}: ${error.message}`) + ws.terminate() + }) + + ws.on('close', () => { + this.handleClose(ws, mapId, awareness) + }) + + this.send(ws, encodeSyncStep1Message(doc)) + this.sendAwarenessStates(ws, awareness) + this.send(ws, encodeWriteAccessMessage(writable)) + } + + private handleMessage( + ws: WebSocket, + doc: Y.Doc, + awareness: awarenessProtocol.Awareness, + mapId: string, + writable: boolean, + data: Uint8Array + ): void { + try { + const decoder = decoding.createDecoder(data) + const messageType = decoding.readVarUint(decoder) + + switch (messageType) { + case MESSAGE_SYNC: + this.handleSyncMessage(ws, doc, decoder, writable) + break + case MESSAGE_AWARENESS: + this.handleAwarenessMessage(ws, awareness, mapId, decoder) + break + } + } catch (error) { + this.logger.error( + `Message handling error: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + private handleSyncMessage( + ws: WebSocket, + doc: Y.Doc, + decoder: decoding.Decoder, + writable: boolean + ): void { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, MESSAGE_SYNC) + + if (writable) { + syncProtocol.readSyncMessage(decoder, encoder, doc, null) + } else { + processReadOnlySyncMessage(decoder, encoder, doc) + } + + if (encoding.length(encoder) > 1) { + this.send(ws, encoding.toUint8Array(encoder)) + } + } + + private handleAwarenessMessage( + ws: WebSocket, + awareness: awarenessProtocol.Awareness, + mapId: string, + decoder: decoding.Decoder + ): void { + const update = decoding.readVarUint8Array(decoder) + const meta = this.connectionMeta.get(ws) + + this.trackAwarenessClients(meta, update) + awarenessProtocol.applyAwarenessUpdate(awareness, update, ws) + } + + private trackAwarenessClients( + meta: ConnectionMeta | undefined, + update: Uint8Array + ): void { + if (!meta) return + try { + for (const clientId of parseAwarenessClientIds(update)) { + meta.awarenessClientIds.add(clientId) + } + } catch (error) { + this.logger.debug( + `Awareness tracking parse error: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + private handleClose( + ws: WebSocket, + mapId: string, + awareness: awarenessProtocol.Awareness + ): void { + const meta = this.connectionMeta.get(ws) + + if (meta && meta.awarenessClientIds.size > 0) { + awarenessProtocol.removeAwarenessStates( + awareness, + Array.from(meta.awarenessClientIds), + null + ) + } + + if (meta) { + this.limiter.releaseConnection(meta.ip) + } + + this.connectionMeta.delete(ws) + const connections = this.mapConnections.get(mapId) + if (connections) { + connections.delete(ws) + } + + const remainingCount = this.mapConnections.get(mapId)?.size ?? 0 + + if (remainingCount === 0) { + this.mapConnections.delete(mapId) + this.cleanupMapResources(mapId) + } + + this.docManager.notifyClientCount(mapId, remainingCount).catch((error) => { + this.logger.error( + `Persistence error during disconnect for map ${mapId}: ${error instanceof Error ? error.message : String(error)}` + ) + }) + } + + private cleanupMapResources(mapId: string): void { + const handler = this.docUpdateHandlers.get(mapId) + if (handler) { + const doc = this.docManager.getDoc(mapId) + if (doc) doc.off('update', handler) + this.docUpdateHandlers.delete(mapId) + } + + const awareness = this.awarenessInstances.get(mapId) + if (awareness) { + awareness.destroy() + this.awarenessInstances.delete(mapId) + } + + this.persistenceService.unregisterDebounce(mapId) + } + + private ensureDocUpdateHandler(mapId: string, doc: Y.Doc): void { + if (this.docUpdateHandlers.has(mapId)) return + + const handler = (update: Uint8Array, origin: unknown): void => { + const message = encodeSyncUpdateMessage(update) + this.broadcastToMap( + mapId, + message, + origin instanceof WebSocket ? origin : null + ) + } + + doc.on('update', handler) + this.docUpdateHandlers.set(mapId, handler) + } + + private sendAwarenessStates( + ws: WebSocket, + awareness: awarenessProtocol.Awareness + ): void { + const states = awareness.getStates() + if (states.size === 0) return + + this.send(ws, encodeAwarenessMessage(awareness, Array.from(states.keys()))) + } + + private broadcastToMap( + mapId: string, + message: Uint8Array, + exclude: WebSocket | null + ): void { + const connections = this.mapConnections.get(mapId) + if (!connections) return + + for (const ws of connections) { + if (ws !== exclude && ws.readyState === WebSocket.OPEN) { + this.send(ws, message) + } + } + } + + private send(ws: WebSocket, message: Uint8Array): void { + if (ws.readyState === WebSocket.OPEN) { + ws.send(message, (err) => { + if (err) { + this.logger.error(`Send error: ${err.message}`) + } + }) + } + } + + private cleanup(): void { + for (const [mapId, connections] of this.mapConnections) { + for (const ws of connections) { + ws.close(WS_CLOSE_SERVER_SHUTDOWN, 'Server shutting down') + } + this.cleanupMapResources(mapId) + } + this.mapConnections.clear() + this.connectionMeta.clear() + this.limiter.reset() + + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval) + this.heartbeatInterval = null + } + + if (this.wss) { + this.wss.close() + this.wss = null + } + } +} diff --git a/teammapper-backend/src/map/map.module.ts b/teammapper-backend/src/map/map.module.ts index 39709a144..2f4d142f9 100644 --- a/teammapper-backend/src/map/map.module.ts +++ b/teammapper-backend/src/map/map.module.ts @@ -1,4 +1,4 @@ -import { MiddlewareConsumer, Module } from '@nestjs/common' +import { MiddlewareConsumer, Module, Provider } from '@nestjs/common' import { CacheModule } from '@nestjs/cache-manager' import { ScheduleModule } from '@nestjs/schedule' import { TypeOrmModule } from '@nestjs/typeorm' @@ -7,11 +7,32 @@ import { MapsGateway } from './controllers/maps.gateway' import { MmpMap } from './entities/mmpMap.entity' import { MmpNode } from './entities/mmpNode.entity' import { MapsService } from './services/maps.service' +import { YjsDocManagerService } from './services/yjs-doc-manager.service' +import { YjsPersistenceService } from './services/yjs-persistence.service' +import { YjsGateway } from './controllers/yjs-gateway.service' +import { WsConnectionLimiterService } from './services/ws-connection-limiter.service' import { TasksService } from './services/tasks.service' import MermaidController from './controllers/mermaid.controller' import { AiService } from './services/ai.service' import cookieParser from 'cookie-parser' import { PersonIdMiddleware } from '../auth/person-id.middleware' +import configService from '../config.service' + +// When Yjs is enabled, the Socket.io gateway (MapsGateway) must be excluded +// because both cannot bind to the same HTTP upgrade path simultaneously. +const baseProviders: Provider[] = [ + MapsService, + YjsDocManagerService, + YjsPersistenceService, + WsConnectionLimiterService, + YjsGateway, + TasksService, + AiService, +] + +const mapProviders: Provider[] = configService.isYjsEnabled() + ? baseProviders + : [...baseProviders, MapsGateway] @Module({ imports: [ @@ -20,7 +41,7 @@ import { PersonIdMiddleware } from '../auth/person-id.middleware' ScheduleModule.forRoot(), ], controllers: [MapsController, MermaidController], - providers: [MapsService, MapsGateway, TasksService, AiService], + providers: mapProviders, exports: [MapsService], }) export class MapModule { diff --git a/teammapper-backend/src/map/services/maps.service.spec.ts b/teammapper-backend/src/map/services/maps.service.spec.ts index 23779358b..5f6388ef3 100644 --- a/teammapper-backend/src/map/services/maps.service.spec.ts +++ b/teammapper-backend/src/map/services/maps.service.spec.ts @@ -940,10 +940,37 @@ describe('MapsService', () => { describe('getMapsOfUser', () => { it('returns [] if no userId is provided', async () => { + await mapsRepo.save({ ownerExternalId: undefined }) const result = await mapsService.getMapsOfUser('') expect(result).toEqual([]) }) + it('returns [] if the id is the string undefined', async () => { + await mapsRepo.save({ ownerExternalId: undefined }) + const result = await mapsService.getMapsOfUser( + undefined as unknown as string + ) + expect(result).toEqual([]) + }) + + it('returns [] if the id is the string null', async () => { + await mapsRepo.save({ ownerExternalId: null }) + const result = await mapsService.getMapsOfUser(null as unknown as string) + expect(result).toEqual([]) + }) + + it('returns [] if the id is an empty string', async () => { + await mapsRepo.save({ ownerExternalId: undefined }) + const result = await mapsService.getMapsOfUser('') + expect(result).toEqual([]) + }) + + it('returns [] if the id is an empty array', async () => { + await mapsRepo.save({ ownerExternalId: undefined }) + const result = await mapsService.getMapsOfUser([] as unknown as string) + expect(result).toEqual([]) + }) + it('returns only maps belonging to the given userId', async () => { const user1 = 'user1' const user2 = 'user2' diff --git a/teammapper-backend/src/map/services/maps.service.ts b/teammapper-backend/src/map/services/maps.service.ts index a663e66df..4dc2ebd3f 100644 --- a/teammapper-backend/src/map/services/maps.service.ts +++ b/teammapper-backend/src/map/services/maps.service.ts @@ -1037,8 +1037,8 @@ export class MapsService { return 0 } - deleteMap(uuid: string) { - this.mapsRepository.delete({ id: uuid }) + async deleteMap(uuid: string): Promise { + await this.mapsRepository.delete({ id: uuid }) } async validatesNodeParentForNode( diff --git a/teammapper-backend/src/map/services/ws-connection-limiter.service.spec.ts b/teammapper-backend/src/map/services/ws-connection-limiter.service.spec.ts new file mode 100644 index 000000000..616a8f6ff --- /dev/null +++ b/teammapper-backend/src/map/services/ws-connection-limiter.service.spec.ts @@ -0,0 +1,208 @@ +import { WsConnectionLimiterService } from './ws-connection-limiter.service' +import { jest } from '@jest/globals' +import type { IncomingMessage } from 'http' + +jest.mock('../../config.service', () => ({ + __esModule: true, + default: { + isYjsRateLimitingEnabled: jest.fn(() => true), + isWsTrustProxy: jest.fn(() => false), + getWsGlobalMaxConnections: jest.fn(() => 500), + getWsPerIpMaxConnections: jest.fn(() => 50), + getWsPerIpRateLimit: jest.fn(() => 10), + getWsPerIpRateWindowMs: jest.fn(() => 10000), + }, +})) + +import configService from '../../config.service' +const mockedConfig = configService as jest.Mocked + +const createMockRequest = (ip: string = '127.0.0.1'): IncomingMessage => + ({ + socket: { remoteAddress: ip }, + headers: {}, + }) as unknown as IncomingMessage + +const createForwardedRequest = ( + forwardedIp: string, + remoteIp: string = '10.0.0.1' +): IncomingMessage => + ({ + socket: { remoteAddress: remoteIp }, + headers: { 'x-forwarded-for': forwardedIp }, + }) as unknown as IncomingMessage + +// Access private state for test setup +interface LimiterState { + globalConnectionCount: number + perIpConnectionCount: Map + perIpRateWindows: Map +} + +const getState = (service: WsConnectionLimiterService): LimiterState => + service as unknown as LimiterState + +describe('WsConnectionLimiterService', () => { + let service: WsConnectionLimiterService + + beforeEach(() => { + service = new WsConnectionLimiterService() + }) + + afterEach(() => { + service.reset() + mockedConfig.isYjsRateLimitingEnabled.mockReturnValue(true) + mockedConfig.isWsTrustProxy.mockReturnValue(false) + }) + + describe('checkLimits', () => { + it('rejects at global limit with 503', () => { + getState(service).globalConnectionCount = 500 + + const result = service.checkLimits(createMockRequest('10.0.0.1')) + + expect(result).toEqual({ status: 503, reason: 'Service Unavailable' }) + }) + + it('rejects at per-IP limit with 429', () => { + getState(service).perIpConnectionCount.set('10.0.0.1', 50) + + const result = service.checkLimits(createMockRequest('10.0.0.1')) + + expect(result).toEqual({ status: 429, reason: 'Too Many Requests' }) + }) + + it('rejects when rate limited with 429', () => { + const now = Date.now() + const timestamps = Array.from({ length: 10 }, (_, i) => now - i * 100) + getState(service).perIpRateWindows.set('10.0.0.1', timestamps) + + const result = service.checkLimits(createMockRequest('10.0.0.1')) + + expect(result).toEqual({ status: 429, reason: 'Too Many Requests' }) + }) + + it('accepts connection within all limits', () => { + const result = service.checkLimits(createMockRequest('10.0.0.1')) + + expect(result).toBeNull() + }) + + it('increments global count on accept', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + + expect(getState(service).globalConnectionCount).toBe(1) + }) + + it('increments per-IP count on accept', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + + expect(getState(service).perIpConnectionCount.get('10.0.0.1')).toBe(1) + }) + + it('skips all checks when feature flag is disabled', () => { + mockedConfig.isYjsRateLimitingEnabled.mockReturnValue(false) + getState(service).globalConnectionCount = 500 + + expect(service.checkLimits(createMockRequest('10.0.0.1'))).toBeNull() + }) + }) + + describe('releaseConnection', () => { + it('decrements global count', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + service.releaseConnection('10.0.0.1') + + expect(getState(service).globalConnectionCount).toBe(0) + }) + + it('decrements per-IP count', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + service.checkLimits(createMockRequest('10.0.0.1')) + service.releaseConnection('10.0.0.1') + + expect(getState(service).perIpConnectionCount.get('10.0.0.1')).toBe(1) + }) + + it('removes IP entry when count reaches zero', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + service.releaseConnection('10.0.0.1') + + expect(getState(service).perIpConnectionCount.has('10.0.0.1')).toBe(false) + }) + + it('does not go below zero for global count', () => { + service.releaseConnection('10.0.0.1') + + expect(getState(service).globalConnectionCount).toBe(0) + }) + }) + + describe('cleanupExpiredRateWindows', () => { + it('removes expired rate windows', () => { + const expired = Date.now() - 20000 + getState(service).perIpRateWindows.set('10.0.0.1', [expired]) + + service.cleanupExpiredRateWindows() + + expect(getState(service).perIpRateWindows.has('10.0.0.1')).toBe(false) + }) + + it('keeps recent rate windows', () => { + const recent = Date.now() - 100 + getState(service).perIpRateWindows.set('10.0.0.1', [recent]) + + service.cleanupExpiredRateWindows() + + expect(getState(service).perIpRateWindows.has('10.0.0.1')).toBe(true) + }) + + it('skips cleanup when feature flag is disabled', () => { + mockedConfig.isYjsRateLimitingEnabled.mockReturnValue(false) + const expired = Date.now() - 20000 + getState(service).perIpRateWindows.set('10.0.0.1', [expired]) + + service.cleanupExpiredRateWindows() + + expect(getState(service).perIpRateWindows.has('10.0.0.1')).toBe(true) + }) + }) + + describe('getClientIp', () => { + it('uses remoteAddress by default', () => { + const req = createMockRequest('192.168.1.1') + + expect(service.getClientIp(req)).toBe('192.168.1.1') + }) + + it('ignores x-forwarded-for when trust proxy is disabled', () => { + const req = createForwardedRequest('203.0.113.50', '192.168.1.1') + + expect(service.getClientIp(req)).toBe('192.168.1.1') + }) + + it('uses x-forwarded-for when trust proxy is enabled', () => { + mockedConfig.isWsTrustProxy.mockReturnValue(true) + const req = createForwardedRequest('203.0.113.50, 70.41.3.18') + + expect(service.getClientIp(req)).toBe('203.0.113.50') + }) + + it('falls back to remoteAddress when trust proxy enabled but no header', () => { + mockedConfig.isWsTrustProxy.mockReturnValue(true) + const req = createMockRequest('192.168.1.1') + + expect(service.getClientIp(req)).toBe('192.168.1.1') + }) + }) + + describe('reset', () => { + it('clears all state', () => { + service.checkLimits(createMockRequest('10.0.0.1')) + service.reset() + + expect(getState(service).globalConnectionCount).toBe(0) + expect(getState(service).perIpConnectionCount.size).toBe(0) + }) + }) +}) diff --git a/teammapper-backend/src/map/services/ws-connection-limiter.service.ts b/teammapper-backend/src/map/services/ws-connection-limiter.service.ts new file mode 100644 index 000000000..3eb8adf00 --- /dev/null +++ b/teammapper-backend/src/map/services/ws-connection-limiter.service.ts @@ -0,0 +1,144 @@ +import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common' +import type { IncomingMessage } from 'http' +import configService from '../../config.service' + +interface Rejection { + status: number + reason: string +} + +@Injectable() +export class WsConnectionLimiterService implements OnModuleDestroy { + private readonly logger = new Logger(WsConnectionLimiterService.name) + + private globalConnectionCount = 0 + private readonly perIpConnectionCount = new Map() + private readonly perIpRateWindows = new Map() + + onModuleDestroy(): void { + this.reset() + } + + checkLimits(req: IncomingMessage): Rejection | null { + if (!configService.isYjsRateLimitingEnabled()) return null + + const globalRejection = this.checkGlobalLimit() + if (globalRejection) return globalRejection + + const ip = this.getClientIp(req) + const ipRejection = this.checkPerIpLimit(ip) + if (ipRejection) return ipRejection + + const rateRejection = this.checkRateLimit(ip) + if (rateRejection) return rateRejection + + this.acceptConnection(ip) + this.logger.debug( + `Connection accepted — global: ${this.globalConnectionCount}, unique IPs: ${this.perIpConnectionCount.size}` + ) + return null + } + + releaseConnection(ip: string): void { + this.globalConnectionCount = Math.max(0, this.globalConnectionCount - 1) + this.decrementIpCount(ip) + this.logger.debug( + `Connection released — global: ${this.globalConnectionCount}, unique IPs: ${this.perIpConnectionCount.size}` + ) + } + + cleanupExpiredRateWindows(): void { + if (!configService.isYjsRateLimitingEnabled()) return + + const windowMs = configService.getWsPerIpRateWindowMs() + const now = Date.now() + const sizeBefore = this.perIpRateWindows.size + for (const [ip, timestamps] of this.perIpRateWindows) { + const recent = timestamps.filter((t) => now - t < windowMs) + if (recent.length === 0) { + this.perIpRateWindows.delete(ip) + } else { + this.perIpRateWindows.set(ip, recent) + } + } + const removed = sizeBefore - this.perIpRateWindows.size + if (removed > 0) { + this.logger.debug( + `Rate window cleanup — removed: ${removed}, remaining: ${this.perIpRateWindows.size}` + ) + } + } + + getClientIp(req: IncomingMessage): string { + if (configService.isWsTrustProxy()) { + const forwarded = req.headers['x-forwarded-for'] + if (typeof forwarded === 'string') return forwarded.split(',')[0].trim() + } + return req.socket?.remoteAddress ?? 'unknown' + } + + reset(): void { + this.globalConnectionCount = 0 + this.perIpConnectionCount.clear() + this.perIpRateWindows.clear() + } + + private checkGlobalLimit(): Rejection | null { + const globalMax = configService.getWsGlobalMaxConnections() + if (this.globalConnectionCount >= globalMax) { + this.logger.warn( + `Global connection limit reached — max: ${globalMax}, unique IPs: ${this.perIpConnectionCount.size}` + ) + return { status: 503, reason: 'Service Unavailable' } + } + return null + } + + private checkPerIpLimit(ip: string): Rejection | null { + const perIpMax = configService.getWsPerIpMaxConnections() + const currentIpCount = this.perIpConnectionCount.get(ip) ?? 0 + if (currentIpCount >= perIpMax) { + this.logger.warn(`Per-IP connection limit reached — max: ${perIpMax}`) + return { status: 429, reason: 'Too Many Requests' } + } + return null + } + + private checkRateLimit(ip: string): Rejection | null { + if (this.isRateLimited(ip)) { + this.logger.warn('Rate limit exceeded for a client') + return { status: 429, reason: 'Too Many Requests' } + } + return null + } + + private acceptConnection(ip: string): void { + this.recordConnectionAttempt(ip) + this.globalConnectionCount++ + const currentIpCount = this.perIpConnectionCount.get(ip) ?? 0 + this.perIpConnectionCount.set(ip, currentIpCount + 1) + } + + private isRateLimited(ip: string): boolean { + const windowMs = configService.getWsPerIpRateWindowMs() + const maxRate = configService.getWsPerIpRateLimit() + const now = Date.now() + const timestamps = this.perIpRateWindows.get(ip) ?? [] + const recent = timestamps.filter((t) => now - t < windowMs) + return recent.length >= maxRate + } + + private recordConnectionAttempt(ip: string): void { + const existing = this.perIpRateWindows.get(ip) ?? [] + this.perIpRateWindows.set(ip, [...existing, Date.now()]) + } + + private decrementIpCount(ip: string): void { + const count = this.perIpConnectionCount.get(ip) ?? 0 + if (count <= 1) { + this.perIpConnectionCount.delete(ip) + } else { + this.perIpConnectionCount.set(ip, count - 1) + } + } +} diff --git a/teammapper-backend/src/map/services/yjs-doc-manager.service.spec.ts b/teammapper-backend/src/map/services/yjs-doc-manager.service.spec.ts new file mode 100644 index 000000000..ee7a16581 --- /dev/null +++ b/teammapper-backend/src/map/services/yjs-doc-manager.service.spec.ts @@ -0,0 +1,253 @@ +import { YjsDocManagerService } from './yjs-doc-manager.service' +import { MapsService } from './maps.service' +import { YjsPersistenceService } from './yjs-persistence.service' +import { MmpNode } from '../entities/mmpNode.entity' +import { MmpMap } from '../entities/mmpMap.entity' +import { jest } from '@jest/globals' + +const createMockMap = (): MmpMap => { + const map = new MmpMap() + map.id = 'map-1' + map.name = 'Test Map' + map.options = { fontMaxSize: 28, fontMinSize: 6, fontIncrement: 2 } + return map +} + +const createMockNode = (id: string, root = false): MmpNode => { + const node = new MmpNode() + node.id = id + node.nodeMapId = 'map-1' + node.name = 'Node' + node.root = root + node.locked = false + node.detached = false + node.k = 1 + node.coordinatesX = 0 + node.coordinatesY = 0 + node.orderNumber = 1 + return node +} + +const createMockMapsService = (): jest.Mocked => { + return { + findMap: jest.fn(), + findNodes: jest.fn(), + } as unknown as jest.Mocked +} + +const createMockPersistenceService = (): jest.Mocked => { + return { + persistImmediately: jest.fn(), + } as unknown as jest.Mocked +} + +describe('YjsDocManagerService', () => { + let service: YjsDocManagerService + let mapsService: jest.Mocked + let persistenceService: jest.Mocked + + beforeEach(() => { + jest.useFakeTimers({ advanceTimers: true }) + mapsService = createMockMapsService() + persistenceService = createMockPersistenceService() + persistenceService.persistImmediately.mockResolvedValue(undefined) + service = new YjsDocManagerService(mapsService, persistenceService) + }) + + afterEach(() => { + service.onModuleDestroy() + jest.useRealTimers() + }) + + const setupWithNodes = ( + nodes: MmpNode[] = [createMockNode('root-1', true)] + ) => { + mapsService.findMap.mockResolvedValue(createMockMap()) + mapsService.findNodes.mockResolvedValue(nodes) + } + + const setupConnectedDoc = async (mapId = 'map-1') => { + setupWithNodes() + await service.getOrCreateDoc(mapId) + await service.notifyClientCount(mapId, 1) + } + + describe('getOrCreateDoc', () => { + it('queries database for map and nodes', async () => { + setupWithNodes() + + await service.getOrCreateDoc('map-1') + + expect(mapsService.findMap).toHaveBeenCalledWith('map-1') + expect(mapsService.findNodes).toHaveBeenCalledWith('map-1') + }) + + it('hydrates Y.Doc with all database nodes', async () => { + setupWithNodes([ + createMockNode('root-1', true), + createMockNode('child-1'), + ]) + + const doc = await service.getOrCreateDoc('map-1') + + expect(doc.getMap('nodes').size).toBe(2) + }) + + it('returns existing Y.Doc on subsequent access', async () => { + setupWithNodes() + + const doc1 = await service.getOrCreateDoc('map-1') + const doc2 = await service.getOrCreateDoc('map-1') + + expect(doc1).toBe(doc2) + }) + + it('throws when map not found', async () => { + mapsService.findMap.mockResolvedValue(null) + mapsService.findNodes.mockResolvedValue([]) + + await expect(service.getOrCreateDoc('missing')).rejects.toThrow( + 'Map missing not found' + ) + }) + + it('deduplicates concurrent hydrations for the same mapId', async () => { + setupWithNodes() + + const [doc1, doc2] = await Promise.all([ + service.getOrCreateDoc('map-1'), + service.getOrCreateDoc('map-1'), + ]) + + expect(doc1).toBe(doc2) + expect(mapsService.findMap).toHaveBeenCalledTimes(1) + }) + }) + + describe('notifyClientCount', () => { + it('cancels grace timer when count is positive', async () => { + setupWithNodes() + await service.getOrCreateDoc('map-1') + await service.notifyClientCount('map-1', 0) + + jest.advanceTimersByTime(15_000) + await service.notifyClientCount('map-1', 1) + jest.advanceTimersByTime(20_000) + + expect(service.hasDoc('map-1')).toBe(true) + }) + + it('persists and starts grace timer when count reaches zero', async () => { + await setupConnectedDoc() + + await service.notifyClientCount('map-1', 0) + + expect(persistenceService.persistImmediately).toHaveBeenCalledWith( + 'map-1', + expect.anything() + ) + expect(service.hasDoc('map-1')).toBe(true) + }) + + it('does nothing for untracked map', async () => { + await service.notifyClientCount('nonexistent', 0) + + expect(persistenceService.persistImmediately).not.toHaveBeenCalled() + }) + }) + + describe('eviction lifecycle', () => { + it('persists on last client disconnect', async () => { + await setupConnectedDoc() + + await service.notifyClientCount('map-1', 0) + + expect(persistenceService.persistImmediately).toHaveBeenCalledWith( + 'map-1', + expect.anything() + ) + }) + + it('retains doc during grace period after last disconnect', async () => { + await setupConnectedDoc() + + await service.notifyClientCount('map-1', 0) + + expect(service.hasDoc('map-1')).toBe(true) + }) + + it('evicts doc after grace period expires', async () => { + await setupConnectedDoc() + await service.notifyClientCount('map-1', 0) + + jest.advanceTimersByTime(31_000) + + expect(service.hasDoc('map-1')).toBe(false) + }) + + it('cancels grace timer if client reconnects', async () => { + await setupConnectedDoc() + await service.notifyClientCount('map-1', 0) + + jest.advanceTimersByTime(15_000) + await service.notifyClientCount('map-1', 1) + jest.advanceTimersByTime(20_000) + + expect(service.hasDoc('map-1')).toBe(true) + }) + + it('reuses in-memory doc during grace period', async () => { + setupWithNodes() + const doc1 = await service.getOrCreateDoc('map-1') + await service.notifyClientCount('map-1', 1) + await service.notifyClientCount('map-1', 0) + + jest.advanceTimersByTime(10_000) + const doc2 = await service.getOrCreateDoc('map-1') + + expect(doc1).toBe(doc2) + expect(mapsService.findMap).toHaveBeenCalledTimes(1) + }) + }) + + describe('restoreGraceTimer', () => { + it('starts grace timer when connection count is zero', async () => { + setupWithNodes() + await service.getOrCreateDoc('map-1') + + service.restoreGraceTimer('map-1', 0) + jest.advanceTimersByTime(31_000) + + expect(service.hasDoc('map-1')).toBe(false) + }) + + it('does not start grace timer when connections exist', async () => { + setupWithNodes() + await service.getOrCreateDoc('map-1') + + service.restoreGraceTimer('map-1', 2) + jest.advanceTimersByTime(31_000) + + expect(service.hasDoc('map-1')).toBe(true) + }) + }) + + describe('destroyDoc', () => { + it('removes doc from memory', async () => { + await setupConnectedDoc() + + service.destroyDoc('map-1') + + expect(service.hasDoc('map-1')).toBe(false) + }) + + it('cancels grace timer on destroy', async () => { + await setupConnectedDoc() + await service.notifyClientCount('map-1', 0) + + service.destroyDoc('map-1') + + expect(service.hasDoc('map-1')).toBe(false) + }) + }) +}) diff --git a/teammapper-backend/src/map/services/yjs-doc-manager.service.ts b/teammapper-backend/src/map/services/yjs-doc-manager.service.ts new file mode 100644 index 000000000..2a483f649 --- /dev/null +++ b/teammapper-backend/src/map/services/yjs-doc-manager.service.ts @@ -0,0 +1,154 @@ +import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common' +import * as Y from 'yjs' +import { MapsService } from './maps.service' +import { YjsPersistenceService } from './yjs-persistence.service' +import { hydrateYDoc } from '../utils/yDocConversion' + +interface DocEntry { + doc: Y.Doc + graceTimer: ReturnType | null +} + +@Injectable() +export class YjsDocManagerService implements OnModuleDestroy { + private readonly logger = new Logger(YjsDocManagerService.name) + private readonly docs = new Map() + private readonly hydrating = new Map>() + private readonly GRACE_PERIOD_MS = 30_000 + + constructor( + private readonly mapsService: MapsService, + private readonly persistenceService: YjsPersistenceService + ) {} + + onModuleDestroy(): void { + for (const [mapId] of this.docs) { + this.forceDestroyDoc(mapId) + } + } + + getDoc(mapId: string): Y.Doc | undefined { + return this.docs.get(mapId)?.doc + } + + async getOrCreateDoc(mapId: string): Promise { + const existing = this.docs.get(mapId) + if (existing) { + this.cancelGraceTimer(existing) + return existing.doc + } + + // Deduplicate concurrent hydrations for the same mapId + const inflight = this.hydrating.get(mapId) + if (inflight) return inflight + + const promise = this.hydrateDocFromDb(mapId) + this.hydrating.set(mapId, promise) + try { + return await promise + } finally { + this.hydrating.delete(mapId) + } + } + + async notifyClientCount(mapId: string, count: number): Promise { + const entry = this.docs.get(mapId) + if (!entry) return + + this.logger.debug(`Map ${mapId} client count: ${count}`) + + if (count > 0) { + this.cancelGraceTimer(entry) + return + } + + await this.onLastClientDisconnect(mapId, entry) + } + + destroyDoc(mapId: string): void { + this.forceDestroyDoc(mapId) + } + + // Restores the grace timer if no clients are connected (used on setup failure) + restoreGraceTimer(mapId: string, connectionCount: number): void { + if (connectionCount > 0) return + + const entry = this.docs.get(mapId) + if (!entry) return + + this.startGraceTimer(mapId, entry) + } + + hasDoc(mapId: string): boolean { + return this.docs.has(mapId) + } + + private async hydrateDocFromDb(mapId: string): Promise { + const [map, nodes] = await Promise.all([ + this.mapsService.findMap(mapId), + this.mapsService.findNodes(mapId), + ]) + + if (!map) { + throw new Error(`Map ${mapId} not found`) + } + + const doc = new Y.Doc() + hydrateYDoc(doc, nodes, map) + + this.docs.set(mapId, { + doc, + graceTimer: null, + }) + + this.logger.log( + `Hydrated Y.Doc for map ${mapId} with ${nodes.length} nodes` + ) + return doc + } + + private async onLastClientDisconnect( + mapId: string, + entry: DocEntry + ): Promise { + await this.persistSafely(mapId, entry.doc) + this.startGraceTimer(mapId, entry) + } + + private startGraceTimer(mapId: string, entry: DocEntry): void { + this.cancelGraceTimer(entry) + entry.graceTimer = setTimeout(() => { + this.evictDoc(mapId) + }, this.GRACE_PERIOD_MS) + } + + private cancelGraceTimer(entry: DocEntry): void { + if (entry.graceTimer) { + clearTimeout(entry.graceTimer) + entry.graceTimer = null + } + } + + private evictDoc(mapId: string): void { + const entry = this.docs.get(mapId) + if (!entry) return + + entry.doc.destroy() + this.docs.delete(mapId) + this.logger.log(`Evicted Y.Doc for map ${mapId}`) + } + + private forceDestroyDoc(mapId: string): void { + const entry = this.docs.get(mapId) + if (!entry) return + + this.cancelGraceTimer(entry) + entry.doc.destroy() + this.docs.delete(mapId) + this.logger.log(`Force-destroyed Y.Doc for map ${mapId}`) + } + + private async persistSafely(mapId: string, doc: Y.Doc): Promise { + await this.persistenceService.persistImmediately(mapId, doc) + } +} diff --git a/teammapper-backend/src/map/services/yjs-persistence.service.spec.ts b/teammapper-backend/src/map/services/yjs-persistence.service.spec.ts new file mode 100644 index 000000000..fa8ce7295 --- /dev/null +++ b/teammapper-backend/src/map/services/yjs-persistence.service.spec.ts @@ -0,0 +1,415 @@ +import { Test, TestingModule } from '@nestjs/testing' +import { getRepositoryToken, TypeOrmModule } from '@nestjs/typeorm' +import { Repository } from 'typeorm' +import { ConfigModule } from '@nestjs/config' +import * as Y from 'yjs' +import { v4 as uuidv4 } from 'uuid' +import AppModule from '../../app.module' +import { + createTestConfiguration, + destroyWorkerDatabase, +} from '../../../test/db' +import { truncateDatabase } from 'test/helper' +import { MmpMap } from '../entities/mmpMap.entity' +import { MmpNode } from '../entities/mmpNode.entity' +import { YjsPersistenceService } from './yjs-persistence.service' +import { hydrateYDoc } from '../utils/yDocConversion' +import { jest } from '@jest/globals' + +describe('YjsPersistenceService', () => { + let service: YjsPersistenceService + let nodesRepo: Repository + let mapsRepo: Repository + let moduleFixture: TestingModule + + beforeAll(async () => { + jest.useFakeTimers({ advanceTimers: true }) + + moduleFixture = await Test.createTestingModule({ + imports: [ + ConfigModule, + TypeOrmModule.forRoot( + await createTestConfiguration(process.env.JEST_WORKER_ID || '') + ), + AppModule, + ], + }).compile() + + mapsRepo = moduleFixture.get>(getRepositoryToken(MmpMap)) + nodesRepo = moduleFixture.get>( + getRepositoryToken(MmpNode) + ) + service = new YjsPersistenceService(nodesRepo, mapsRepo) + }) + + afterAll(async () => { + await destroyWorkerDatabase( + mapsRepo.manager.connection, + process.env.JEST_WORKER_ID || '' + ) + await moduleFixture.close() + jest.useRealTimers() + }) + + beforeEach(async () => { + await truncateDatabase(mapsRepo.manager.connection) + jest.restoreAllMocks() + }) + + const createMapWithRootNode = async (): Promise<{ + map: MmpMap + rootNode: MmpNode + }> => { + const map = await mapsRepo.save({ + name: 'Test Map', + options: { fontMaxSize: 28, fontMinSize: 6, fontIncrement: 2 }, + }) + const rootNode = await nodesRepo.save({ + nodeMapId: map.id, + name: 'Root', + root: true, + detached: false, + coordinatesX: 0, + coordinatesY: 0, + k: 1, + }) + return { map, rootNode } + } + + const hydrateFromDb = async ( + map: MmpMap, + nodes?: MmpNode[] + ): Promise => { + const doc = new Y.Doc() + const dbNodes = + nodes ?? (await nodesRepo.find({ where: { nodeMapId: map.id } })) + hydrateYDoc(doc, dbNodes, map) + return doc + } + + const addChildToDoc = (doc: Y.Doc, parentId: string): string => { + const childId = uuidv4() + const nodesMap = doc.getMap('nodes') as Y.Map> + const child = new Y.Map() + child.set('id', childId) + child.set('parent', parentId) + child.set('name', 'Child Node') + child.set('isRoot', false) + child.set('locked', false) + child.set('detached', false) + child.set('k', 1) + child.set('coordinates', { x: 100, y: 50 }) + child.set('colors', { name: '#333', background: '#fff', branch: '' }) + child.set('font', { style: 'normal', size: 16, weight: 'normal' }) + child.set('image', { src: '', size: 0 }) + child.set('link', { href: '' }) + nodesMap.set(childId, child) + return childId + } + + describe('persistDoc', () => { + it('persists new Y.Doc nodes to the database', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + addChildToDoc(doc, rootNode.id) + + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes).toHaveLength(2) + + doc.destroy() + }) + + it('persists child node coordinates correctly', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + addChildToDoc(doc, rootNode.id) + + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes.find((n) => n.name === 'Child Node')).toMatchObject({ + coordinatesX: 100, + coordinatesY: 50, + }) + + doc.destroy() + }) + + it('updates map name from Y.Doc options', async () => { + const { map } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const optionsMap = doc.getMap('mapOptions') as Y.Map + optionsMap.set('name', 'Updated Map Name') + + await service.persistDoc(map.id, doc) + + const updatedMap = await mapsRepo.findOne({ where: { id: map.id } }) + expect(updatedMap!.name).toBe('Updated Map Name') + + doc.destroy() + }) + + it('updates lastModified timestamp on persist', async () => { + const { map } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + + const beforePersist = new Date() + await service.persistDoc(map.id, doc) + + const updatedMap = await mapsRepo.findOne({ where: { id: map.id } }) + expect(updatedMap!.lastModified!.getTime()).toBeGreaterThanOrEqual( + beforePersist.getTime() + ) + + doc.destroy() + }) + + it('removes nodes not present in Y.Doc', async () => { + const { map, rootNode } = await createMapWithRootNode() + await nodesRepo.save({ + nodeMapId: map.id, + name: 'Extra Node', + root: false, + detached: true, + coordinatesX: 50, + coordinatesY: 50, + k: 1, + }) + const doc = await hydrateFromDb(map, [rootNode]) + + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes).toEqual([expect.objectContaining({ name: 'Root' })]) + + doc.destroy() + }) + + it('updates existing nodes in place', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + + const nodesMap = doc.getMap('nodes') as Y.Map> + nodesMap.get(rootNode.id)!.set('name', 'Renamed Root') + + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes).toEqual([ + expect.objectContaining({ id: rootNode.id, name: 'Renamed Root' }), + ]) + + doc.destroy() + }) + + it('deletes all DB nodes when Y.Doc nodes map is empty', async () => { + const { map } = await createMapWithRootNode() + const doc = new Y.Doc() + hydrateYDoc(doc, [], map) + + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes).toHaveLength(0) + + doc.destroy() + }) + + it('sets lastModified on each persisted node', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map, [rootNode]) + + const beforePersist = new Date() + await service.persistDoc(map.id, doc) + + const dbNodes = await nodesRepo.find({ where: { nodeMapId: map.id } }) + expect(dbNodes[0].lastModified!.getTime()).toBeGreaterThanOrEqual( + beforePersist.getTime() + ) + + doc.destroy() + }) + }) + + describe('debounced persistence', () => { + it('debounces multiple updates into one persist', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'Update 1') + yRoot.set('name', 'Update 2') + yRoot.set('name', 'Update 3') + await jest.advanceTimersByTimeAsync(2_500) + + expect(persistSpy).toHaveBeenCalledTimes(1) + + service.unregisterDebounce(map.id) + doc.destroy() + }) + + it('resets debounce timer on new changes', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'Change 1') + await jest.advanceTimersByTimeAsync(1_500) + yRoot.set('name', 'Change 2') + await jest.advanceTimersByTimeAsync(2_500) + + expect(persistSpy).toHaveBeenCalledTimes(1) + + service.unregisterDebounce(map.id) + doc.destroy() + }) + }) + + describe('persistImmediately', () => { + it('persists immediately and cancels pending debounce', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'Immediate Change') + + await service.persistImmediately(map.id, doc) + await jest.advanceTimersByTimeAsync(3_000) + + expect(persistSpy).toHaveBeenCalledTimes(1) + + service.unregisterDebounce(map.id) + doc.destroy() + }) + + it('handles persist errors without throwing', async () => { + const doc = new Y.Doc() + + await expect( + service.persistImmediately(uuidv4(), doc) + ).resolves.toBeUndefined() + + doc.destroy() + }) + }) + + describe('unregisterDebounce', () => { + it('removes the observer so changes after unregister do not trigger persist', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + service.unregisterDebounce(map.id) + + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'After Unregister') + await jest.advanceTimersByTimeAsync(3_000) + + expect(persistSpy).not.toHaveBeenCalled() + + doc.destroy() + }) + }) + + describe('onModuleDestroy', () => { + it('clears active timers and flushes pending docs', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'Pending Change') + + await service.onModuleDestroy() + + expect(persistSpy).toHaveBeenCalledWith(map.id, doc) + + doc.destroy() + }) + + it('does not fire debounce timers after shutdown', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'Change') + + await service.onModuleDestroy() + persistSpy.mockClear() + + await jest.advanceTimersByTimeAsync(3_000) + + expect(persistSpy).not.toHaveBeenCalled() + + doc.destroy() + }) + + it('unregisters observers so subsequent doc changes do not trigger persist', async () => { + const { map, rootNode } = await createMapWithRootNode() + const doc = await hydrateFromDb(map) + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + service.registerDebounce(map.id, doc) + await service.onModuleDestroy() + persistSpy.mockClear() + + const yRoot = (doc.getMap('nodes') as Y.Map>).get( + rootNode.id + )! + yRoot.set('name', 'After Shutdown') + await jest.advanceTimersByTimeAsync(3_000) + + expect(persistSpy).not.toHaveBeenCalled() + + doc.destroy() + }) + + it('skips flush when no timers are active', async () => { + const persistSpy = jest + .spyOn(service, 'persistDoc') + .mockResolvedValue(undefined) + + await service.onModuleDestroy() + + expect(persistSpy).not.toHaveBeenCalled() + }) + }) +}) diff --git a/teammapper-backend/src/map/services/yjs-persistence.service.ts b/teammapper-backend/src/map/services/yjs-persistence.service.ts new file mode 100644 index 000000000..a83c07af9 --- /dev/null +++ b/teammapper-backend/src/map/services/yjs-persistence.service.ts @@ -0,0 +1,254 @@ +import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common' +import { InjectRepository } from '@nestjs/typeorm' +import { Not, In, QueryRunner, Repository } from 'typeorm' +import * as Y from 'yjs' +import { MmpNode } from '../entities/mmpNode.entity' +import { MmpMap } from '../entities/mmpMap.entity' +import { yMapToMmpNode, yMapToMapOptions } from '../utils/yDocConversion' +import { orderNodesFromRoot } from '../utils/nodeOrdering' + +interface DebounceEntry { + doc: Y.Doc + timer: ReturnType | null + observer: () => void +} + +@Injectable() +export class YjsPersistenceService implements OnModuleDestroy { + private readonly logger = new Logger(YjsPersistenceService.name) + private readonly debounceTimers = new Map() + private readonly DEBOUNCE_MS = 2_000 + private readonly FLUSH_TIMEOUT_MS = 5_000 + + constructor( + @InjectRepository(MmpNode) + private readonly nodesRepository: Repository, + @InjectRepository(MmpMap) + private readonly mapsRepository: Repository + ) {} + + async onModuleDestroy(): Promise { + const pendingFlush = this.clearAllTimersAndObservers() + + if (pendingFlush.length === 0) return + + this.logger.log(`Flushing ${pendingFlush.length} pending docs on shutdown`) + await this.flushWithTimeout(pendingFlush) + } + + // Persists a Y.Doc's nodes and options to the database in a transaction + async persistDoc(mapId: string, doc: Y.Doc): Promise { + const queryRunner = + this.nodesRepository.manager.connection.createQueryRunner() + await queryRunner.connect() + + try { + await queryRunner.startTransaction() + + const nodesMap = doc.getMap('nodes') as Y.Map> + const optionsMap = doc.getMap('mapOptions') as Y.Map + const now = new Date() + + const mmpNodes = this.extractNodesFromYDoc(nodesMap, mapId, now) + + // Remove nodes that are no longer in the Y.Doc + await this.deleteRemovedNodes(queryRunner, mapId, mmpNodes) + + // Upsert current nodes (save inserts or updates by PK) + if (mmpNodes.length > 0) { + await this.upsertNodes(queryRunner, mmpNodes) + } + + // Update map options and lastModified + await this.updateMapMetadata(queryRunner, mapId, optionsMap, now) + + await queryRunner.commitTransaction() + this.logger.debug( + `Persisted Y.Doc for map ${mapId} (${mmpNodes.length} nodes)` + ) + } catch (error) { + await this.rollbackSafely(queryRunner) + this.logger.error( + `Failed to persist Y.Doc for map ${mapId}: ${error instanceof Error ? error.message : String(error)}` + ) + throw error + } finally { + await this.releaseSafely(queryRunner) + } + } + + // Registers a Y.Doc update observer that triggers debounced persistence + registerDebounce(mapId: string, doc: Y.Doc): void { + this.unregisterDebounce(mapId) + + const observer = (): void => { + this.resetDebounceTimer(mapId, doc) + } + + doc.on('update', observer) + this.debounceTimers.set(mapId, { doc, timer: null, observer }) + } + + // Removes debounce observer and cancels pending timer + unregisterDebounce(mapId: string): void { + const entry = this.debounceTimers.get(mapId) + if (!entry) return + + entry.doc.off('update', entry.observer) + if (entry.timer) clearTimeout(entry.timer) + this.debounceTimers.delete(mapId) + } + + // Immediately persists, skipping debounce (used on last client disconnect) + async persistImmediately(mapId: string, doc: Y.Doc): Promise { + this.cancelDebounceTimer(mapId) + + try { + await this.persistDoc(mapId, doc) + } catch (error) { + this.logger.error( + `Immediate persist failed for map ${mapId}: ${error instanceof Error ? error.message : String(error)}` + ) + // Do not crash — error is logged, retry will happen on next debounce + } + } + + private resetDebounceTimer(mapId: string, doc: Y.Doc): void { + this.cancelDebounceTimer(mapId) + + const entry = this.debounceTimers.get(mapId) + if (!entry) return + + entry.timer = setTimeout(async () => { + try { + await this.persistDoc(mapId, doc) + } catch (error) { + this.logger.error( + `Debounced persist failed for map ${mapId}: ${error instanceof Error ? error.message : String(error)}` + ) + // Do not crash — retry on next debounce cycle + } + }, this.DEBOUNCE_MS) + } + + private cancelDebounceTimer(mapId: string): void { + const entry = this.debounceTimers.get(mapId) + if (entry?.timer) { + clearTimeout(entry.timer) + entry.timer = null + } + } + + // Extracts nodes from Y.Doc, ensuring root is first with stable orderNumbers + private extractNodesFromYDoc( + nodesMap: Y.Map>, + mapId: string, + now: Date + ): Partial[] { + const nodes: Partial[] = [] + nodesMap.forEach((yNode) => { + nodes.push({ ...yMapToMmpNode(yNode, mapId), lastModified: now }) + }) + return orderNodesFromRoot(nodes) + } + + private async deleteRemovedNodes( + queryRunner: QueryRunner, + mapId: string, + currentNodes: Partial[] + ): Promise { + const keepIds = currentNodes + .map((n) => n.id) + .filter((id): id is string => !!id) + + if (keepIds.length === 0) { + await queryRunner.manager.delete(MmpNode, { nodeMapId: mapId }) + return + } + + await queryRunner.manager.delete(MmpNode, { + nodeMapId: mapId, + id: Not(In(keepIds)), + }) + } + + private async upsertNodes( + queryRunner: QueryRunner, + nodes: Partial[] + ): Promise { + const entities = nodes.map((node) => + queryRunner.manager.create(MmpNode, node) + ) + await queryRunner.manager.save(entities) + } + + private async updateMapMetadata( + queryRunner: QueryRunner, + mapId: string, + optionsMap: Y.Map, + now: Date + ): Promise { + const { name, options } = yMapToMapOptions(optionsMap) + await queryRunner.manager.update(MmpMap, mapId, { + name, + options, + lastModified: now, + }) + } + + private async rollbackSafely(queryRunner: QueryRunner): Promise { + try { + if (queryRunner.isTransactionActive) { + await queryRunner.rollbackTransaction() + } + } catch (error) { + this.logger.error( + `Rollback failed: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + private async releaseSafely(queryRunner: QueryRunner): Promise { + try { + if (!queryRunner.isReleased) { + await queryRunner.release() + } + } catch (error) { + this.logger.error( + `Release failed: ${error instanceof Error ? error.message : String(error)}` + ) + } + } + + private clearAllTimersAndObservers(): Array<{ + mapId: string + doc: Y.Doc + }> { + const pendingFlush: Array<{ mapId: string; doc: Y.Doc }> = [] + + for (const [mapId, entry] of this.debounceTimers) { + entry.doc.off('update', entry.observer) + if (entry.timer) { + clearTimeout(entry.timer) + pendingFlush.push({ mapId, doc: entry.doc }) + } + } + this.debounceTimers.clear() + + return pendingFlush + } + + private async flushWithTimeout( + entries: Array<{ mapId: string; doc: Y.Doc }> + ): Promise { + const flushPromise = Promise.allSettled( + entries.map(({ mapId, doc }) => this.persistDoc(mapId, doc)) + ) + + const timeoutPromise = new Promise((resolve) => + setTimeout(resolve, this.FLUSH_TIMEOUT_MS) + ) + + await Promise.race([flushPromise, timeoutPromise]) + } +} diff --git a/teammapper-backend/src/map/utils/nodeOrdering.spec.ts b/teammapper-backend/src/map/utils/nodeOrdering.spec.ts new file mode 100644 index 000000000..abfb153d0 --- /dev/null +++ b/teammapper-backend/src/map/utils/nodeOrdering.spec.ts @@ -0,0 +1,109 @@ +import { MmpNode } from '../entities/mmpNode.entity' +import { orderNodesFromRoot } from './nodeOrdering' + +const makeNode = (overrides: Partial): Partial => ({ + root: false, + nodeParentId: null, + ...overrides, +}) + +describe('orderNodesFromRoot', () => { + it('places root node first with orderNumber 1', () => { + const nodes = [ + makeNode({ id: 'child', nodeParentId: 'root', root: false }), + makeNode({ id: 'root', root: true }), + ] + + const result = orderNodesFromRoot(nodes) + + expect(result[0]).toMatchObject({ id: 'root', orderNumber: 1 }) + expect(result[1]).toMatchObject({ id: 'child', orderNumber: 2 }) + }) + + it('orders parents before children in a three-level tree', () => { + const nodes = [ + makeNode({ id: 'grandchild', nodeParentId: 'child' }), + makeNode({ id: 'child', nodeParentId: 'root' }), + makeNode({ id: 'root', root: true }), + ] + + const result = orderNodesFromRoot(nodes) + const ids = result.map((n) => n.id) + + expect(ids).toEqual(['root', 'child', 'grandchild']) + expect(result.map((n) => n.orderNumber)).toEqual([1, 2, 3]) + }) + + it('handles multiple children of the same parent', () => { + const nodes = [ + makeNode({ id: 'root', root: true }), + makeNode({ id: 'b', nodeParentId: 'root' }), + makeNode({ id: 'a', nodeParentId: 'root' }), + ] + + const result = orderNodesFromRoot(nodes) + + expect(result[0].id).toBe('root') + expect(result.map((n) => n.id)).toContain('a') + expect(result.map((n) => n.id)).toContain('b') + }) + + it('handles a deep chain: root → A → B → C → D', () => { + const nodes = [ + makeNode({ id: 'D', nodeParentId: 'C' }), + makeNode({ id: 'B', nodeParentId: 'A' }), + makeNode({ id: 'root', root: true }), + makeNode({ id: 'C', nodeParentId: 'B' }), + makeNode({ id: 'A', nodeParentId: 'root' }), + ] + + const result = orderNodesFromRoot(nodes) + const ids = result.map((n) => n.id) + + expect(ids).toEqual(['root', 'A', 'B', 'C', 'D']) + expect(result.map((n) => n.orderNumber)).toEqual([1, 2, 3, 4, 5]) + }) + + it('returns input unchanged when no root node exists', () => { + const nodes = [ + makeNode({ id: 'a', nodeParentId: 'x' }), + makeNode({ id: 'b', nodeParentId: 'y' }), + ] + + const result = orderNodesFromRoot(nodes) + + expect(result.map((n) => n.id)).toEqual(['a', 'b']) + }) + + it('handles a single root node with no children', () => { + const nodes = [makeNode({ id: 'root', root: true })] + + const result = orderNodesFromRoot(nodes) + + expect(result).toEqual([ + { id: 'root', root: true, nodeParentId: null, orderNumber: 1 }, + ]) + }) + + it('does not mutate the input array', () => { + const nodes = [ + makeNode({ id: 'child', nodeParentId: 'root' }), + makeNode({ id: 'root', root: true }), + ] + const original = [...nodes] + + orderNodesFromRoot(nodes) + + expect(nodes).toEqual(original) + }) + + it('does not mutate the input node objects', () => { + const root = makeNode({ id: 'root', root: true }) + const child = makeNode({ id: 'child', nodeParentId: 'root' }) + + orderNodesFromRoot([child, root]) + + expect(root).not.toHaveProperty('orderNumber') + expect(child).not.toHaveProperty('orderNumber') + }) +}) diff --git a/teammapper-backend/src/map/utils/nodeOrdering.ts b/teammapper-backend/src/map/utils/nodeOrdering.ts new file mode 100644 index 000000000..003ad70e1 --- /dev/null +++ b/teammapper-backend/src/map/utils/nodeOrdering.ts @@ -0,0 +1,46 @@ +import { MmpNode } from '../entities/mmpNode.entity' + +// Groups non-root nodes by their parent ID +const groupByParentId = ( + nodes: ReadonlyArray> +): ReadonlyMap>> => + nodes + .filter((n) => !n.root) + .reduce((acc, node) => { + const pid = node.nodeParentId ?? '' + acc.set(pid, [...(acc.get(pid) ?? []), node]) + return acc + }, new Map[]>()) + +// Recursive breadth-first search: processes head of queue, enqueues its children, accumulates result +const collectBreadthFirst = ( + queue: ReadonlyArray>, + childrenOf: ReadonlyMap>>, + collected: ReadonlyArray> = [] +): ReadonlyArray> => { + if (queue.length === 0) return collected + const [current, ...rest] = queue + const kids = childrenOf.get(current.id ?? '') ?? [] + return collectBreadthFirst([...rest, ...kids], childrenOf, [ + ...collected, + current, + ]) +} + +// Assigns sequential orderNumbers starting from 1 +const assignOrderNumbers = ( + nodes: ReadonlyArray> +): Partial[] => + nodes.map((node, index) => ({ ...node, orderNumber: index + 1 })) + +// Orders nodes via breadth-first search (root first, parents before children) with sequential orderNumbers +export const orderNodesFromRoot = ( + nodes: ReadonlyArray> +): Partial[] => { + const root = nodes.find((n) => n.root) + if (!root) return [...nodes] + + const childrenOf = groupByParentId(nodes) + const ordered = collectBreadthFirst([root], childrenOf) + return assignOrderNumbers(ordered) +} diff --git a/teammapper-backend/src/map/utils/yDocConversion.spec.ts b/teammapper-backend/src/map/utils/yDocConversion.spec.ts new file mode 100644 index 000000000..7fd0cba4a --- /dev/null +++ b/teammapper-backend/src/map/utils/yDocConversion.spec.ts @@ -0,0 +1,249 @@ +import * as Y from 'yjs' +import { MmpNode } from '../entities/mmpNode.entity' +import { MmpMap } from '../entities/mmpMap.entity' +import { + populateYMapFromNode, + yMapToMmpNode, + populateYMapFromMapOptions, + yMapToMapOptions, + hydrateYDoc, +} from './yDocConversion' + +const createTestNode = (overrides: Partial = {}): MmpNode => { + const node = new MmpNode() + node.id = 'node-1' + node.nodeMapId = 'map-1' + node.nodeParentId = null + node.name = 'Test Node' + node.root = true + node.locked = false + node.detached = false + node.k = 1.5 + node.coordinatesX = 100 + node.coordinatesY = 200 + node.colorsName = '#333' + node.colorsBackground = '#fff' + node.colorsBranch = '#999' + node.fontStyle = 'italic' + node.fontSize = 16 + node.fontWeight = 'bold' + node.imageSrc = 'img.png' + node.imageSize = 80 + node.linkHref = 'https://example.com' + node.orderNumber = 1 + node.lastModified = new Date() + node.createdAt = new Date() + Object.assign(node, overrides) + return node +} + +const createTestMap = (overrides: Partial = {}): MmpMap => { + const map = new MmpMap() + map.id = 'map-1' + map.name = 'Test Map' + map.options = { fontMaxSize: 28, fontMinSize: 6, fontIncrement: 2 } + map.lastModified = new Date() + map.lastAccessed = new Date() + map.createdAt = new Date() + Object.assign(map, overrides) + return map +} + +const yNodeToPlainObject = (yNode: Y.Map) => ({ + id: yNode.get('id'), + parent: yNode.get('parent'), + name: yNode.get('name'), + isRoot: yNode.get('isRoot'), + locked: yNode.get('locked'), + detached: yNode.get('detached'), + k: yNode.get('k'), + coordinates: yNode.get('coordinates'), + colors: yNode.get('colors'), + font: yNode.get('font'), + image: yNode.get('image'), + link: yNode.get('link'), +}) + +const populateAndGet = ( + node: MmpNode +): { yNode: Y.Map; doc: Y.Doc } => { + const doc = new Y.Doc() + const nodesMap = doc.getMap('nodes') as Y.Map> + populateYMapFromNode(nodesMap, node) + return { yNode: nodesMap.get(node.id)!, doc } +} + +describe('yDocConversion', () => { + describe('populateYMapFromNode', () => { + it('populates a Y.Map with all node fields', () => { + const { yNode, doc } = populateAndGet(createTestNode()) + + expect(yNodeToPlainObject(yNode)).toEqual({ + id: 'node-1', + parent: null, + name: 'Test Node', + isRoot: true, + locked: false, + detached: false, + k: 1.5, + coordinates: { x: 100, y: 200 }, + colors: { name: '#333', background: '#fff', branch: '#999' }, + font: { style: 'italic', size: 16, weight: 'bold' }, + image: { src: 'img.png', size: 80 }, + link: { href: 'https://example.com' }, + }) + + doc.destroy() + }) + + it('defaults nullable fields to safe values', () => { + const { yNode, doc } = populateAndGet( + createTestNode({ + name: null, + colorsName: null, + colorsBackground: null, + colorsBranch: null, + fontStyle: null, + fontSize: null, + fontWeight: null, + imageSrc: null, + imageSize: null, + linkHref: null, + locked: null, + k: null, + }) + ) + + expect(yNodeToPlainObject(yNode)).toMatchObject({ + name: '', + locked: false, + k: 1, + colors: { name: '', background: '', branch: '' }, + font: { style: '', size: 12, weight: '' }, + image: { src: '', size: 0 }, + link: { href: '' }, + }) + + doc.destroy() + }) + }) + + describe('yMapToMmpNode', () => { + it('converts Y.Map back to MmpNode with all fields', () => { + const { yNode, doc } = populateAndGet(createTestNode()) + + expect(yMapToMmpNode(yNode, 'map-1')).toMatchObject({ + id: 'node-1', + name: 'Test Node', + root: true, + locked: false, + detached: false, + k: 1.5, + coordinatesX: 100, + coordinatesY: 200, + colorsName: '#333', + colorsBackground: '#fff', + colorsBranch: '#999', + fontStyle: 'italic', + fontSize: 16, + fontWeight: 'bold', + imageSrc: 'img.png', + imageSize: 80, + linkHref: 'https://example.com', + nodeMapId: 'map-1', + }) + + doc.destroy() + }) + + it('sets nodeParentId to undefined for null parent', () => { + const { yNode, doc } = populateAndGet( + createTestNode({ nodeParentId: null }) + ) + + expect(yMapToMmpNode(yNode, 'map-1').nodeParentId).toBeUndefined() + + doc.destroy() + }) + + it('preserves non-null nodeParentId', () => { + const { yNode, doc } = populateAndGet( + createTestNode({ nodeParentId: 'parent-id', root: false }) + ) + + expect(yMapToMmpNode(yNode, 'map-1').nodeParentId).toBe('parent-id') + + doc.destroy() + }) + }) + + describe('populateYMapFromMapOptions / yMapToMapOptions roundtrip', () => { + it('round-trips map name and options', () => { + const doc = new Y.Doc() + const optionsMap = doc.getMap('mapOptions') as Y.Map + populateYMapFromMapOptions(optionsMap, createTestMap()) + + expect(yMapToMapOptions(optionsMap)).toEqual({ + name: 'Test Map', + options: { fontMaxSize: 28, fontMinSize: 6, fontIncrement: 2 }, + }) + + doc.destroy() + }) + + it('converts empty name to null on readback', () => { + const doc = new Y.Doc() + const optionsMap = doc.getMap('mapOptions') as Y.Map + populateYMapFromMapOptions(optionsMap, createTestMap({ name: null })) + + expect(yMapToMapOptions(optionsMap).name).toBeNull() + + doc.destroy() + }) + }) + + describe('hydrateYDoc', () => { + it('populates all nodes into the nodes map', () => { + const doc = new Y.Doc() + hydrateYDoc( + doc, + [ + createTestNode(), + createTestNode({ id: 'node-2', root: false, name: 'Child' }), + ], + createTestMap() + ) + + expect((doc.getMap('nodes') as Y.Map>).size).toBe(2) + + doc.destroy() + }) + + it('handles an empty node list', () => { + const doc = new Y.Doc() + hydrateYDoc(doc, [], createTestMap()) + + expect((doc.getMap('nodes') as Y.Map>).size).toBe(0) + + doc.destroy() + }) + + it('executes all writes within a single transaction', () => { + const doc = new Y.Doc() + let updateCount = 0 + doc.on('update', () => { + updateCount++ + }) + + hydrateYDoc( + doc, + [createTestNode(), createTestNode({ id: 'node-2', root: false })], + createTestMap() + ) + + expect(updateCount).toBe(1) + + doc.destroy() + }) + }) +}) diff --git a/teammapper-backend/src/map/utils/yDocConversion.ts b/teammapper-backend/src/map/utils/yDocConversion.ts new file mode 100644 index 000000000..ab07a3f11 --- /dev/null +++ b/teammapper-backend/src/map/utils/yDocConversion.ts @@ -0,0 +1,138 @@ +import * as Y from 'yjs' +import { MmpNode } from '../entities/mmpNode.entity' +import { MapOptions } from '../types' +import { MmpMap } from '../entities/mmpMap.entity' + +// Converts an MmpNode entity to a Y.Map and sets it in the nodes container +export const populateYMapFromNode = ( + nodesMap: Y.Map>, + node: MmpNode +): void => { + const yNode = new Y.Map() + yNode.set('id', node.id) + yNode.set('parent', node.nodeParentId ?? null) + yNode.set('name', node.name ?? '') + yNode.set('isRoot', node.root ?? false) + yNode.set('locked', node.locked ?? false) + yNode.set('detached', node.detached ?? false) + yNode.set('k', node.k ?? 1) + yNode.set('coordinates', { + x: node.coordinatesX ?? 0, + y: node.coordinatesY ?? 0, + }) + yNode.set('colors', { + name: node.colorsName ?? '', + background: node.colorsBackground ?? '', + branch: node.colorsBranch ?? '', + }) + yNode.set('font', { + style: node.fontStyle ?? '', + size: node.fontSize ?? 12, + weight: node.fontWeight ?? '', + }) + yNode.set('image', { + src: node.imageSrc ?? '', + size: node.imageSize ?? 0, + }) + yNode.set('link', { href: node.linkHref ?? '' }) + yNode.set('orderNumber', node.orderNumber ?? 0) + nodesMap.set(node.id, yNode) +} + +// Converts a Y.Map entry back to a partial MmpNode for persistence +export const yMapToMmpNode = ( + yNode: Y.Map, + mapId: string +): Partial => { + const coords = yNode.get('coordinates') as + | { x: number; y: number } + | undefined + const colors = yNode.get('colors') as + | { + name: string + background: string + branch: string + } + | undefined + const font = yNode.get('font') as + | { + style: string + size: number + weight: string + } + | undefined + const image = yNode.get('image') as + | { + src: string + size: number + } + | undefined + const link = yNode.get('link') as { href: string } | undefined + const parent = yNode.get('parent') as string | null | undefined + + return { + id: yNode.get('id') as string, + nodeParentId: parent || undefined, + name: (yNode.get('name') as string) ?? '', + root: (yNode.get('isRoot') as boolean) ?? false, + locked: (yNode.get('locked') as boolean) ?? false, + detached: (yNode.get('detached') as boolean) ?? false, + k: (yNode.get('k') as number) ?? 1, + coordinatesX: coords?.x ?? 0, + coordinatesY: coords?.y ?? 0, + colorsName: colors?.name ?? '', + colorsBackground: colors?.background ?? '', + colorsBranch: colors?.branch ?? '', + fontStyle: font?.style ?? '', + fontSize: font?.size ?? 12, + fontWeight: font?.weight ?? '', + imageSrc: image?.src ?? '', + imageSize: image?.size ?? 0, + linkHref: link?.href ?? '', + orderNumber: (yNode.get('orderNumber') as number) ?? undefined, + nodeMapId: mapId, + } +} + +// Populates the mapOptions Y.Map from an MmpMap entity +export const populateYMapFromMapOptions = ( + optionsMap: Y.Map, + map: MmpMap +): void => { + optionsMap.set('name', map.name ?? '') + if (map.options) { + optionsMap.set('fontMaxSize', map.options.fontMaxSize) + optionsMap.set('fontMinSize', map.options.fontMinSize) + optionsMap.set('fontIncrement', map.options.fontIncrement) + } +} + +// Extracts map options from the mapOptions Y.Map +export const yMapToMapOptions = ( + optionsMap: Y.Map +): { name: string | null; options: MapOptions } => { + return { + name: (optionsMap.get('name') as string) || null, + options: { + fontMaxSize: (optionsMap.get('fontMaxSize') as number) ?? 28, + fontMinSize: (optionsMap.get('fontMinSize') as number) ?? 6, + fontIncrement: (optionsMap.get('fontIncrement') as number) ?? 2, + }, + } +} + +// Hydrates a Y.Doc from database entities +export const hydrateYDoc = ( + doc: Y.Doc, + nodes: MmpNode[], + map: MmpMap +): void => { + doc.transact(() => { + const nodesMap = doc.getMap('nodes') as Y.Map> + for (const node of nodes) { + populateYMapFromNode(nodesMap, node) + } + const optionsMap = doc.getMap('mapOptions') as Y.Map + populateYMapFromMapOptions(optionsMap, map) + }) +} diff --git a/teammapper-backend/src/map/utils/yjsProtocol.spec.ts b/teammapper-backend/src/map/utils/yjsProtocol.spec.ts new file mode 100644 index 000000000..445a023b0 --- /dev/null +++ b/teammapper-backend/src/map/utils/yjsProtocol.spec.ts @@ -0,0 +1,225 @@ +import * as Y from 'yjs' +import * as syncProtocol from 'y-protocols/sync' +import * as awarenessProtocol from 'y-protocols/awareness' +import * as encoding from 'lib0/encoding' +import * as decoding from 'lib0/decoding' +import { + MESSAGE_SYNC, + MESSAGE_AWARENESS, + encodeSyncStep1Message, + encodeSyncUpdateMessage, + encodeAwarenessMessage, + processReadOnlySyncMessage, + parseAwarenessClientIds, + extractPathname, + parseQueryParams, + checkWriteAccess, + toUint8Array, +} from './yjsProtocol' + +describe('yjsProtocol', () => { + describe('encodeSyncStep1Message', () => { + it('produces a valid sync step 1 message', () => { + const doc = new Y.Doc() + const message = encodeSyncStep1Message(doc) + + const decoder = decoding.createDecoder(message) + expect(decoding.readVarUint(decoder)).toBe(MESSAGE_SYNC) + expect(decoding.readVarUint(decoder)).toBe( + syncProtocol.messageYjsSyncStep1 + ) + + doc.destroy() + }) + }) + + describe('encodeSyncUpdateMessage', () => { + it('produces a valid sync update message', () => { + const doc = new Y.Doc() + doc.getMap('test').set('key', 'value') + const update = Y.encodeStateAsUpdate(doc) + + const message = encodeSyncUpdateMessage(update) + + const decoder = decoding.createDecoder(message) + expect(decoding.readVarUint(decoder)).toBe(MESSAGE_SYNC) + expect(decoding.readVarUint(decoder)).toBe(syncProtocol.messageYjsUpdate) + + doc.destroy() + }) + }) + + describe('encodeAwarenessMessage', () => { + it('produces a valid awareness message', () => { + const doc = new Y.Doc() + const awareness = new awarenessProtocol.Awareness(doc) + awareness.setLocalState({ user: 'test' }) + + const message = encodeAwarenessMessage( + awareness, + Array.from(awareness.getStates().keys()) + ) + + const decoder = decoding.createDecoder(message) + expect(decoding.readVarUint(decoder)).toBe(MESSAGE_AWARENESS) + + awareness.destroy() + doc.destroy() + }) + }) + + describe('processReadOnlySyncMessage', () => { + it('processes SyncStep1 requests from read-only clients', () => { + const doc = new Y.Doc() + doc.getMap('nodes').set('node-1', new Y.Map()) + + const clientDoc = new Y.Doc() + const reqEncoder = encoding.createEncoder() + syncProtocol.writeSyncStep1(reqEncoder, clientDoc) + const requestBytes = encoding.toUint8Array(reqEncoder) + + const decoder = decoding.createDecoder(requestBytes) + const responseEncoder = encoding.createEncoder() + processReadOnlySyncMessage(decoder, responseEncoder, doc) + + // Should have written a SyncStep2 response + expect(encoding.length(responseEncoder)).toBeGreaterThan(0) + + clientDoc.destroy() + doc.destroy() + }) + + it('drops update messages from read-only clients', () => { + const doc = new Y.Doc() + const clientDoc = new Y.Doc() + clientDoc.getMap('nodes').set('node-1', new Y.Map()) + + // Build a raw update message (messageYjsUpdate = 2) + const reqEncoder = encoding.createEncoder() + syncProtocol.writeUpdate(reqEncoder, Y.encodeStateAsUpdate(clientDoc)) + const requestBytes = encoding.toUint8Array(reqEncoder) + + const decoder = decoding.createDecoder(requestBytes) + const responseEncoder = encoding.createEncoder() + processReadOnlySyncMessage(decoder, responseEncoder, doc) + + // Should not have written any response + expect(encoding.length(responseEncoder)).toBe(0) + expect(doc.getMap('nodes').size).toBe(0) + + clientDoc.destroy() + doc.destroy() + }) + }) + + describe('parseAwarenessClientIds', () => { + it('extracts client IDs from an awareness update', () => { + const doc = new Y.Doc() + const awareness = new awarenessProtocol.Awareness(doc) + awareness.setLocalState({ user: 'test' }) + + const update = awarenessProtocol.encodeAwarenessUpdate(awareness, [ + doc.clientID, + ]) + const clientIds = parseAwarenessClientIds(update) + + expect(clientIds).toEqual([doc.clientID]) + + awareness.destroy() + doc.destroy() + }) + + it('throws on malformed binary data', () => { + expect(() => parseAwarenessClientIds(new Uint8Array([255]))).toThrow() + }) + }) + + describe('extractPathname', () => { + it('extracts pathname from a URL', () => { + expect(extractPathname('/yjs?mapId=123')).toBe('/yjs') + }) + + it('returns empty string for undefined', () => { + expect(extractPathname(undefined)).toBe('') + }) + + it('returns empty string for empty string', () => { + expect(extractPathname('')).toBe('') + }) + + it('handles complex paths', () => { + expect(extractPathname('/api/yjs?a=1&b=2')).toBe('/api/yjs') + }) + }) + + describe('parseQueryParams', () => { + it('parses mapId and secret', () => { + expect(parseQueryParams('/yjs?mapId=abc&secret=xyz')).toEqual({ + mapId: 'abc', + secret: 'xyz', + }) + }) + + it('returns nulls for missing params', () => { + expect(parseQueryParams('/yjs')).toEqual({ + mapId: null, + secret: null, + }) + }) + + it('returns nulls for undefined URL', () => { + expect(parseQueryParams(undefined)).toEqual({ + mapId: null, + secret: null, + }) + }) + + it('parses mapId without secret', () => { + expect(parseQueryParams('/yjs?mapId=abc')).toEqual({ + mapId: 'abc', + secret: null, + }) + }) + }) + + describe('checkWriteAccess', () => { + it('grants write when map has no secret', () => { + expect(checkWriteAccess(null, null)).toBe(true) + expect(checkWriteAccess(null, 'any')).toBe(true) + }) + + it('grants write when secrets match', () => { + expect(checkWriteAccess('secret', 'secret')).toBe(true) + }) + + it('denies write when secrets differ', () => { + expect(checkWriteAccess('secret', 'wrong')).toBe(false) + }) + + it('denies write when no secret given', () => { + expect(checkWriteAccess('secret', null)).toBe(false) + }) + }) + + describe('toUint8Array', () => { + it('converts Buffer to Uint8Array', () => { + const buf = Buffer.from([1, 2, 3]) + const result = toUint8Array(buf) + expect(result).toBeInstanceOf(Uint8Array) + expect(Array.from(result)).toEqual([1, 2, 3]) + }) + + it('converts ArrayBuffer to Uint8Array', () => { + const ab = new ArrayBuffer(3) + new Uint8Array(ab).set([4, 5, 6]) + const result = toUint8Array(ab) + expect(Array.from(result)).toEqual([4, 5, 6]) + }) + + it('converts Buffer[] to Uint8Array', () => { + const bufs = [Buffer.from([1, 2]), Buffer.from([3, 4])] + const result = toUint8Array(bufs) + expect(Array.from(result)).toEqual([1, 2, 3, 4]) + }) + }) +}) diff --git a/teammapper-backend/src/map/utils/yjsProtocol.ts b/teammapper-backend/src/map/utils/yjsProtocol.ts new file mode 100644 index 000000000..eff8f8dca --- /dev/null +++ b/teammapper-backend/src/map/utils/yjsProtocol.ts @@ -0,0 +1,155 @@ +import * as Y from 'yjs' +import * as syncProtocol from 'y-protocols/sync' +import * as awarenessProtocol from 'y-protocols/awareness' +import * as encoding from 'lib0/encoding' +import * as decoding from 'lib0/decoding' + +// Message types matching y-websocket client protocol +export const MESSAGE_SYNC = 0 +export const MESSAGE_AWARENESS = 1 +// Custom message type for communicating write access to clients +// (types 2=auth and 3=queryAwareness are reserved by y-websocket) +export const MESSAGE_WRITE_ACCESS = 4 + +// WebSocket close codes (4000-4999 private-use range per RFC 6455) +export const WS_CLOSE_MISSING_PARAM = 4000 +export const WS_CLOSE_MAP_DELETED = 4001 +export const WS_CLOSE_MAP_NOT_FOUND = 4004 +export const WS_CLOSE_INTERNAL_ERROR = 4500 +export const WS_CLOSE_SERVER_SHUTDOWN = 1001 + +// 1 MiB — generous for mind map sync messages (typically 1–50 KB) +export const WS_MAX_PAYLOAD = 1_048_576 + +// Heartbeat interval for zombie connection detection (ms) +export const HEARTBEAT_INTERVAL_MS = 30_000 + +// Connection setup timeout (ms) — slow DB responses close with 1013 +export const CONNECTION_SETUP_TIMEOUT_MS = 10_000 + +// Standard WebSocket close code: Try Again Later (RFC 6455) +export const WS_CLOSE_TRY_AGAIN = 1013 + +export interface ConnectionMeta { + mapId: string + writable: boolean + awarenessClientIds: Set + isAlive: boolean + ip: string +} + +export interface ParsedQueryParams { + mapId: string | null + secret: string | null +} + +// Encodes a SyncStep1 message for initial doc state exchange +export const encodeSyncStep1Message = (doc: Y.Doc): Uint8Array => { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, MESSAGE_SYNC) + syncProtocol.writeSyncStep1(encoder, doc) + return encoding.toUint8Array(encoder) +} + +// Encodes a sync update message for broadcasting doc changes +export const encodeSyncUpdateMessage = (update: Uint8Array): Uint8Array => { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, MESSAGE_SYNC) + syncProtocol.writeUpdate(encoder, update) + return encoding.toUint8Array(encoder) +} + +// Encodes an awareness update message for broadcasting presence +export const encodeAwarenessMessage = ( + awareness: awarenessProtocol.Awareness, + clients: number[] +): Uint8Array => { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, MESSAGE_AWARENESS) + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate(awareness, clients) + ) + return encoding.toUint8Array(encoder) +} + +// Encodes a write-access message to inform the client of its permissions +export const encodeWriteAccessMessage = (writable: boolean): Uint8Array => { + const encoder = encoding.createEncoder() + encoding.writeVarUint(encoder, MESSAGE_WRITE_ACCESS) + encoding.writeVarUint(encoder, writable ? 1 : 0) + return encoding.toUint8Array(encoder) +} + +// For read-only clients: process SyncStep1 (state request), drop writes +export const processReadOnlySyncMessage = ( + decoder: decoding.Decoder, + encoder: encoding.Encoder, + doc: Y.Doc +): void => { + const syncMsgType = decoding.readVarUint(decoder) + if (syncMsgType === syncProtocol.messageYjsSyncStep1) { + syncProtocol.readSyncStep1(decoder, encoder, doc) + } + // Drop SyncStep2 and Update messages from read-only clients +} + +// Extracts awareness clientIds from a raw awareness update binary +export const parseAwarenessClientIds = (update: Uint8Array): number[] => { + const clientIds: number[] = [] + const decoder = decoding.createDecoder(update) + const len = decoding.readVarUint(decoder) + for (let i = 0; i < len; i++) { + clientIds.push(decoding.readVarUint(decoder)) + // Skip remaining fields (clock + state) + decoding.readVarUint(decoder) + decoding.readVarString(decoder) + } + return clientIds +} + +// Extracts the pathname from a potentially relative URL +export const extractPathname = (url: string | undefined): string => { + if (!url) return '' + try { + return new URL(url, 'http://localhost').pathname + } catch { + return '' + } +} + +// Parses mapId and secret from WebSocket URL query or path params +// Supports both legacy (/yjs?mapId=) and y-websocket (/yjs/?secret=...) +export const parseQueryParams = ( + url: string | undefined +): ParsedQueryParams => { + if (!url) return { mapId: null, secret: null } + try { + const parsed = new URL(url, 'http://localhost') + const pathParts = parsed.pathname.split('/').filter(Boolean) + const mapId = + parsed.searchParams.get('mapId') ?? + (pathParts.length >= 2 ? pathParts[1] : null) + return { mapId, secret: parsed.searchParams.get('secret') } + } catch { + return { mapId: null, secret: null } + } +} + +// Checks write access: writable if map has no secret, or secret matches +export const checkWriteAccess = ( + modificationSecret: string | null, + givenSecret: string | null +): boolean => { + if (!modificationSecret) return true + return givenSecret === modificationSecret +} + +// Converts ws message data to Uint8Array +export const toUint8Array = ( + data: Buffer | ArrayBuffer | Buffer[] +): Uint8Array => { + if (data instanceof ArrayBuffer) return new Uint8Array(data) + if (Array.isArray(data)) return new Uint8Array(Buffer.concat(data)) + return new Uint8Array(data) +} diff --git a/teammapper-backend/src/settings/settings.service.ts b/teammapper-backend/src/settings/settings.service.ts index 761bd6a80..5aaa0bafb 100644 --- a/teammapper-backend/src/settings/settings.service.ts +++ b/teammapper-backend/src/settings/settings.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common' import * as fs from 'fs' import * as path from 'path' import deepmerge from 'deepmerge' +import configService from '../config.service' +import { Settings } from './settings.types' @Injectable() export class SettingsService { @@ -18,7 +20,7 @@ export class SettingsService { 'config/settings.override.json' ) - getSettings() { + getSettings(): Settings { const defaultFileData = fs.readFileSync(this.defaultSettingsPath, 'utf-8') const defaultSettings = JSON.parse(defaultFileData) @@ -35,6 +37,8 @@ export class SettingsService { overrideSettings = JSON.parse(overrideFileData) } - return deepmerge(defaultSettings, overrideSettings) + const settings = deepmerge(defaultSettings, overrideSettings) as Settings + settings.systemSettings.featureFlags.yjs = configService.isYjsEnabled() + return settings } } diff --git a/teammapper-backend/src/settings/settings.types.ts b/teammapper-backend/src/settings/settings.types.ts new file mode 100644 index 000000000..16df5b834 --- /dev/null +++ b/teammapper-backend/src/settings/settings.types.ts @@ -0,0 +1,58 @@ +export interface NodeColors { + name: string + background: string + branch?: string +} + +export interface NodeFont { + size: number + style: string + weight: string +} + +export interface NodeLink { + href: string +} + +export interface NodeImage { + src: string + size: number +} + +export interface NodeSettings { + name: string + link: NodeLink + image: NodeImage + colors: NodeColors + font: NodeFont + locked?: boolean +} + +export interface MapOptions { + centerOnResize: boolean + autoBranchColors: boolean + showLinktext: boolean + fontMaxSize: number + fontMinSize: number + fontIncrement: number + defaultNode: NodeSettings + rootNode: NodeSettings +} + +export interface FeatureFlags { + pictograms: boolean + ai: boolean + yjs: boolean +} + +export interface Settings { + systemSettings: { + info: { name: string; version: string } + urls: { pictogramApiUrl: string; pictogramStaticUrl: string } + featureFlags: FeatureFlags + } + userSettings: { + general: { language: string } + mapOptions: MapOptions + } +} diff --git a/teammapper-backend/test/jest-e2e.json b/teammapper-backend/test/jest-e2e.json index 48fb5a49d..3d56d82e9 100644 --- a/teammapper-backend/test/jest-e2e.json +++ b/teammapper-backend/test/jest-e2e.json @@ -6,5 +6,6 @@ "transform": { "^.+\\.(t|j)s$": "ts-jest" }, - "modulePaths": [""] + "modulePaths": [""], + "setupFiles": ["./test/jest-setup.ts"] } diff --git a/teammapper-backend/test/jest-setup.ts b/teammapper-backend/test/jest-setup.ts new file mode 100644 index 000000000..644679e14 --- /dev/null +++ b/teammapper-backend/test/jest-setup.ts @@ -0,0 +1,7 @@ +// Disable Yjs feature flag so that MapModule includes the legacy Socket.io +// MapsGateway in its providers. When YJS_ENABLED is true, MapModule excludes +// MapsGateway because both gateways cannot bind to the same HTTP upgrade path +// simultaneously in production. The e2e tests (app.e2e-spec, map-operations-error.e2e-spec) +// rely on the Socket.io gateway for join/addNodes/updateNode events, so it must +// be registered. Unit tests that need Yjs enabled mock configService directly. +process.env.YJS_ENABLED = 'false' diff --git a/teammapper-frontend/e2e/navigation.spec.ts b/teammapper-frontend/e2e/navigation.spec.ts index bb60e9f5e..5468be7d0 100644 --- a/teammapper-frontend/e2e/navigation.spec.ts +++ b/teammapper-frontend/e2e/navigation.spec.ts @@ -10,6 +10,8 @@ test('navigates to settings page and back to map', async ({ page }) => { await expect(page.locator('.settings')).toBeVisible(); // Check for the settings title - the text might be translated await expect(page.locator('h2[mat-dialog-title]')).toBeVisible(); + // Wait for route animation to finish so old map component is fully removed + await expect(page.locator('.map')).toHaveCount(0); // Navigate back await page.locator('.close-button').click(); diff --git a/teammapper-frontend/package.json b/teammapper-frontend/package.json index 3b5bb740e..2c81fedbc 100644 --- a/teammapper-frontend/package.json +++ b/teammapper-frontend/package.json @@ -40,23 +40,23 @@ "format": "npx prettier . --write" }, "dependencies": { - "@angular-devkit/build-angular": "21.0.4", - "@angular/animations": "21.0.6", - "@angular/cdk": "21.0.5", - "@angular/cli": "21.0.4", - "@angular/common": "21.0.6", - "@angular/compiler": "21.0.6", - "@angular/compiler-cli": "21.0.6", - "@angular/core": "21.0.6", - "@angular/forms": "21.0.6", - "@angular/material": "21.0.5", - "@angular/platform-browser": "21.0.6", - "@angular/platform-browser-dynamic": "21.0.6", - "@angular/router": "21.0.6", + "@angular-devkit/build-angular": "21.1.4", + "@angular/animations": "21.1.4", + "@angular/cdk": "21.1.4", + "@angular/cli": "21.1.4", + "@angular/common": "21.1.4", + "@angular/compiler": "21.1.4", + "@angular/compiler-cli": "21.1.4", + "@angular/core": "21.1.4", + "@angular/forms": "21.1.4", + "@angular/material": "21.1.4", + "@angular/platform-browser": "21.1.4", + "@angular/platform-browser-dynamic": "21.1.4", + "@angular/router": "21.1.4", "@fortawesome/angular-fontawesome": "^4.0.0", - "@fortawesome/fontawesome-svg-core": "^7.1.0", - "@fortawesome/free-brands-svg-icons": "^7.1.0", - "@fortawesome/free-solid-svg-icons": "^7.1.0", + "@fortawesome/fontawesome-svg-core": "^7.2.0", + "@fortawesome/free-brands-svg-icons": "^7.2.0", + "@fortawesome/free-solid-svg-icons": "^7.2.0", "@material-design-icons/font": "^0.14.15", "@ngx-translate/core": "^17.0.0", "@ngx-translate/http-loader": "^17.0.0", @@ -66,59 +66,61 @@ "d3": "7.9.0", "deep-object-diff": "^1.1.9", "dompurify": "3.3.1", - "jspdf": "^3.0.4", + "jspdf": "^4.1.0", "localforage": "1.10.0", "ngx-color-picker": "^20.1.1", "ngx-toastr": "^19.1.0", "qr-code-styling": "1.9.2", "rxjs": "~7.8.2", - "socket.io-client": "~4.8.1", + "socket.io-client": "~4.8.3", "tslib": "^2.8.1", "uuid": "13.0.0", + "y-websocket": "^3.0.0", + "yjs": "^13.6.29", "zone.js": "^0.16.0" }, "devDependencies": { - "@angular-builders/jest": "^21.0.0-beta.1", - "@angular-devkit/architect": "0.2100.4", - "@angular-devkit/core": "21.0.4", - "@angular-devkit/schematics": "21.0.4", - "@angular-eslint/builder": "21.1.0", - "@angular-eslint/eslint-plugin": "21.1.0", - "@angular-eslint/eslint-plugin-template": "21.1.0", - "@angular-eslint/schematics": "21.1.0", - "@angular-eslint/template-parser": "21.1.0", - "@angular/language-service": "21.0.6", - "@compodoc/compodoc": "^1.1.32", + "@angular-builders/jest": "^21.0.3", + "@angular-devkit/architect": "0.2101.4", + "@angular-devkit/core": "21.1.4", + "@angular-devkit/schematics": "21.1.4", + "@angular-eslint/builder": "21.2.0", + "@angular-eslint/eslint-plugin": "21.2.0", + "@angular-eslint/eslint-plugin-template": "21.2.0", + "@angular-eslint/schematics": "21.2.0", + "@angular-eslint/template-parser": "21.2.0", + "@angular/language-service": "21.1.4", + "@compodoc/compodoc": "^1.2.1", "@eslint/js": "^9.39.2", - "@playwright/test": "^1.57.0", - "@schematics/angular": "^21.0.4", + "@playwright/test": "^1.58.2", + "@schematics/angular": "^21.1.4", "@types/d3": "7.4.3", "@types/jest": "30.0.0", "@types/mousetrap": "1.6.15", - "@types/node": "^25.0.3", - "@typescript-eslint/eslint-plugin": "^8.50.0", - "@typescript-eslint/parser": "^8.50.0", - "angular-eslint": "^21.1.0", + "@types/node": "^25.2.3", + "@typescript-eslint/eslint-plugin": "^8.55.0", + "@typescript-eslint/parser": "^8.55.0", + "angular-eslint": "^21.2.0", "eslint": "^9.39.2", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-jest": "^29.6.0", - "eslint-plugin-prettier": "^5.5.4", - "globals": "^16.5.0", + "eslint-plugin-jest": "^29.15.0", + "eslint-plugin-prettier": "^5.5.5", + "globals": "^17.3.0", "jest": "^30.2.0", - "jest-environment-jsdom": "^30.2.0", "jest-canvas-mock": "^2.5.2", + "jest-environment-jsdom": "^30.2.0", "jest-preset-angular": "^16.0.0", "minimist": "^1.2.8", - "prettier": "^3.7.4", + "prettier": "^3.8.1", "ts-node": "~10.9.2", "typescript": "~5.9.3", - "typescript-eslint": "^8.50.0" + "typescript-eslint": "^8.55.0" }, "optionalDependencies": { - "@nx/nx-darwin-arm64": "22.3.3", - "@nx/nx-darwin-x64": "22.3.3", - "@nx/nx-linux-x64-gnu": "22.3.3", - "@nx/nx-win32-x64-msvc": "22.3.3" + "@nx/nx-darwin-arm64": "22.5.1", + "@nx/nx-darwin-x64": "22.5.1", + "@nx/nx-linux-x64-gnu": "22.5.1", + "@nx/nx-win32-x64-msvc": "22.5.1" }, "workspaces": [ "packages/*" diff --git a/teammapper-frontend/packages/mermaid-mindmap-parser/package.json b/teammapper-frontend/packages/mermaid-mindmap-parser/package.json index a6ef03e3e..19b27af38 100644 --- a/teammapper-frontend/packages/mermaid-mindmap-parser/package.json +++ b/teammapper-frontend/packages/mermaid-mindmap-parser/package.json @@ -32,6 +32,6 @@ "devDependencies": { "typescript": "~5.9.3", "jison": "0.4.18", - "vite": "^7.3.0" + "vite": "^7.3.1" } } 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 ca69191be..3e616d1b0 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 @@ -18,6 +18,18 @@ 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 * as Y from 'yjs'; +import { + populateYMapFromNodeProps, + yMapToNodeProps, + buildYjsWsUrl, + parseWriteAccessBytes, + resolveClientColor, + findAffectedNodes, + resolveMmpPropertyUpdate, + resolveCompoundMmpUpdates, + sortParentFirst, +} from './yjs-utils'; // Mock the NodePropertyMapping module jest.mock('@mmp/index', () => ({ @@ -42,34 +54,21 @@ jest.mock('@mmp/index', () => ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars import { NodePropertyMapping } from '@mmp/index'; -/** - * Type helper for accessing private members of MapSyncService in tests. - * Private methods contain important logic that needs unit testing. - */ -interface MapSyncServicePrivate { +// Minimal private access for tests that must interact with Socket.io internals +interface MapSyncServiceSocketAccess { socket: { emit: jest.Mock; removeAllListeners: jest.Mock; on?: jest.Mock; - io?: { - on: jest.Mock; - }; + io?: { on: jest.Mock }; }; - getUserFriendlyErrorMessage: (code: string, msg: string) => Promise; + emitAddNode: (node: ExportNodeProperties) => void; } -/** - * Helper function to safely access private members of MapSyncService. - * Uses double cast to bypass TypeScript's protection of private members. - */ -function asPrivate(service: MapSyncService): MapSyncServicePrivate { - return service as unknown as MapSyncServicePrivate; +function asSocketAccess(service: MapSyncService): MapSyncServiceSocketAccess { + return service as unknown as MapSyncServiceSocketAccess; } -/** - * Factory function to create fully-typed mock nodes. - * Ensures all required properties are present, preventing partial mock type assertions. - */ function createMockNode( overrides?: Partial ): ExportNodeProperties { @@ -137,6 +136,12 @@ describe('MapSyncService', () => { selectNode: jest.fn(), getRootNode: jest.fn(), on: jest.fn(), + updateNode: jest.fn(), + existNode: jest.fn().mockReturnValue(true), + addNodesFromServer: jest.fn(), + removeNode: jest.fn(), + highlightNode: jest.fn(), + exportAsJSON: jest.fn().mockReturnValue([]), } as unknown as jest.Mocked; httpService = { @@ -151,6 +156,10 @@ describe('MapSyncService', () => { settingsService = { getCachedUserSettings: jest.fn(), + getCachedSystemSettings: jest.fn().mockReturnValue({ + featureFlags: { yjs: false, pictograms: false, ai: false }, + }), + setEditMode: jest.fn(), } as unknown as jest.Mocked; toastService = { @@ -222,7 +231,7 @@ describe('MapSyncService', () => { emit: emitSpy, removeAllListeners: jest.fn(), }; - asPrivate(service).socket = socketSpy; + asSocketAccess(service).socket = socketSpy; jest.spyOn(service, 'getAttachedMap').mockReturnValue({ key: 'map-test', cachedMap: { @@ -236,10 +245,10 @@ describe('MapSyncService', () => { }, }); - service.addNode(mockNode); + asSocketAccess(service).emitAddNode(mockNode); }); - it('success response does nothing', async () => { + it('success response triggers no side effects', async () => { const successResponse: SuccessResponse = { success: true, data: [mockNode], @@ -247,9 +256,11 @@ describe('MapSyncService', () => { await handleResponse(successResponse); - expect(mmpService.new).not.toHaveBeenCalled(); - expect(toastService.showValidationCorrection).not.toHaveBeenCalled(); - expect(dialogService.openCriticalErrorDialog).not.toHaveBeenCalled(); + 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 () => { @@ -345,12 +356,10 @@ describe('MapSyncService', () => { await handleResponse(errorResponse); - // Should treat as malformed response since fullMapState is invalid expect(dialogService.openCriticalErrorDialog).toHaveBeenCalledWith({ code: 'MALFORMED_RESPONSE', message: expect.stringContaining('invalid response'), }); - expect(mmpService.new).not.toHaveBeenCalled(); }); it('error without fullMapState with translation failure uses fallback', async () => { @@ -375,26 +384,6 @@ describe('MapSyncService', () => { }); }); - describe('getUserFriendlyErrorMessage', () => { - it('returns user-friendly message for known codes', async () => { - const message = await asPrivate(service).getUserFriendlyErrorMessage( - 'SERVER_ERROR', - 'CRITICAL_ERROR.SERVER_ERROR' - ); - - expect(message).toContain('server encountered an error'); - }); - - it('returns default message for unknown codes', async () => { - const message = await asPrivate(service).getUserFriendlyErrorMessage( - 'UNKNOWN_CODE', - 'CRITICAL_ERROR.UNKNOWN' - ); - - expect(message).toContain('unexpected error occurred'); - }); - }); - describe('map initialization', () => { beforeEach(() => { const mockCachedMapEntry = { @@ -421,7 +410,7 @@ describe('MapSyncService', () => { }, }; - asPrivate(service).socket = socketSpy; + asSocketAccess(service).socket = socketSpy; }); it('loads map data into mmpService on initMap', () => { @@ -445,3 +434,402 @@ describe('MapSyncService', () => { }); }); }); + +// ─── Yjs utility function tests (pure functions) ────────────── + +describe('Y.Doc conversion utilities', () => { + let doc: Y.Doc; + let nodesMap: Y.Map>; + + beforeEach(() => { + doc = new Y.Doc(); + nodesMap = doc.getMap('nodes') as Y.Map>; + }); + + afterEach(() => { + doc.destroy(); + }); + + it('round-trips node properties through Y.Map', () => { + const input = createMockNode({ + id: 'n1', + name: 'Hello', + parent: 'root', + k: 1.5, + isRoot: false, + locked: true, + detached: true, + coordinates: { x: 100, y: 200 }, + colors: { name: '#ff0000', background: '#00ff00', branch: '#0000ff' }, + font: { size: 16, style: 'italic', weight: 'bold' }, + image: { src: 'http://img.png', size: 50 }, + link: { href: 'http://example.com' }, + }); + + const yNode = new Y.Map(); + populateYMapFromNodeProps(yNode, input); + nodesMap.set('n1', yNode); + + const result = yMapToNodeProps(nodesMap.get('n1')!); + + expect(result).toEqual( + expect.objectContaining({ + id: 'n1', + name: 'Hello', + parent: 'root', + k: 1.5, + isRoot: false, + locked: true, + detached: true, + coordinates: { x: 100, y: 200 }, + colors: { + name: '#ff0000', + background: '#00ff00', + branch: '#0000ff', + }, + font: { size: 16, style: 'italic', weight: 'bold' }, + image: { src: 'http://img.png', size: 50 }, + link: { href: 'http://example.com' }, + }) + ); + }); + + it('applies defaults for missing optional properties', () => { + const input: ExportNodeProperties = { + id: 'n2', + parent: undefined, + k: undefined, + name: undefined, + isRoot: undefined, + locked: undefined, + detached: undefined, + coordinates: undefined, + colors: undefined, + font: undefined, + image: undefined, + link: undefined, + } as unknown as ExportNodeProperties; + + const yNode = new Y.Map(); + populateYMapFromNodeProps(yNode, input); + nodesMap.set('n2', yNode); + + const result = yMapToNodeProps(nodesMap.get('n2')!); + + expect(result).toEqual( + expect.objectContaining({ + parent: null, + k: 1, + name: '', + isRoot: false, + locked: false, + detached: false, + coordinates: { x: 0, y: 0 }, + }) + ); + }); +}); + +describe('write access message parsing', () => { + it('returns true for writable message', () => { + const result = parseWriteAccessBytes(new Uint8Array([4, 1])); + expect(result).toBe(true); + }); + + it('returns false for read-only message', () => { + const result = parseWriteAccessBytes(new Uint8Array([4, 0])); + expect(result).toBe(false); + }); + + it('returns null for wrong type byte', () => { + const result = parseWriteAccessBytes(new Uint8Array([0, 1])); + expect(result).toBeNull(); + }); + + it('returns null for message too short to be valid', () => { + const result = parseWriteAccessBytes(new Uint8Array([4])); + expect(result).toBeNull(); + }); +}); + +describe('Yjs URL building', () => { + let querySelectorSpy: jest.SpyInstance; + + beforeEach(() => { + querySelectorSpy = jest.spyOn(document, 'querySelector'); + }); + + afterEach(() => { + querySelectorSpy.mockRestore(); + }); + + // jsdom default location is http://localhost, so tests use that baseline + it('builds ws URL and uses document base href', () => { + querySelectorSpy.mockReturnValue({ + getAttribute: () => '/', + }); + + const url = buildYjsWsUrl(); + + // jsdom runs on http://localhost -> ws: + expect(url).toBe('ws://localhost/yjs'); + }); + + it('incorporates base href into path', () => { + querySelectorSpy.mockReturnValue({ + getAttribute: () => '/app/', + }); + + const url = buildYjsWsUrl(); + + expect(url).toBe('ws://localhost/app/yjs'); + }); + + it('appends trailing slash to base href if missing', () => { + querySelectorSpy.mockReturnValue({ + getAttribute: () => '/app', + }); + + const url = buildYjsWsUrl(); + + expect(url).toBe('ws://localhost/app/yjs'); + }); + + it('defaults base href to / when no base element', () => { + querySelectorSpy.mockReturnValue(null); + + const url = buildYjsWsUrl(); + + expect(url).toBe('ws://localhost/yjs'); + }); + + it('selects protocol based on page protocol', () => { + // Verify the protocol-selection logic via the method output + // jsdom defaults to http: -> ws:, confirming the mapping works + querySelectorSpy.mockReturnValue(null); + const url = buildYjsWsUrl(); + expect(url).toMatch(/^ws:\/\//); + // The https: -> wss: path uses the same ternary expression + }); +}); + +describe('Y.Doc property application to MMP', () => { + it('resolves simple property (name) via reverse mapping', () => { + const updates = resolveMmpPropertyUpdate('name', 'New Name'); + expect(updates).toEqual([{ prop: 'name', val: 'New Name' }]); + }); + + it('resolves simple property (locked) via reverse mapping', () => { + const updates = resolveMmpPropertyUpdate('locked', true); + expect(updates).toEqual([{ prop: 'locked', val: true }]); + }); + + it('resolves compound property (colors) via reverse mapping', () => { + const updates = resolveMmpPropertyUpdate('colors', { + background: '#ff0000', + branch: '#00ff00', + name: '#0000ff', + }); + + expect(updates).toEqual([ + { prop: 'backgroundColor', val: '#ff0000' }, + { prop: 'branchColor', val: '#00ff00' }, + { prop: 'nameColor', val: '#0000ff' }, + ]); + }); + + it('resolves compound property (font) via reverse mapping', () => { + const updates = resolveMmpPropertyUpdate('font', { + size: 20, + weight: 'bold', + style: 'italic', + }); + + expect(updates).toEqual( + expect.arrayContaining([ + { prop: 'fontSize', val: 20 }, + { prop: 'fontWeight', val: 'bold' }, + { prop: 'fontStyle', val: 'italic' }, + ]) + ); + }); + + it('returns empty array for unknown property keys', () => { + const updates = resolveMmpPropertyUpdate('unknown_key', 'value'); + expect(updates).toEqual([]); + }); + + it('handles null compound value gracefully', () => { + const updates = resolveCompoundMmpUpdates( + { background: 'backgroundColor' }, + null as unknown as Record + ); + expect(updates).toEqual([]); + }); +}); + +describe('client color resolution', () => { + it('returns existing color when no collision', () => { + const result = resolveClientColor( + '#ff0000', + new Set(['#00ff00', '#0000ff']) + ); + expect(result).toBe('#ff0000'); + }); + + it('generates a different valid hex color on collision', () => { + const result = resolveClientColor('#00ff00', new Set(['#00ff00'])); + expect(result).toMatch(/^#(?!00ff00)[0-9a-f]{6}$/); + }); + + it('handles empty used colors set', () => { + const result = resolveClientColor('#ff0000', new Set()); + expect(result).toBe('#ff0000'); + }); +}); + +describe('findAffectedNodes', () => { + it('collects node IDs from both old and new mappings', () => { + const oldMapping = { + c1: { nodeId: 'node-a', color: '#ff0000' }, + c2: { nodeId: 'node-b', color: '#00ff00' }, + }; + + const newMapping = { + c1: { nodeId: 'node-b', color: '#ff0000' }, + c3: { nodeId: 'node-c', color: '#0000ff' }, + }; + + const result = findAffectedNodes(oldMapping, newMapping); + + expect(result).toEqual(new Set(['node-a', 'node-b', 'node-c'])); + }); + + it('excludes empty nodeId strings', () => { + const oldMapping = { + c1: { nodeId: '', color: '#ff0000' }, + }; + + const newMapping = { + c1: { nodeId: 'node-a', color: '#ff0000' }, + }; + + const result = findAffectedNodes(oldMapping, newMapping); + + expect(result).toEqual(new Set(['node-a'])); + }); + + it('returns empty set when no nodes selected', () => { + const oldMapping = { + c1: { nodeId: '', color: '#ff0000' }, + }; + + const newMapping = { + c1: { nodeId: '', color: '#ff0000' }, + }; + + const result = findAffectedNodes(oldMapping, newMapping); + + expect(result.size).toBe(0); + }); +}); + +describe('sortParentFirst', () => { + it('places root node first when children appear before parent', () => { + const child = createMockNode({ + id: 'child-1', + parent: 'root-1', + isRoot: false, + }); + const root = createMockNode({ id: 'root-1', parent: '', isRoot: true }); + + const result = sortParentFirst([child, root]); + + expect(result.map(n => n.id)).toEqual(['root-1', 'child-1']); + }); + + it('ensures grandchild nodes come after their parent', () => { + const grandchild = createMockNode({ + id: 'gc-1', + parent: 'child-1', + isRoot: false, + }); + const child = createMockNode({ + id: 'child-1', + parent: 'root-1', + isRoot: false, + }); + const root = createMockNode({ id: 'root-1', parent: '', isRoot: true }); + + const result = sortParentFirst([grandchild, child, root]); + + expect(result.map(n => n.id)).toEqual(['root-1', 'child-1', 'gc-1']); + }); + + it('handles already-sorted input without changing order', () => { + const root = createMockNode({ id: 'root-1', parent: '', isRoot: true }); + const child1 = createMockNode({ + id: 'c1', + parent: 'root-1', + isRoot: false, + }); + const child2 = createMockNode({ + id: 'c2', + parent: 'root-1', + isRoot: false, + }); + + const result = sortParentFirst([root, child1, child2]); + + expect(result.map(n => n.id)).toEqual(['root-1', 'c1', 'c2']); + }); + + it('returns original array when no root is found', () => { + const node1 = createMockNode({ id: 'n1', parent: 'n2', isRoot: false }); + const node2 = createMockNode({ id: 'n2', parent: 'n1', isRoot: false }); + + const result = sortParentFirst([node1, node2]); + + expect(result.map(n => n.id)).toEqual(['n1', 'n2']); + }); + + it('groups sibling nodes under their shared parent', () => { + const root = createMockNode({ id: 'root', parent: '', isRoot: true }); + const b = createMockNode({ id: 'b', parent: 'root', isRoot: false }); + const a = createMockNode({ id: 'a', parent: 'root', isRoot: false }); + const bChild = createMockNode({ + id: 'b-child', + parent: 'b', + isRoot: false, + }); + + const result = sortParentFirst([bChild, a, b, root]); + + expect(result[0].id).toBe('root'); + expect(result.indexOf(b)).toBeLessThan(result.indexOf(bChild)); + }); + + it('returns empty array for empty input', () => { + const result = sortParentFirst([]); + + expect(result).toEqual([]); + }); + + it('appends orphaned nodes not reachable from root', () => { + const root = createMockNode({ id: 'root', parent: '', isRoot: true }); + const child = createMockNode({ + id: 'child', + parent: 'root', + isRoot: false, + }); + const orphan = createMockNode({ + id: 'orphan', + parent: 'deleted-parent', + isRoot: false, + }); + + const result = sortParentFirst([orphan, child, root]); + + expect(result.map(n => n.id)).toEqual(['root', 'child', 'orphan']); + }); +}); 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 8ab201d5e..8e55ef6b9 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 @@ -1,6 +1,6 @@ import { Injectable, OnDestroy, inject } from '@angular/core'; import { MmpService } from '../mmp/mmp.service'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { CachedAdminMapEntry, CachedAdminMapValue, @@ -39,19 +39,28 @@ import { UtilsService } from '../utils/utils.service'; import { StorageService } from '../storage/storage.service'; import { SettingsService } from '../settings/settings.service'; import { ToastrService } from 'ngx-toastr'; -import { MapDiff } from '@mmp/map/handlers/history'; +import { MapDiff, SnapshotChanges } from '@mmp/map/handlers/history'; import { ToastService } from '../toast/toast.service'; import { DialogService } from '../dialog/dialog.service'; +import * as Y from 'yjs'; +import { WebsocketProvider } from 'y-websocket'; +import { + ClientColorMapping, + ClientColorMappingValue, + populateYMapFromNodeProps, + yMapToNodeProps, + buildYjsWsUrl, + parseWriteAccessBytes, + resolveClientColor, + findAffectedNodes, + resolveMmpPropertyUpdate, + sortParentFirst, +} from './yjs-utils'; const DEFAULT_COLOR = '#000000'; const DEFAULT_SELF_COLOR = '#c0c0c0'; - -type ClientColorMapping = Record; - -interface ClientColorMappingValue { - nodeId: string; - color: string; -} +const WS_CLOSE_MAP_DELETED = 4001; +const MESSAGE_WRITE_ACCESS = 4; type ServerClientList = Record; @@ -79,11 +88,29 @@ export class MapSyncService implements OnDestroy { // inform other parts of the app about the connection state private readonly connectionStatusSubject: BehaviorSubject; + // Socket.io fields (used when yjs feature flag is false) private socket: Socket; + + // Yjs fields (used when yjs feature flag is true) + private yDoc: Y.Doc | null = null; + private wsProvider: WebsocketProvider | null = null; + private yjsSynced = false; + private yjsWritable = false; + private yjsSubscriptions: Subscription[] = []; + private yjsMapId: string | null = null; + private yjsNodesObserver: + | Parameters['observeDeep']>[0] + | null = null; + private yjsOptionsObserver: Parameters['observe']>[0] | null = + null; + private yjsAwarenessHandler: (() => void) | null = null; + + // Common fields private colorMapping: ClientColorMapping; private availableColors: string[]; private clientColor: string; private modificationSecret: string; + private readonly yjsEnabled: boolean; constructor() { // Initialization of the behavior subjects. @@ -92,8 +119,8 @@ export class MapSyncService implements OnDestroy { null ); this.connectionStatusSubject = new BehaviorSubject(null); - this.clientListSubject = new BehaviorSubject([]); + this.availableColors = COLORS; this.clientColor = this.availableColors[ @@ -101,32 +128,25 @@ export class MapSyncService implements OnDestroy { ]; this.modificationSecret = ''; this.colorMapping = {}; + this.yjsEnabled = + this.settingsService.getCachedSystemSettings()?.featureFlags?.yjs ?? + false; - 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, - }); + if (!this.yjsEnabled) { + this.initSocketConnection(); + } } ngOnDestroy() { - this.reset(); + if (this.yjsEnabled) { + this.resetYjs(); + } else { + this.resetSocketIo(); + } } + // ─── Public API ────────────────────────────────────────────── + /** * Creates a new map on server and prepares it locally * Stores admin data and enables edit mode @@ -138,29 +158,6 @@ export class MapSyncService implements OnDestroy { return privateServerMap; } - /** - * Store private map data locally for admin access - */ - private storePrivateMapData(privateServerMap: PrivateServerMap): void { - const serverMap = privateServerMap.map; - this.storageService.set(serverMap.uuid, { - adminId: privateServerMap.adminId, - modificationSecret: privateServerMap.modificationSecret, - ttl: serverMap.deletedAt, - rootName: serverMap.data[0].name, - createdAt: serverMap.createdAt, - }); - } - - /** - * Setup state for newly created map - */ - private setupNewMapState(privateServerMap: PrivateServerMap): void { - this.prepareMap(privateServerMap.map); - this.settingsService.setEditMode(true); - this.modificationSecret = privateServerMap.modificationSecret; - } - public async prepareExistingMap( id: string, modificationSecret: string @@ -174,17 +171,16 @@ export class MapSyncService implements OnDestroy { this.updateCachedMapForAdmin(serverMap); this.prepareMap(serverMap); - return serverMap; } - // In case the component is destroyed or will be reinitialized it is important to reset state - // that might cause problems or performance issues, e.g. removing listeners, cleanup state. - // The current map is used inside the settings component and should stay therefore as it was. + // Detach MMP listeners but keep the Yjs connection alive for reuse public reset() { - if (this.socket) { - this.socket.removeAllListeners(); - this.leaveMap(); + if (this.yjsEnabled) { + this.detachYjsObservers(); + this.unsubscribeYjsListeners(); + } else { + this.resetSocketIo(); } this.colorMapping = {}; } @@ -195,8 +191,12 @@ export class MapSyncService implements OnDestroy { this.mmpService.selectNode(this.mmpService.getRootNode().id) ); - this.createMapListeners(); - this.listenServerEvents(this.getAttachedMap().cachedMap.uuid); + if (this.yjsEnabled) { + this.initYjs(); + } else { + this.createSocketIoListeners(); + this.listenServerEvents(this.getAttachedMap().cachedMap.uuid); + } } public attachMap(cachedMapEntry: CachedMapEntry): void { @@ -227,10 +227,6 @@ export class MapSyncService implements OnDestroy { return this.connectionStatusSubject.getValue(); } - private setConnectionStatusSubject(value: ConnectionStatus) { - this.connectionStatusSubject.next(value); - } - // update the attached map from outside control flow public async updateAttachedMap(): Promise { const cachedMapEntry: CachedMapEntry = this.getAttachedMap(); @@ -248,32 +244,182 @@ export class MapSyncService implements OnDestroy { this.attachMap({ key: cachedMapEntry.key, cachedMap }); } - public async joinMap(mmpUuid: string, color: string): Promise { - return await new Promise( - ( - resolve: (value: MapProperties) => void, - reject: (reason: string) => void - ) => { - this.socket.emit( - 'join', - { mapId: mmpUuid, color }, - (serverMap: MapProperties) => { - if (!serverMap) { - reject('Server Map not available'); - return; - } - resolve(serverMap); + public updateMapOptions(options?: CachedMapOptions) { + if (this.yjsEnabled) { + this.writeMapOptionsToYDoc(options); + } else { + this.emitUpdateMapOptions(options); + } + } + + public async deleteMap(adminId: string): Promise { + if (this.yjsEnabled) { + await this.deleteMapViaHttp(adminId); + } else { + this.deleteMapViaSocket(adminId); + } + } + + public async fetchUserMapsFromServer(): Promise { + const response = await this.httpService.get(API_URL.ROOT, '/maps'); + if (!response.ok) return []; + const json: ServerMapInfo[] = await response.json(); + return json.map(map => ({ + id: map.uuid, + cachedAdminMapValue: { + adminId: map.adminId, + modificationSecret: map.modificationSecret, + ttl: map.ttl ? new Date(map.ttl) : new Date(), + rootName: map.rootName, + createdAt: new Date(map.createdAt), + }, + })); + } + + // ─── Socket.io initialization ──────────────────────────────── + + private initSocketConnection(): 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 resetSocketIo(): void { + if (this.socket) { + this.socket.removeAllListeners(); + this.leaveMap(); + } + } + + // ─── Socket.io map listeners (MMP → Socket.io) ────────────── + + private createSocketIoListeners() { + this.setupSocketIoCreateHandler(); + this.setupSocketIoSelectionHandlers(); + this.setupSocketIoNodeUpdateHandler(); + this.setupSocketIoUndoRedoHandlers(); + this.setupSocketIoNodeCreateHandler(); + this.setupSocketIoPasteHandler(); + this.setupSocketIoNodeRemoveHandler(); + } + + private setupSocketIoCreateHandler(): void { + this.mmpService.on('create').subscribe((_result: MapCreateEvent) => { + this.attachedNodeSubject.next(this.mmpService.selectNode()); + this.updateAttachedMap(); + this.updateMap(); + }); + } + + private setupSocketIoSelectionHandlers(): void { + this.mmpService + .on('nodeSelect') + .subscribe((nodeProps: ExportNodeProperties) => { + this.updateNodeSelectionSocketIo(nodeProps.id, true); + this.attachedNodeSubject.next(nodeProps); + }); + + this.mmpService + .on('nodeDeselect') + .subscribe((nodeProps: ExportNodeProperties) => { + this.updateNodeSelectionSocketIo(nodeProps.id, false); + this.attachedNodeSubject.next(nodeProps); + }); + } + + private setupSocketIoNodeUpdateHandler(): void { + this.mmpService.on('nodeUpdate').subscribe((result: NodeUpdateEvent) => { + this.attachedNodeSubject.next(result.nodeProperties); + this.emitUpdateNode(result); + this.updateAttachedMap(); + }); + } + + private setupSocketIoUndoRedoHandlers(): void { + this.mmpService.on('undo').subscribe((diff?: MapDiff) => { + this.attachedNodeSubject.next(this.mmpService.selectNode()); + this.updateAttachedMap(); + this.emitApplyMapChangesByDiff(diff, 'undo'); + }); + + this.mmpService.on('redo').subscribe((diff?: MapDiff) => { + this.attachedNodeSubject.next(this.mmpService.selectNode()); + this.updateAttachedMap(); + this.emitApplyMapChangesByDiff(diff, 'redo'); + }); + } + + private setupSocketIoNodeCreateHandler(): void { + this.mmpService + .on('nodeCreate') + .subscribe((newNode: ExportNodeProperties) => { + this.emitAddNode(newNode); + this.updateAttachedMap(); + this.mmpService.selectNode(newNode.id); + this.mmpService.editNode(); + }); + } + + private setupSocketIoPasteHandler(): void { + this.mmpService + .on('nodePaste') + .subscribe((newNodes: ExportNodeProperties[]) => { + this.emitAddNodes(newNodes); + this.updateAttachedMap(); + }); + } + + private setupSocketIoNodeRemoveHandler(): void { + this.mmpService + .on('nodeRemove') + .subscribe((removedNode: ExportNodeProperties) => { + this.emitRemoveNode(removedNode); + this.updateAttachedMap(); + }); + } + + // ─── Socket.io emit methods ────────────────────────────────── + + private async 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); + } + ); + }); } - public leaveMap() { + private leaveMap() { this.socket.emit('leave'); } - public addNode(newNode: ExportNodeProperties) { + private emitAddNode(newNode: ExportNodeProperties) { // Emit with acknowledgment callback for error handling this.socket.emit( 'addNodes', @@ -292,7 +438,7 @@ export class MapSyncService implements OnDestroy { * Add multiple nodes with server validation and error handling * Used for bulk operations like paste */ - public addNodes(newNodes: ExportNodeProperties[]) { + private emitAddNodes(newNodes: ExportNodeProperties[]) { // Emit with acknowledgment callback for error handling this.socket.emit( 'addNodes', @@ -310,7 +456,7 @@ export class MapSyncService implements OnDestroy { /** * Update node property with server validation and error handling */ - public updateNode(nodeUpdate: NodeUpdateEvent) { + private emitUpdateNode(nodeUpdate: NodeUpdateEvent) { // Emit with acknowledgment callback for error handling this.socket.emit( 'updateNode', @@ -329,7 +475,7 @@ export class MapSyncService implements OnDestroy { /** * Remove node with server validation and error handling */ - public removeNode(removedNode: ExportNodeProperties) { + private emitRemoveNode(removedNode: ExportNodeProperties) { // Emit with acknowledgment callback for error handling this.socket.emit( 'removeNode', @@ -347,14 +493,15 @@ export class MapSyncService implements OnDestroy { /** * Apply undo/redo changes with server validation and error handling */ - public applyMapChangesByDiff(diff: MapDiff, operationType: 'undo' | 'redo') { - const cachedMapEntry: CachedMapEntry = this.getAttachedMap(); - + private emitApplyMapChangesByDiff( + diff: MapDiff, + operationType: 'undo' | 'redo' + ) { // Emit with acknowledgment callback for error handling this.socket.emit( 'applyMapChangesByDiff', { - mapId: cachedMapEntry.cachedMap.uuid, + mapId: this.getAttachedMap().cachedMap.uuid, diff, modificationSecret: this.modificationSecret, }, @@ -367,7 +514,7 @@ export class MapSyncService implements OnDestroy { ); } - public updateMap() { + private updateMap() { const cachedMapEntry: CachedMapEntry = this.getAttachedMap(); this.socket.emit('updateMap', { mapId: cachedMapEntry.cachedMap.uuid, @@ -376,7 +523,7 @@ export class MapSyncService implements OnDestroy { }); } - public updateMapOptions(options?: CachedMapOptions) { + private emitUpdateMapOptions(options?: CachedMapOptions) { const cachedMapEntry: CachedMapEntry = this.getAttachedMap(); this.socket.emit('updateMapOptions', { mapId: cachedMapEntry.cachedMap.uuid, @@ -385,17 +532,16 @@ export class MapSyncService implements OnDestroy { }); } - public async deleteMap(adminId: string): Promise { + private deleteMapViaSocket(adminId: string): void { const cachedMapEntry: CachedMapEntry = this.getAttachedMap(); - const body: { adminId: string; mapId: string } = { + this.socket.emit('deleteMap', { adminId, mapId: cachedMapEntry.cachedMap.uuid, - }; - this.socket.emit('deleteMap', body); + }); } - public async updateNodeSelection(id: string, selected: boolean) { - // Remember all clients selections with the dedicated colors to switch between colors when clients change among nodes + // Remember all clients selections with the dedicated colors to switch between colors when clients change among nodes + private updateNodeSelectionSocketIo(id: string, selected: boolean) { if (selected) { this.colorMapping[this.socket.id] = { color: DEFAULT_SELF_COLOR, @@ -429,60 +575,7 @@ export class MapSyncService implements OnDestroy { ); } - private async fetchMapFromServer(id: string): Promise { - const response = await this.httpService.get(API_URL.ROOT, '/maps/' + id); - if (!response.ok) return null; - const json: ServerMap = await response.json(); - return json; - } - - public async fetchUserMapsFromServer(): Promise { - const response = await this.httpService.get(API_URL.ROOT, '/maps'); - if (!response.ok) return []; - const json: ServerMapInfo[] = await response.json(); - const mapEntries: CachedAdminMapEntry[] = json.map(map => ({ - id: map.uuid, - cachedAdminMapValue: { - createdAt: map.createdAt ? new Date(map.createdAt) : null, - adminId: map.adminId, - modificationSecret: map.modificationSecret, - ttl: map.ttl ? new Date(map.ttl) : new Date(), - rootName: map.rootName, - }, - })); - return mapEntries; - } - - private async postMapToServer(): Promise { - const response = await this.httpService.post( - API_URL.ROOT, - '/maps/', - JSON.stringify({ - rootNode: - this.settingsService.getCachedUserSettings().mapOptions.rootNode, - }) - ); - - return response.json(); - } - - /** - * Return the key of the map in the storage - */ - private createKey(uuid: string): string { - return `map-${uuid}`; - } - - /** - * Converts server map - */ - private convertServerMapToMmp(serverMap: ServerMap): MapProperties { - return Object.assign({}, serverMap, { - lastModified: Date.parse(serverMap.lastModified), - deletedAt: Date.parse(serverMap.deletedAt), - createdAt: Date.parse(serverMap.createdAt), - }); - } + // ─── Socket.io server event handlers ───────────────────────── /** * Setup all server event listeners and join the map @@ -539,7 +632,7 @@ export class MapSyncService implements OnDestroy { const msg = await this.utilsService.translate(notification.message); if (!msg) return; - const toastHandlers = { + const toastHandlers: Record void> = { error: () => this.toastrService.error(msg), success: () => this.toastrService.success(msg), warning: () => this.toastrService.warning(msg), @@ -601,9 +694,8 @@ export class MapSyncService implements OnDestroy { private setupNodeRemovedHandler(): void { this.socket.on('nodeRemoved', (result: ResponseNodeRemoved) => { if (result.clientId === this.socket.id) return; - const removedNodeId = result.nodeId; - if (this.mmpService.existNode(removedNodeId)) { - this.mmpService.removeNode(removedNodeId, false); + if (this.mmpService.existNode(result.nodeId)) { + this.mmpService.removeNode(result.nodeId, false); } }); } @@ -643,35 +735,29 @@ export class MapSyncService implements OnDestroy { * Apply map diff for undo/redo operations */ private applyMapDiff(diff: MapDiff): void { - const { added, updated, deleted } = diff; - - this.applyAddedNodes(added); - this.applyUpdatedNodes(updated); - this.applyDeletedNodes(deleted); + this.applyAddedNodes(diff.added); + this.applyUpdatedNodes(diff.updated); + this.applyDeletedNodes(diff.deleted); } /** * Apply added nodes from diff */ - private applyAddedNodes(added: unknown): void { - if (added && typeof added === 'object') { - for (const nodeId in added) { - const node = added[nodeId]; - this.mmpService.addNode(node, false); - } + private applyAddedNodes(added: SnapshotChanges): void { + if (!added) return; + for (const nodeId in added) { + this.mmpService.addNode(added[nodeId], false); } } /** * Apply updated nodes from diff */ - private applyUpdatedNodes(updated: unknown): void { - if (updated && typeof updated === 'object') { - for (const nodeId in updated) { - const node = updated[nodeId]; - if (this.mmpService.existNode(nodeId)) { - this.applyNodePropertyUpdates(nodeId, node); - } + private applyUpdatedNodes(updated: SnapshotChanges): void { + if (!updated) return; + for (const nodeId in updated) { + if (this.mmpService.existNode(nodeId)) { + this.applyNodePropertyUpdates(nodeId, updated[nodeId]); } } } @@ -679,10 +765,13 @@ export class MapSyncService implements OnDestroy { /** * Apply property updates to a single node */ - private applyNodePropertyUpdates(nodeId: string, nodeUpdates: unknown): void { - if (typeof nodeUpdates !== 'object' || !nodeUpdates) return; + private applyNodePropertyUpdates( + nodeId: string, + nodeUpdates: Partial + ): void { + if (!nodeUpdates) return; - for (const property in nodeUpdates as Record) { + for (const property in nodeUpdates) { const updatedProperty = this.getClientProperty( property, (nodeUpdates as Record)[property] @@ -717,11 +806,10 @@ export class MapSyncService implements OnDestroy { if (mapping && typeof value === 'object') { const subProperty = Object.keys(value)[0]; - const nestedMapping = mapping[subProperty]; - return { - clientProperty: nestedMapping, - directValue: value[subProperty], - }; + const nestedMapping = mapping[ + subProperty as keyof typeof mapping + ] as string; + return { clientProperty: nestedMapping, directValue: value[subProperty] }; } return; @@ -730,12 +818,11 @@ export class MapSyncService implements OnDestroy { /** * Apply deleted nodes from diff */ - private applyDeletedNodes(deleted: unknown): void { - if (deleted && typeof deleted === 'object') { - for (const nodeId in deleted) { - if (this.mmpService.existNode(nodeId)) { - this.mmpService.removeNode(nodeId, false); - } + private applyDeletedNodes(deleted: SnapshotChanges): void { + if (!deleted) return; + for (const nodeId in deleted) { + if (this.mmpService.existNode(nodeId)) { + this.mmpService.removeNode(nodeId, false); } } } @@ -860,47 +947,7 @@ export class MapSyncService implements OnDestroy { }); } - private colorForNode(nodeId: string): string { - const matchingClient = this.clientForNode(nodeId); - return matchingClient ? this.colorMapping[matchingClient].color : ''; - } - - private clientForNode(nodeId: string): string { - return Object.keys(this.colorMapping) - .filter((key: string) => { - return this.colorMapping[key]?.nodeId === nodeId; - }) - .shift(); - } - - private extractClientListForSubscriber(): void { - this.clientListSubject.next( - Object.values(this.colorMapping).map( - (e: ClientColorMappingValue) => e?.color - ) - ); - } - - private prepareMap(serverMap: ServerMap) { - const mapKey = this.createKey(serverMap.uuid); - const mapProps = this.convertServerMapToMmp(serverMap); - this.attachMap({ - key: mapKey, - cachedMap: { ...mapProps, ...{ options: serverMap.options } }, - }); - this.mmpService.updateAdditionalMapOptions(serverMap.options); - } - - private async updateCachedMapForAdmin(serverMap: ServerMap) { - const map: CachedAdminMapValue | null = (await this.storageService.get( - serverMap.uuid - )) as CachedAdminMapValue | null; - if (map) { - map.ttl = new Date(serverMap.deletedAt); - map.rootName = serverMap.data?.[0]?.name; - this.storageService.set(serverMap.uuid, map); - } - } + // ─── Socket.io error handling ──────────────────────────────── /** * Validate ServerMap structure at runtime @@ -969,51 +1016,65 @@ export class MapSyncService implements OnDestroy { // Validate error response structure before processing if (!this.isValidErrorResponse(response)) { - let malformedResponseMessage: string; - try { - malformedResponseMessage = await this.utilsService.translate( - 'TOASTS.ERRORS.MALFORMED_RESPONSE' - ); - } catch { - malformedResponseMessage = 'Invalid server response. Please try again.'; - } - this.dialogService.openCriticalErrorDialog({ - code: 'MALFORMED_RESPONSE', - message: malformedResponseMessage, - }); + await this.showMalformedResponseError(); return; } // Error occurred - reload from fullMapState if available if (response.fullMapState) { - // Reload entire map from server's authoritative state - this.mmpService.new(response.fullMapState.data, false); - - // Show appropriate error notification - let operationFailedMessage: string; - try { - operationFailedMessage = await this.utilsService.translate( - 'TOASTS.ERRORS.OPERATION_FAILED_MAP_RELOADED' - ); - } catch { - operationFailedMessage = 'Operation failed - map reloaded'; - } - this.toastService.showValidationCorrection( - `${operationName}`, - operationFailedMessage - ); + await this.handleRecoverableError(response, operationName); } else { // No fullMapState provided - show critical error - const userMessage = await this.getUserFriendlyErrorMessage( - response.code || 'SERVER_ERROR', - response.message || 'Unknown error' + 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, + }); + } - this.dialogService.openCriticalErrorDialog({ - code: response.code || 'SERVER_ERROR', - message: userMessage, - }); + private async handleRecoverableError( + response: ValidationErrorResponse | CriticalErrorResponse, + operationName: string + ): Promise { + // Reload entire map from server's authoritative state + 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'; } + // Show appropriate error notification + 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, + }); } /** @@ -1041,73 +1102,766 @@ export class MapSyncService implements OnDestroy { } } - private createMapListeners() { - // create is NOT called by the mmp lib for initial map load / and call, but for _imported_ maps - this.mmpService.on('create').subscribe((_result: MapCreateEvent) => { - this.attachedNodeSubject.next(this.mmpService.selectNode()); + // ─── Yjs initialization ────────────────────────────────────── - this.updateAttachedMap(); - this.updateMap(); + private initYjs(): void { + const uuid = this.getAttachedMap().cachedMap.uuid; + + if (this.hasActiveYjsConnection(uuid)) { + this.reattachYjsListeners(); + return; + } + + this.resetYjs(); + this.yjsMapId = uuid; + this.yDoc = new Y.Doc(); + this.setupYjsConnection(uuid); + this.setupYjsConnectionStatus(); + this.setupYjsMapDeletionHandler(); + this.createYjsListeners(); + } + + private hasActiveYjsConnection(mapId: string): boolean { + return ( + this.yDoc !== null && this.wsProvider !== null && this.yjsMapId === mapId + ); + } + + private reattachYjsListeners(): void { + this.createYjsListeners(); + if (this.yjsSynced) { + this.loadMapFromYDoc(); + this.setupYjsNodesObserver(); + this.setupYjsMapOptionsObserver(); + this.setupYjsAwareness(); + this.settingsService.setEditMode(this.yjsWritable); + this.setConnectionStatusSubject('connected'); + } + } + + private setupYjsConnection(mapId: string): void { + const wsUrl = this.buildYjsWsUrl(); + this.wsProvider = new WebsocketProvider(wsUrl, mapId, this.yDoc, { + params: { secret: this.modificationSecret }, + maxBackoffTime: 5000, + disableBc: true, }); - this.mmpService - .on('nodeSelect') - .subscribe((nodeProps: ExportNodeProperties) => { - this.updateNodeSelection(nodeProps.id, true); - this.attachedNodeSubject.next(nodeProps); - }); + this.setupYjsWriteAccessListener(); - this.mmpService - .on('nodeDeselect') - .subscribe((nodeProps: ExportNodeProperties) => { - this.updateNodeSelection(nodeProps.id, false); - this.attachedNodeSubject.next(nodeProps); - }); + this.wsProvider.on('sync', (synced: boolean) => { + if (synced && !this.yjsSynced) { + this.handleFirstYjsSync(); + } + }); + } - this.mmpService.on('nodeUpdate').subscribe((result: NodeUpdateEvent) => { - this.attachedNodeSubject.next(result.nodeProperties); - this.updateNode(result); - this.updateAttachedMap(); + private buildYjsWsUrl(): string { + return buildYjsWsUrl(); + } + + private handleFirstYjsSync(): void { + this.yjsSynced = true; + this.loadMapFromYDoc(); + this.setupYjsNodesObserver(); + this.setupYjsMapOptionsObserver(); + this.setupYjsAwareness(); + this.settingsService.setEditMode(this.yjsWritable); + this.setConnectionStatusSubject('connected'); + } + + private setupYjsConnectionStatus(): void { + this.wsProvider.on( + 'status', + (event: { status: 'connected' | 'disconnected' | 'connecting' }) => { + if (!this.wsProvider) return; + if (event.status === 'connected') { + this.setConnectionStatusSubject('connected'); + } else if (event.status === 'disconnected') { + this.setConnectionStatusSubject('disconnected'); + } + } + ); + } + + private setupYjsMapDeletionHandler(): void { + this.wsProvider.on('connection-close', (event: CloseEvent | null) => { + if (event?.code === WS_CLOSE_MAP_DELETED) { + window.location.reload(); + } }); + } - this.mmpService.on('undo').subscribe((diff?: MapDiff) => { - this.attachedNodeSubject.next(this.mmpService.selectNode()); - // Updating the attached map is important because this persists changes after refresh - this.updateAttachedMap(); - this.applyMapChangesByDiff(diff, 'undo'); + // Listen for the server's write-access message (type 4) to set edit mode + private setupYjsWriteAccessListener(): void { + // Prevent "Unable to compute message" warning in y-websocket + this.wsProvider.messageHandlers[MESSAGE_WRITE_ACCESS] = () => { + // no-op: handled via raw WebSocket listener below + }; + + this.wsProvider.on( + 'status', + (event: { status: 'connected' | 'disconnected' | 'connecting' }) => { + if (event.status !== 'connected') return; + this.attachWriteAccessListener(); + } + ); + } + + private attachWriteAccessListener(): void { + const ws = this.wsProvider?.ws; + if (!ws) return; + ws.addEventListener('message', (event: MessageEvent) => { + this.parseWriteAccessMessage(event); }); + } - this.mmpService.on('redo').subscribe((diff?: MapDiff) => { - this.attachedNodeSubject.next(this.mmpService.selectNode()); - // Updating the attached map is important because this persists changes after refresh - this.updateAttachedMap(); - this.applyMapChangesByDiff(diff, 'redo'); + private parseWriteAccessMessage(event: MessageEvent): void { + const data = new Uint8Array(event.data as ArrayBuffer); + const result = parseWriteAccessBytes(data); + if (result !== null) { + this.yjsWritable = result; + this.settingsService.setEditMode(this.yjsWritable); + } + } + + private resetYjs(): void { + this.unsubscribeYjsListeners(); + this.detachYjsObservers(); + const provider = this.wsProvider; + this.wsProvider = null; + if (provider) { + provider.disconnect(); + provider.destroy(); + } + if (this.yDoc) { + this.yDoc.destroy(); + this.yDoc = null; + } + this.yjsSynced = false; + this.yjsWritable = false; + this.yjsMapId = null; + } + + // ─── Yjs: initial map load ─────────────────────────────────── + + private loadMapFromYDoc(): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + const snapshot = this.extractSnapshotFromYDoc(nodesMap); + if (snapshot.length > 0) { + this.mmpService.new(snapshot, false); + } + } + + private extractSnapshotFromYDoc( + nodesMap: Y.Map> + ): ExportNodeProperties[] { + const nodes: ExportNodeProperties[] = []; + nodesMap.forEach((yNode: Y.Map) => { + nodes.push(this.yMapToNodeProps(yNode)); }); + return sortParentFirst(nodes); + } - this.mmpService - .on('nodeCreate') - .subscribe((newNode: ExportNodeProperties) => { - // Send node creation to server for validation - this.addNode(newNode); + // ─── Yjs: MMP event listeners (MMP → Y.Doc) ───────────────── + + private createYjsListeners(): void { + this.unsubscribeYjsListeners(); + this.setupYjsCreateHandler(); + this.setupYjsSelectionHandlers(); + this.setupYjsNodeUpdateHandler(); + this.setupYjsUndoRedoHandlers(); + this.setupYjsNodeCreateHandler(); + this.setupYjsPasteHandler(); + this.setupYjsNodeRemoveHandler(); + } + + private unsubscribeYjsListeners(): void { + this.yjsSubscriptions.forEach(sub => sub.unsubscribe()); + this.yjsSubscriptions = []; + } + + // Handles map import: clear and repopulate Y.Doc nodes + private setupYjsCreateHandler(): void { + this.yjsSubscriptions.push( + this.mmpService.on('create').subscribe((_result: MapCreateEvent) => { + this.attachedNodeSubject.next(this.mmpService.selectNode()); this.updateAttachedMap(); - this.mmpService.selectNode(newNode.id); - this.mmpService.editNode(); - }); + if (this.yjsSynced) { + this.writeImportToYDoc(); + } + }) + ); + } - this.mmpService - .on('nodePaste') - .subscribe((newNodes: ExportNodeProperties[]) => { - // Send bulk paste operations to server for validation - this.addNodes(newNodes); + private setupYjsSelectionHandlers(): void { + this.yjsSubscriptions.push( + this.mmpService + .on('nodeSelect') + .subscribe((nodeProps: ExportNodeProperties) => { + if (!this.yDoc) return; + this.updateYjsAwarenessSelection(nodeProps.id); + this.attachedNodeSubject.next(nodeProps); + }) + ); + + this.yjsSubscriptions.push( + this.mmpService + .on('nodeDeselect') + .subscribe((nodeProps: ExportNodeProperties) => { + if (!this.yDoc) return; + this.updateYjsAwarenessSelection(null); + this.attachedNodeSubject.next(nodeProps); + }) + ); + } + + private setupYjsNodeUpdateHandler(): void { + this.yjsSubscriptions.push( + this.mmpService.on('nodeUpdate').subscribe((result: NodeUpdateEvent) => { + if (!this.yDoc) return; + this.attachedNodeSubject.next(result.nodeProperties); + this.writeNodeUpdateToYDoc(result); this.updateAttachedMap(); - }); + }) + ); + } - this.mmpService - .on('nodeRemove') - .subscribe((removedNode: ExportNodeProperties) => { - // Send node removal to server for validation - this.removeNode(removedNode); + private setupYjsUndoRedoHandlers(): void { + this.yjsSubscriptions.push( + this.mmpService.on('undo').subscribe((diff?: MapDiff) => { + if (!this.yDoc) return; + this.attachedNodeSubject.next(this.mmpService.selectNode()); this.updateAttachedMap(); - }); + this.writeUndoRedoDiffToYDoc(diff); + }) + ); + + this.yjsSubscriptions.push( + this.mmpService.on('redo').subscribe((diff?: MapDiff) => { + if (!this.yDoc) return; + this.attachedNodeSubject.next(this.mmpService.selectNode()); + this.updateAttachedMap(); + this.writeUndoRedoDiffToYDoc(diff); + }) + ); + } + + private setupYjsNodeCreateHandler(): void { + this.yjsSubscriptions.push( + this.mmpService + .on('nodeCreate') + .subscribe((newNode: ExportNodeProperties) => { + if (!this.yDoc) return; + this.writeNodeCreateToYDoc(newNode); + this.updateAttachedMap(); + this.mmpService.selectNode(newNode.id); + this.mmpService.editNode(); + }) + ); + } + + private setupYjsPasteHandler(): void { + this.yjsSubscriptions.push( + this.mmpService + .on('nodePaste') + .subscribe((newNodes: ExportNodeProperties[]) => { + if (!this.yDoc) return; + this.writeNodesPasteToYDoc(newNodes); + this.updateAttachedMap(); + }) + ); + } + + private setupYjsNodeRemoveHandler(): void { + this.yjsSubscriptions.push( + this.mmpService + .on('nodeRemove') + .subscribe((removedNode: ExportNodeProperties) => { + if (!this.yDoc) return; + this.writeNodeRemoveFromYDoc(removedNode.id); + this.updateAttachedMap(); + }) + ); + } + + // ─── Yjs: write operations (MMP → Y.Doc) ──────────────────── + + private writeNodeCreateToYDoc(nodeProps: ExportNodeProperties): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + const yNode = new Y.Map(); + this.populateYMapFromNodeProps(yNode, nodeProps); + nodesMap.set(nodeProps.id, yNode); + } + + private writeNodeUpdateToYDoc(event: NodeUpdateEvent): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + const yNode = nodesMap.get(event.nodeProperties.id); + if (!yNode) return; + + const topLevelKey = NodePropertyMapping[event.changedProperty][0]; + const value = + event.nodeProperties[topLevelKey as keyof ExportNodeProperties]; + yNode.set(topLevelKey, value); + } + + private writeNodeRemoveFromYDoc(nodeId: string): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + nodesMap.delete(nodeId); + } + + private writeNodesPasteToYDoc(nodes: ExportNodeProperties[]): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + this.yDoc.transact(() => { + for (const node of nodes) { + const yNode = new Y.Map(); + this.populateYMapFromNodeProps(yNode, node); + nodesMap.set(node.id, yNode); + } + }); + } + + private writeImportToYDoc(): void { + const snapshot = this.mmpService.exportAsJSON(); + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + const sorted = sortParentFirst(snapshot); + + this.yDoc.transact(() => { + this.clearAndRepopulateNodes(nodesMap, sorted); + }); + } + + private clearAndRepopulateNodes( + nodesMap: Y.Map>, + snapshot: ExportNodeProperties[] + ): void { + for (const key of Array.from(nodesMap.keys())) { + nodesMap.delete(key); + } + for (const node of snapshot) { + const yNode = new Y.Map(); + this.populateYMapFromNodeProps(yNode, node); + nodesMap.set(node.id, yNode); + } + } + + private writeUndoRedoDiffToYDoc(diff: MapDiff): void { + if (!diff) return; + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + + this.yDoc.transact(() => { + this.writeAddedNodesToYDoc(nodesMap, diff.added); + this.writeUpdatedNodesToYDoc(nodesMap, diff.updated); + this.writeDeletedNodesFromYDoc(nodesMap, diff.deleted); + }); + } + + private writeAddedNodesToYDoc( + nodesMap: Y.Map>, + added: SnapshotChanges + ): void { + if (!added) return; + for (const nodeId in added) { + const nodeProps = added[nodeId] as ExportNodeProperties; + const yNode = new Y.Map(); + this.populateYMapFromNodeProps(yNode, nodeProps); + nodesMap.set(nodeId, yNode); + } + } + + private writeUpdatedNodesToYDoc( + nodesMap: Y.Map>, + updated: SnapshotChanges + ): void { + if (!updated) return; + for (const nodeId in updated) { + const yNode = nodesMap.get(nodeId); + if (!yNode) continue; + this.applyPropertyUpdatesToYMap(yNode, updated[nodeId]); + } + } + + private applyPropertyUpdatesToYMap( + yNode: Y.Map, + updates: Partial + ): void { + if (!updates) return; + for (const key in updates) { + yNode.set(key, (updates as Record)[key]); + } + } + + private writeDeletedNodesFromYDoc( + nodesMap: Y.Map>, + deleted: SnapshotChanges + ): void { + if (!deleted) return; + for (const nodeId in deleted) { + nodesMap.delete(nodeId); + } + } + + private writeMapOptionsToYDoc(options?: CachedMapOptions): void { + if (!this.yDoc || !options) return; + const optionsMap = this.yDoc.getMap('mapOptions'); + optionsMap.set('fontMaxSize', options.fontMaxSize); + optionsMap.set('fontMinSize', options.fontMinSize); + optionsMap.set('fontIncrement', options.fontIncrement); + } + + private async deleteMapViaHttp(adminId: string): Promise { + const mapId = this.getAttachedMap().cachedMap.uuid; + await this.httpService.delete( + API_URL.ROOT, + `/maps/${mapId}`, + JSON.stringify({ adminId }) + ); + } + + // ─── Yjs: Y.Doc observers (Y.Doc → MMP) ───────────────────── + + private detachYjsObservers(): void { + if (this.yDoc && this.yjsNodesObserver) { + const nodesMap = this.yDoc.getMap('nodes'); + nodesMap.unobserveDeep(this.yjsNodesObserver); + this.yjsNodesObserver = null; + } + if (this.yDoc && this.yjsOptionsObserver) { + const optionsMap = this.yDoc.getMap('mapOptions'); + optionsMap.unobserve(this.yjsOptionsObserver); + this.yjsOptionsObserver = null; + } + if (this.wsProvider && this.yjsAwarenessHandler) { + this.wsProvider.awareness.off('change', this.yjsAwarenessHandler); + this.yjsAwarenessHandler = null; + } + } + + private setupYjsNodesObserver(): void { + const nodesMap = this.yDoc.getMap('nodes') as Y.Map>; + this.yjsNodesObserver = ( + events: Y.YEvent>>>[], + transaction: Y.Transaction + ) => { + if (transaction.local) return; + for (const event of events) { + this.handleYjsNodeEvent(event, nodesMap); + } + }; + nodesMap.observeDeep(this.yjsNodesObserver); + } + + private handleYjsNodeEvent( + event: Y.YEvent>>>, + nodesMap: Y.Map> + ): void { + if (event.target === nodesMap) { + this.handleTopLevelNodeChanges(event, nodesMap); + } else { + this.handleNodePropertyChanges(event); + } + } + + // Handles node add/delete/update from the top-level nodes map + private handleTopLevelNodeChanges( + event: Y.YEvent>>>, + nodesMap: Y.Map> + ): void { + const mapEvent = event as unknown as Y.YMapEvent>; + + if (this.isFullMapReplacement(mapEvent, nodesMap)) { + this.loadMapFromYDoc(); + this.showImportToast(); + return; + } + + mapEvent.keysChanged.forEach(key => { + const change = mapEvent.changes.keys.get(key); + if (!change) return; + + if (change.action === 'add') { + this.applyRemoteNodeAdd(nodesMap.get(key)); + } else if (change.action === 'update') { + this.applyRemoteNodeDelete(key); + this.applyRemoteNodeAdd(nodesMap.get(key)); + } else if (change.action === 'delete') { + this.applyRemoteNodeDelete(key); + } + }); + } + + // Show toast to inform the user that a remote client imported a map + private async showImportToast(): Promise { + const msg = await this.utilsService.translate('TOASTS.MAP_IMPORT_SUCCESS'); + if (msg) this.toastrService.success(msg); + } + + // Detects a full map import by checking if a root node is being added/replaced. + // Normal operations never add root nodes, so this reliably identifies imports. + private isFullMapReplacement( + mapEvent: Y.YMapEvent>, + nodesMap: Y.Map> + ): boolean { + for (const [key, change] of mapEvent.changes.keys) { + if (change.action === 'add' || change.action === 'update') { + const yNode = nodesMap.get(key); + if (yNode?.get('isRoot')) return true; + } + } + return false; + } + + // Handles property changes on individual node Y.Maps + private handleNodePropertyChanges( + event: Y.YEvent>>> + ): void { + const yNode = event.target as unknown as Y.Map; + const nodeId = yNode.get('id') as string; + if (!nodeId || !this.mmpService.existNode(nodeId)) return; + + const mapEvent = event as unknown as Y.YMapEvent; + mapEvent.keysChanged.forEach(key => { + this.applyYDocPropertyToMmp(nodeId, key, yNode.get(key)); + }); + } + + private applyRemoteNodeAdd(yNode: Y.Map): void { + if (!yNode) return; + const nodeProps = this.yMapToNodeProps(yNode); + this.mmpService.addNodesFromServer([nodeProps]); + } + + private applyRemoteNodeDelete(nodeId: string): void { + if (this.mmpService.existNode(nodeId)) { + this.mmpService.removeNode(nodeId, false); + } + } + + // Applies a Y.Doc property change to MMP + private applyYDocPropertyToMmp( + nodeId: string, + yjsKey: string, + value: unknown + ): void { + for (const update of resolveMmpPropertyUpdate(yjsKey, value)) { + this.mmpService.updateNode(update.prop, update.val, false, false, nodeId); + } + } + + private setupYjsMapOptionsObserver(): void { + const optionsMap = this.yDoc.getMap('mapOptions'); + this.yjsOptionsObserver = (_: unknown, transaction: Y.Transaction) => { + if (transaction.local) return; + this.applyRemoteMapOptions(); + }; + optionsMap.observe(this.yjsOptionsObserver); + } + + private applyRemoteMapOptions(): void { + const optionsMap = this.yDoc.getMap('mapOptions'); + const options: CachedMapOptions = { + fontMaxSize: (optionsMap.get('fontMaxSize') as number) ?? 28, + fontMinSize: (optionsMap.get('fontMinSize') as number) ?? 6, + fontIncrement: (optionsMap.get('fontIncrement') as number) ?? 2, + }; + this.mmpService.updateAdditionalMapOptions(options); + } + + // ─── Yjs: Awareness (presence, selection, client list) ─────── + + private setupYjsAwareness(): void { + const awareness = this.wsProvider.awareness; + const color = this.resolveClientColor(awareness); + this.clientColor = color; + + awareness.setLocalStateField('user', { + color, + selectedNodeId: null, + }); + + this.yjsAwarenessHandler = () => { + this.updateFromAwareness(); + }; + awareness.on('change', this.yjsAwarenessHandler); + + // Process awareness states already received before the listener was registered + this.updateFromAwareness(); + } + + // Pick a color that doesn't collide with other clients + private resolveClientColor( + awareness: WebsocketProvider['awareness'] + ): string { + const usedColors = new Set(); + for (const [, state] of awareness.getStates()) { + if (state?.user?.color) usedColors.add(state.user.color); + } + return resolveClientColor(this.clientColor, usedColors); + } + + private updateYjsAwarenessSelection(nodeId: string | null): void { + if (!this.wsProvider) return; + this.wsProvider.awareness.setLocalStateField('user', { + color: this.clientColor, + selectedNodeId: nodeId, + }); + } + + // Rebuild client list and highlights from awareness states + private updateFromAwareness(): void { + const newMapping = this.buildColorMappingFromAwareness(); + const affectedNodes = this.findAffectedNodes(newMapping); + this.colorMapping = newMapping; + this.rehighlightNodes(affectedNodes); + this.extractClientListForSubscriber(); + } + + private buildColorMappingFromAwareness(): ClientColorMapping { + const awareness = this.wsProvider.awareness; + const localClientId = this.yDoc.clientID; + const mapping: ClientColorMapping = {}; + + for (const [clientId, state] of awareness.getStates()) { + if (!state?.user) continue; + const isSelf = clientId === localClientId; + mapping[String(clientId)] = { + color: isSelf ? DEFAULT_SELF_COLOR : state.user.color || DEFAULT_COLOR, + nodeId: state.user.selectedNodeId || '', + }; + } + return mapping; + } + + // Collect node IDs that need re-highlighting + private findAffectedNodes(newMapping: ClientColorMapping): Set { + return findAffectedNodes(this.colorMapping, newMapping); + } + + private rehighlightNodes(nodeIds: Set): void { + for (const nodeId of nodeIds) { + if (!this.mmpService.existNode(nodeId)) continue; + const color = this.colorForNode(nodeId); + this.mmpService.highlightNode(nodeId, color, false); + } + } + + // ─── Y.Doc conversion utilities ────────────────────────────── + + private populateYMapFromNodeProps( + yNode: Y.Map, + nodeProps: ExportNodeProperties + ): void { + populateYMapFromNodeProps(yNode, nodeProps); + } + + private yMapToNodeProps(yNode: Y.Map): ExportNodeProperties { + return yMapToNodeProps(yNode); + } + + // ─── Shared utilities ──────────────────────────────────────── + + private setConnectionStatusSubject(value: ConnectionStatus) { + this.connectionStatusSubject.next(value); + } + + private colorForNode(nodeId: string): string { + const matchingClient = this.clientForNode(nodeId); + return matchingClient ? this.colorMapping[matchingClient].color : ''; + } + + private clientForNode(nodeId: string): string { + return Object.keys(this.colorMapping) + .filter((key: string) => this.colorMapping[key]?.nodeId === nodeId) + .shift(); + } + + private extractClientListForSubscriber(): void { + this.clientListSubject.next( + Object.values(this.colorMapping).map( + (e: ClientColorMappingValue) => e?.color + ) + ); + } + + /** + * Store private map data locally for admin access + */ + private storePrivateMapData(privateServerMap: PrivateServerMap): void { + const serverMap = privateServerMap.map; + this.storageService.set(serverMap.uuid, { + adminId: privateServerMap.adminId, + modificationSecret: privateServerMap.modificationSecret, + ttl: serverMap.deletedAt, + rootName: serverMap.data[0].name, + createdAt: serverMap.createdAt, + }); + } + + /** + * Setup state for newly created map + */ + private setupNewMapState(privateServerMap: PrivateServerMap): void { + this.prepareMap(privateServerMap.map); + this.settingsService.setEditMode(true); + this.modificationSecret = privateServerMap.modificationSecret; + } + + private async fetchMapFromServer(id: string): Promise { + const response = await this.httpService.get(API_URL.ROOT, '/maps/' + id); + if (!response.ok) return null; + const json: ServerMap = await response.json(); + return json; + } + + private async postMapToServer(): Promise { + const response = await this.httpService.post( + API_URL.ROOT, + '/maps/', + JSON.stringify({ + rootNode: + this.settingsService.getCachedUserSettings().mapOptions.rootNode, + }) + ); + + return response.json(); + } + + /** + * Return the key of the map in the storage + */ + private createKey(uuid: string): string { + return `map-${uuid}`; + } + + /** + * Converts server map + */ + private convertServerMapToMmp(serverMap: ServerMap): MapProperties { + return Object.assign({}, serverMap, { + lastModified: Date.parse(serverMap.lastModified), + deletedAt: Date.parse(serverMap.deletedAt), + createdAt: Date.parse(serverMap.createdAt), + }); + } + + private prepareMap(serverMap: ServerMap) { + const mapKey = this.createKey(serverMap.uuid); + const mapProps = this.convertServerMapToMmp(serverMap); + this.attachMap({ + key: mapKey, + cachedMap: { ...mapProps, ...{ options: serverMap.options } }, + }); + this.mmpService.updateAdditionalMapOptions(serverMap.options); + } + + private async updateCachedMapForAdmin(serverMap: ServerMap) { + const map: CachedAdminMapValue | null = (await this.storageService.get( + serverMap.uuid + )) as CachedAdminMapValue | null; + if (map) { + map.ttl = new Date(serverMap.deletedAt); + map.rootName = serverMap.data?.[0]?.name; + this.storageService.set(serverMap.uuid, map); + } } } diff --git a/teammapper-frontend/src/app/core/services/map-sync/yjs-utils.ts b/teammapper-frontend/src/app/core/services/map-sync/yjs-utils.ts new file mode 100644 index 000000000..ac1dca2b7 --- /dev/null +++ b/teammapper-frontend/src/app/core/services/map-sync/yjs-utils.ts @@ -0,0 +1,196 @@ +import * as Y from 'yjs'; +import { ExportNodeProperties } from '@mmp/map/types'; +import { ReversePropertyMapping } from './server-types'; + +const MESSAGE_WRITE_ACCESS = 4; + +export type ClientColorMapping = Record; + +export interface ClientColorMappingValue { + nodeId: string; + color: string; +} + +export function populateYMapFromNodeProps( + yNode: Y.Map, + nodeProps: ExportNodeProperties +): void { + yNode.set('id', nodeProps.id); + yNode.set('parent', nodeProps.parent ?? null); + yNode.set('name', nodeProps.name ?? ''); + yNode.set('isRoot', nodeProps.isRoot ?? false); + yNode.set('locked', nodeProps.locked ?? false); + yNode.set('detached', nodeProps.detached ?? false); + yNode.set('k', nodeProps.k ?? 1); + yNode.set('coordinates', nodeProps.coordinates ?? { x: 0, y: 0 }); + yNode.set( + 'colors', + nodeProps.colors ?? { name: '', background: '', branch: '' } + ); + yNode.set('font', nodeProps.font ?? { size: 12, style: '', weight: '' }); + yNode.set('image', nodeProps.image ?? { src: '', size: 0 }); + yNode.set('link', nodeProps.link ?? { href: '' }); +} + +export function yMapToNodeProps(yNode: Y.Map): ExportNodeProperties { + return { + id: yNode.get('id') as string, + parent: (yNode.get('parent') as string) ?? null, + k: (yNode.get('k') as number) ?? 1, + name: (yNode.get('name') as string) ?? '', + isRoot: (yNode.get('isRoot') as boolean) ?? false, + locked: (yNode.get('locked') as boolean) ?? false, + detached: (yNode.get('detached') as boolean) ?? false, + coordinates: (yNode.get('coordinates') as { x: number; y: number }) ?? { + x: 0, + y: 0, + }, + colors: (yNode.get('colors') as ExportNodeProperties['colors']) ?? { + name: '', + background: '', + branch: '', + }, + font: (yNode.get('font') as ExportNodeProperties['font']) ?? { + size: 12, + style: '', + weight: '', + }, + image: (yNode.get('image') as ExportNodeProperties['image']) ?? { + src: '', + size: 0, + }, + link: (yNode.get('link') as ExportNodeProperties['link']) ?? { + href: '', + }, + }; +} + +export function buildYjsWsUrl(): string { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const host = window.location.host; + const baseHref = document.querySelector('base')?.getAttribute('href') ?? '/'; + const path = baseHref.endsWith('/') ? baseHref : baseHref + '/'; + return `${protocol}//${host}${path}yjs`; +} + +// Returns true for writable, false for read-only, null if not a write-access message +export function parseWriteAccessBytes(data: Uint8Array): boolean | null { + if (data.length >= 2 && data[0] === MESSAGE_WRITE_ACCESS) { + return data[1] === 1; + } + return null; +} + +export function resolveClientColor( + currentColor: string, + usedColors: Set +): string { + if (!usedColors.has(currentColor)) return currentColor; + + return ( + '#' + + Math.floor(Math.random() * 16777215) + .toString(16) + .padStart(6, '0') + ); +} + +export function findAffectedNodes( + oldMapping: ClientColorMapping, + newMapping: ClientColorMapping +): Set { + const nodes = new Set(); + for (const value of Object.values(oldMapping)) { + if (value.nodeId) nodes.add(value.nodeId); + } + for (const value of Object.values(newMapping)) { + if (value.nodeId) nodes.add(value.nodeId); + } + return nodes; +} + +export interface MmpPropertyUpdate { + prop: string; + val: unknown; +} + +export function resolveMmpPropertyUpdate( + yjsKey: string, + value: unknown +): MmpPropertyUpdate[] { + const mapping = + ReversePropertyMapping[yjsKey as keyof typeof ReversePropertyMapping]; + if (!mapping) return []; + + if (typeof mapping === 'string') { + return [{ prop: mapping, val: value }]; + } + + return resolveCompoundMmpUpdates( + mapping as Record, + value as Record + ); +} + +// Groups non-root nodes by their parent ID +const groupByParent = ( + nodes: readonly ExportNodeProperties[] +): ReadonlyMap => + nodes + .filter(n => !n.isRoot) + .reduce((acc, node) => { + const pid = node.parent ?? ''; + acc.set(pid, [...(acc.get(pid) ?? []), node]); + return acc; + }, new Map()); + +// Recursive BFS: processes head of queue, enqueues its children, accumulates result +const collectBreadthFirst = ( + queue: readonly ExportNodeProperties[], + childrenOf: ReadonlyMap, + collected: readonly ExportNodeProperties[] = [] +): readonly ExportNodeProperties[] => { + if (queue.length === 0) return collected; + const [current, ...rest] = queue; + const kids = childrenOf.get(current.id) ?? []; + return collectBreadthFirst([...rest, ...kids], childrenOf, [ + ...collected, + current, + ]); +}; + +// Appends nodes not reachable from root to prevent data loss +const appendOrphans = ( + ordered: readonly ExportNodeProperties[], + all: readonly ExportNodeProperties[] +): ExportNodeProperties[] => { + const visited = new Set(ordered.map(n => n.id)); + return [...ordered, ...all.filter(n => !visited.has(n.id))]; +}; + +// Sorts nodes so root comes first and parents always precede their children (BFS order). +// Orphaned nodes (not reachable from root) are appended at the end to prevent data loss. +export const sortParentFirst = ( + nodes: readonly ExportNodeProperties[] +): ExportNodeProperties[] => { + const root = nodes.find(n => n.isRoot); + if (!root) return [...nodes]; + + const childrenOf = groupByParent(nodes); + const ordered = collectBreadthFirst([root], childrenOf); + return appendOrphans(ordered, nodes); +}; + +export function resolveCompoundMmpUpdates( + mapping: Record, + value: Record +): MmpPropertyUpdate[] { + if (!value) return []; + const updates: MmpPropertyUpdate[] = []; + for (const [subKey, mmpProp] of Object.entries(mapping)) { + if (subKey in value) { + updates.push({ prop: mmpProp, val: value[subKey] }); + } + } + return updates; +} diff --git a/teammapper-frontend/src/app/shared/models/settings.model.ts b/teammapper-frontend/src/app/shared/models/settings.model.ts index 174841fb6..0f49616bc 100644 --- a/teammapper-frontend/src/app/shared/models/settings.model.ts +++ b/teammapper-frontend/src/app/shared/models/settings.model.ts @@ -44,4 +44,5 @@ interface Urls { interface FeatureFlags { pictograms: boolean; ai: boolean; + yjs: boolean; } diff --git a/teammapper-frontend/src/proxy.conf.json b/teammapper-frontend/src/proxy.conf.json index 40f97143f..e6cf7fd18 100644 --- a/teammapper-frontend/src/proxy.conf.json +++ b/teammapper-frontend/src/proxy.conf.json @@ -5,6 +5,12 @@ "changeOrigin": true, "ws": true }, + "/yjs": { + "target": "http://localhost:3000", + "secure": false, + "changeOrigin": true, + "ws": true + }, "/api/**": { "target": "http://localhost:3000", "secure": false,