From 624d11483025c7d2ae7ac154152902a7223fc6cf Mon Sep 17 00:00:00 2001 From: Samuel1505 Date: Tue, 28 Apr 2026 03:13:51 +0100 Subject: [PATCH 1/4] Fix #284: Add contract deployment script --- README.md | 16 +++++++++-- scripts/deploy.sh | 73 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) create mode 100755 scripts/deploy.sh diff --git a/README.md b/README.md index 54930ffb..9500d5c0 100644 --- a/README.md +++ b/README.md @@ -92,13 +92,23 @@ npm install npm run dev ``` -### Smart Contracts +### Deployment + +To build, optimize, and deploy the smart contract to a Stellar network, you can use the automated deployment script: ```bash -cd contracts -cargo build --target wasm32-unknown-unknown --release +./scripts/deploy.sh --network testnet ``` +This single command will: +1. Build the contract (`cargo build --target wasm32-unknown-unknown --release`) +2. Optimize the WASM output +3. Deploy the optimized contract to the specified network +4. Initialize the contract with default settings +5. Save the generated contract ID to `deployment-info.json` + +Supported networks include `testnet` and `mainnet`. + ## API Documentation The FlowFi backend API uses URL-based versioning. All endpoints are prefixed with a version (e.g., `/v1/streams`). diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 00000000..894a1088 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +# Default to testnet +NETWORK="testnet" +ADMIN_ALIAS="admin" +TREASURY_ALIAS="treasury" + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --network) NETWORK="$2"; shift ;; + --admin) ADMIN_ALIAS="$2"; shift ;; + --treasury) TREASURY_ALIAS="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +echo "Deploying to $NETWORK..." + +# Check if admin alias exists +if ! stellar keys ls | grep -q "$ADMIN_ALIAS"; then + echo "Generating admin key ($ADMIN_ALIAS)..." + stellar keys generate "$ADMIN_ALIAS" --network "$NETWORK" +fi + +# Check if treasury alias exists +if ! stellar keys ls | grep -q "$TREASURY_ALIAS"; then + echo "Generating treasury key ($TREASURY_ALIAS)..." + stellar keys generate "$TREASURY_ALIAS" --network "$NETWORK" +fi + +ADMIN_ADDRESS=$(stellar keys address "$ADMIN_ALIAS") +TREASURY_ADDRESS=$(stellar keys address "$TREASURY_ALIAS") + +cd contracts + +# Build the contract +echo "Building contract..." +cargo build --target wasm32-unknown-unknown --release + +# Optimize the contract +echo "Optimizing contract..." +stellar contract optimize --wasm target/wasm32-unknown-unknown/release/stream_contract.wasm + +# Deploy the contract +echo "Deploying contract..." +CONTRACT_ID=$(stellar contract deploy \ + --wasm target/wasm32-unknown-unknown/release/stream_contract.optimized.wasm \ + --network "$NETWORK" \ + --source "$ADMIN_ALIAS") + +echo "Contract deployed with ID: $CONTRACT_ID" + +# Initialize the contract +echo "Initializing contract..." +stellar contract invoke \ + --id "$CONTRACT_ID" \ + --network "$NETWORK" \ + --source "$ADMIN_ALIAS" \ + -- \ + initialize \ + --admin "$ADMIN_ADDRESS" \ + --treasury "$TREASURY_ADDRESS" \ + --fee_rate_bps 100 + +cd .. + +# Save to deployment-info.json +echo "{\"network\": \"$NETWORK\", \"contract_id\": \"$CONTRACT_ID\"}" > deployment-info.json + +echo "Deployment complete! Info saved to deployment-info.json" From 4966dcd519aed758bb48ca1279e6268414f5604c Mon Sep 17 00:00:00 2001 From: Samuel1505 Date: Tue, 28 Apr 2026 03:15:15 +0100 Subject: [PATCH 2/4] Fix #285: Write architecture and developer onboarding documentation --- README.md | 5 +- docs/ARCHITECTURE.md | 260 +++++++++---------------------------------- docs/DEVELOPMENT.md | 74 ++++++++++++ 3 files changed, 128 insertions(+), 211 deletions(-) create mode 100644 docs/DEVELOPMENT.md diff --git a/README.md b/README.md index 9500d5c0..b04a6a21 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ flowfi/ │ ├── stream_contract/ # Core streaming logic ├── frontend/ # Next.js + Tailwind CSS frontend ├── docs/ # Documentation -│ └── ARCHITECTURE.md # Architecture overview +│ ├── ARCHITECTURE.md # Architecture overview +│ └── DEVELOPMENT.md # Local development guide ``` ## Architecture @@ -38,6 +39,8 @@ For a detailed explanation of how these components interact, where event indexin ## Getting Started +For full step-by-step instructions, see our [Development Guide](docs/DEVELOPMENT.md). + ### Prerequisites - Node.js & npm diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 7b078da5..1369fbd1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -2,7 +2,7 @@ This document provides a high-level overview of how FlowFi's components interact and how the system processes on-chain events. -## System Components +## System Components & Architecture FlowFi consists of three main components: @@ -10,233 +10,73 @@ FlowFi consists of three main components: 2. **Backend API** - Indexing, API endpoints, and real-time event streaming 3. **Frontend** - User interface built with Next.js +```mermaid +graph TD + User([User]) -->|Creates Stream / Pauses| Frontend + Frontend -->|Invokes Contract| Contract[Soroban Smart Contract] + Contract -->|Emits Events| Blockchain[(Stellar Network)] + + EventWorker[Backend Event Worker] -->|Polls Events| Blockchain + EventWorker -->|Parses & Stores| DB[(PostgreSQL)] + + Backend[Backend API] -->|Reads| DB + Backend -->|Broadcasts Updates| SSE[SSE Connection] + + Frontend -->|Fetches Initial Data| Backend + Frontend -->|Listens for Updates| SSE ``` -┌─────────────┐ -│ Frontend │ (Next.js + React) -│ (Port 3000)│ -└──────┬──────┘ - │ HTTP/REST - │ SSE (Server-Sent Events) - ▼ -┌─────────────┐ -│ Backend │ (Express.js + TypeScript) -│ (Port 3001)│ -└──────┬──────┘ - │ - │ Indexes Events - │ Queries State - ▼ -┌─────────────┐ -│ Stellar │ -│ Network │ -└──────┬──────┘ - │ - │ Smart Contract - │ Events & State - ▼ -┌─────────────┐ -│ Soroban │ -│ Contracts │ (Rust) -└─────────────┘ -``` - -## Component Interactions - -### 1. Soroban Smart Contracts - -**Location:** `contracts/stream_contract/` -The smart contract handles all on-chain logic for payment streams: - -- **Stream Creation**: Users create streams by depositing tokens -- **Withdrawals**: Recipients can withdraw available funds -- **Top-ups**: Senders can add more funds to active streams -- **Cancellation**: Senders can cancel streams and receive refunds - -**Key Contract Functions:** -- `create_stream()` - Creates a new payment stream -- `withdraw()` - Withdraws available funds from a stream -- `top_up_stream()` - Adds funds to an existing stream -- `cancel_stream()` - Cancels a stream and refunds remaining balance -- `get_stream()` - Reads stream state - -**Events Emitted:** -The contract emits events for all state changes: -- `stream_created` - When a new stream is created -- `tokens_withdrawn` - When funds are withdrawn -- `stream_topped_up` - When additional funds are added -- `stream_cancelled` - When a stream is cancelled - -### 2. Backend API - -**Location:** `backend/` +## Data Flow by Event Type -The backend serves multiple purposes: +When a user interacts with the contract, it emits specific events that the backend indexer picks up: -#### A. Event Indexing +- **`stream_created`**: The indexer creates a new stream record in the DB, setting the total duration, per-second rate, and initial balances. +- **`stream_topped_up`**: The indexer increases the deposited amount in the stream record and updates the projected end time based on the rate. +- **`stream_paused`**: The indexer marks the stream as `isPaused = true`, recording the timestamp of the pause. Claimable balance calculations temporarily stop. +- **`stream_resumed`**: The indexer marks the stream as `isPaused = false` and calculates the total duration the stream was paused to correctly offset future accrual. +- **`stream_cancelled`**: The indexer sets `isActive = false`. Funds are permanently divided between sender and recipient as of the cancellation block. +- **`tokens_withdrawn`**: The indexer updates the withdrawn amount and adjusts remaining balances. +- **`stream_completed`**: The indexer marks the stream as finished after all funds have been withdrawn. -**Where Indexing Happens:** +## Pause/Resume Timing Model -The backend indexes on-chain events from Soroban contracts. The indexing process: +To accurately track how much has accrued in a stream, we must exclude the time the stream spent in a paused state. -1. **Event Detection**: The backend listens to Stellar network events (via Stellar Horizon API or similar) -2. **Event Processing**: When contract events are detected, they are processed and stored -3. **Database Storage**: Events are stored in PostgreSQL using Prisma ORM +When a stream is **paused**, we record `last_paused_at`. +When a stream is **resumed**, we calculate the difference `current_time - last_paused_at` and add this duration to a running `total_paused_seconds` tally. -**Database Models:** -- `Stream` - Mirrors on-chain stream state for fast querying -- `StreamEvent` - Stores all on-chain events (CREATED, TOPPED_UP, WITHDRAWN, CANCELLED, COMPLETED) -- `User` - Tracks Stellar wallet addresses - -**Indexing Implementation:** - -The indexing logic is designed to be integrated with a Stellar event listener. See: -- `backend/src/services/indexer-integration.example.ts` - Example integration pattern -- `backend/prisma/schema.prisma` - Database schema for indexed data - -**Event Types Indexed:** -- `CREATED` - Stream creation events -- `TOPPED_UP` - Additional funds added -- `WITHDRAWN` - Funds withdrawn by recipient -- `CANCELLED` - Stream cancellation -- `COMPLETED` - Stream completion (all funds withdrawn) - -#### B. REST API - -The backend provides REST endpoints for: - -- **Stream Management**: Query stream state, create streams (via contract interaction) -- **User Data**: Get user streams, balances, history -- **Health Checks**: API status and metrics - -**API Documentation:** -- Swagger UI: `http://localhost:3001/api-docs` -- OpenAPI Spec: `http://localhost:3001/api-docs.json` +**Accrual Formula:** +``` +accrued_time = current_time - start_time - total_paused_seconds +claimable_balance = accrued_time * rate_per_second +``` +This model ensures that the recipient does not earn funds while the stream is paused, and no complex iterative calculations are required on-chain or off-chain. -#### C. Real-Time Event Streaming +## Database Schema Overview -**Server-Sent Events (SSE):** +The backend uses PostgreSQL with the following primary entities: -The backend provides SSE endpoints for real-time updates: +- **`Stream`**: The core entity tracking streams. Fields include `contract_id`, `sender`, `recipient`, `rate_per_second`, `total_paused_seconds`, `is_paused`, and `is_active`. +- **`StreamEvent`**: An append-only log of all events processed by the indexer. Used for historical reconstruction and providing activity feeds. +- **`User`**: Tracks Stellar wallet addresses. +- **`indexer_state`**: A single-row table tracking the last successfully processed ledger, ensuring the indexer can resume correctly after a restart. -- **Endpoint**: `GET /events/subscribe` -- **Purpose**: Push real-time stream updates to frontend clients -- **Event Types**: `stream.created`, `stream.topped_up`, `stream.withdrawn`, `stream.cancelled`, `stream.completed` +## Component Interactions -**How It Works:** -1. Frontend connects to SSE endpoint -2. Backend maintains connection and broadcasts events -3. When on-chain events are indexed, they trigger SSE broadcasts -4. Frontend receives real-time updates without polling +### 1. Soroban Smart Contracts +**Location:** `contracts/stream_contract/` +The smart contract handles all on-chain logic for payment streams. -See `backend/docs/SSE_ARCHITECTURE.md` for detailed SSE implementation. +### 2. Backend API +**Location:** `backend/` +The backend provides REST endpoints for querying data and SSE endpoints for real-time updates. ### 3. Frontend - **Location:** `frontend/` - -The frontend is a Next.js application that: - -- **Displays Streams**: Shows active streams, incoming/outgoing payments -- **Wallet Integration**: Connects to Stellar wallets (Freighter, etc.) -- **Real-Time Updates**: Subscribes to SSE events for live stream updates -- **Stream Management**: UI for creating, viewing, and managing streams - -**Key Features:** -- Dashboard with stream overview -- Incoming/outgoing stream lists -- Real-time balance updates via SSE -- Wallet connection and transaction signing - -## Data Flow - -### Creating a Stream - -1. **User Action**: User fills out stream creation form in frontend -2. **Frontend**: Prepares transaction and prompts wallet for signature -3. **Stellar Network**: Transaction is submitted and processed -4. **Contract**: `create_stream()` executes, emits `stream_created` event -5. **Backend Indexer**: Detects event, stores in database -6. **Backend SSE**: Broadcasts event to subscribed clients -7. **Frontend**: Receives SSE update, UI updates automatically - -### Withdrawing from a Stream - -1. **User Action**: Recipient clicks withdraw in frontend -2. **Frontend**: Prepares withdrawal transaction, prompts wallet -3. **Stellar Network**: Transaction submitted -4. **Contract**: `withdraw()` executes, emits `tokens_withdrawn` event -5. **Backend Indexer**: Detects event, updates database -6. **Backend SSE**: Broadcasts withdrawal event -7. **Frontend**: Updates balance and stream state - -### Querying Stream State - -1. **User Action**: User navigates to stream details page -2. **Frontend**: Makes REST API call to backend -3. **Backend**: Queries indexed database (fast, no on-chain call needed) -4. **Backend**: Returns stream data -5. **Frontend**: Displays stream information - -For real-time accuracy, the frontend can also: -- Subscribe to SSE events for that specific stream -- Receive updates immediately when on-chain events occur - -## Technology Stack - -### Smart Contracts -- **Language**: Rust -- **Framework**: Soroban SDK -- **Build Target**: `wasm32-unknown-unknown` - -### Backend -- **Runtime**: Node.js -- **Framework**: Express.js -- **Language**: TypeScript -- **Database**: PostgreSQL -- **ORM**: Prisma -- **Real-Time**: Server-Sent Events (SSE) - -### Frontend -- **Framework**: Next.js 16 -- **Language**: TypeScript -- **Styling**: Tailwind CSS -- **State Management**: React Context -- **Wallet Integration**: Stellar SDK - -## Development Workflow - -### Local Development - -1. **Start Infrastructure**: `docker compose up` (PostgreSQL) -2. **Start Backend**: `cd backend && npm run dev` -3. **Start Frontend**: `cd frontend && npm run dev` -4. **Deploy Contracts**: Build and deploy to Stellar testnet - -### Testing - -- **Contracts**: Rust unit tests in contract source files -- **Backend**: Vitest for API and service tests -- **Frontend**: Next.js testing utilities - -## Security Considerations - -- **Rate Limiting**: Backend implements rate limiting on all endpoints -- **Input Validation**: Zod schemas validate all API inputs -- **Authentication**: Wallet-based authentication via Stellar signatures -- **Event Verification**: Indexed events are verified against on-chain state - -## Future Enhancements - -Potential areas for improvement: - -- **Indexer Service**: Dedicated microservice for event indexing -- **Caching Layer**: Redis for frequently accessed stream data -- **WebSocket Support**: Alternative to SSE for bidirectional communication -- **GraphQL API**: More flexible querying for complex frontend needs +The Next.js application that displays streams and integrates with Stellar wallets. ## Related Documentation -- [SSE Architecture](backend/docs/SSE_ARCHITECTURE.md) - Detailed SSE implementation -- [Backend README](backend/SSE_README.md) - Backend-specific documentation +- [Development Guide](DEVELOPMENT.md) - Local setup and workflow +- [SSE Architecture](../backend/docs/SSE_ARCHITECTURE.md) - Detailed SSE implementation - [Contributing Guide](../CONTRIBUTING.md) - Development setup and workflows diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 00000000..92d81aef --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,74 @@ +# FlowFi Development Guide + +This guide explains how to set up the FlowFi project locally for development. + +## Prerequisites + +Ensure you have the following installed before starting: +- **Rust** (for compiling Soroban smart contracts) +- **Node.js 20+** (for frontend and backend) +- **Stellar CLI** (for deploying contracts and managing identities) +- **PostgreSQL** (can be run via Docker Compose) + +## Step-by-Step Local Setup + +1. **Clone the repository:** + ```bash + git clone + cd flowfi + ``` + +2. **Start the database:** + We recommend using the provided `docker-compose.yml` to run the Postgres database locally. + ```bash + docker-compose up -d + ``` + +3. **Install Dependencies:** + Install backend dependencies: + ```bash + cd backend + npm install + ``` + + Install frontend dependencies: + ```bash + cd ../frontend + npm install + ``` + +4. **Environment Variables:** + - In `backend`, copy `.env.example` to `.env` and set `DATABASE_URL` (defaults to the docker-compose setup). + - In `frontend`, copy `.env.example` to `.env.local`. + +5. **Initialize the Database:** + ```bash + cd backend + npx prisma migrate dev + ``` + +6. **Start the Services:** + - Run the backend API & Indexer: + ```bash + cd backend + npm run dev + ``` + - Run the frontend: + ```bash + cd frontend + npm run dev + ``` + +## Running against Testnet vs Local Sandbox + +**Testnet:** +By default, you can configure your backend `.env` and frontend to point to the Stellar Testnet. When deploying the smart contracts, use `--network testnet` (via our `deploy.sh` script) and update your frontend's `NEXT_PUBLIC_CONTRACT_ID`. + +**Local Sandbox:** +The backend supports a sandbox mode for risk-free testing without polluting production or testnet metrics. Enable it by setting `SANDBOX_MODE_ENABLED=true` in your backend `.env` and appending `?sandbox=true` to your requests. See `backend/docs/SANDBOX_MODE.md` for more details. + +## Common Troubleshooting + +- **Indexer Not Syncing:** Check if your `DATABASE_URL` is correct and Postgres is running. Also check the `indexer_state` table in the DB to see if the last processed ledger is stuck. Look at the backend console for Stellar Horizon connection errors. +- **SSE Not Connecting:** If the frontend is not receiving real-time updates, check the Network tab in your browser's DevTools. The SSE connection to `/v1/events/subscribe` should remain open (status 200). Ensure CORS settings in the backend allow the frontend origin. + From 6c94654f175a87ee85d23a7e924c1ddda472d930 Mon Sep 17 00:00:00 2001 From: Samuel1505 Date: Tue, 28 Apr 2026 03:19:57 +0100 Subject: [PATCH 3/4] Fix #278: Add toast notifications and global loading states --- .../components/dashboard/dashboard-view.tsx | 13 +++++----- .../stream-creation/StreamCreationWizard.tsx | 14 ++-------- .../components/stream-creation/TopUpModal.tsx | 14 ++-------- frontend/components/ui/Button.tsx | 26 +++++++++++++++++++ frontend/components/ui/Skeleton.tsx | 24 +++++++++++++++++ 5 files changed, 61 insertions(+), 30 deletions(-) create mode 100644 frontend/components/ui/Skeleton.tsx diff --git a/frontend/components/dashboard/dashboard-view.tsx b/frontend/components/dashboard/dashboard-view.tsx index abbc6c1e..771e523f 100644 --- a/frontend/components/dashboard/dashboard-view.tsx +++ b/frontend/components/dashboard/dashboard-view.tsx @@ -3,6 +3,7 @@ import React from "react"; import Link from "next/link"; import toast from "react-hot-toast"; +import { StreamListSkeleton } from "../ui/Skeleton"; /** * components/dashboard/dashboard-view.tsx @@ -760,9 +761,9 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) { if (activeTab === "incoming") { if (isSnapshotLoading) { return ( -
-

Loading streams...

-

Fetching your incoming streams from the backend API.

+
+

Loading incoming streams...

+
); } @@ -790,9 +791,9 @@ export function DashboardView({ session, onDisconnect }: DashboardViewProps) { if (activeTab === "overview") { if (isSnapshotLoading) { return ( -
-

Loading dashboard...

-

Fetching active and incoming streams from the backend API.

+
+

Loading dashboard...

+
); } diff --git a/frontend/components/stream-creation/StreamCreationWizard.tsx b/frontend/components/stream-creation/StreamCreationWizard.tsx index 65d3f742..bb711692 100644 --- a/frontend/components/stream-creation/StreamCreationWizard.tsx +++ b/frontend/components/stream-creation/StreamCreationWizard.tsx @@ -315,18 +315,8 @@ export const StreamCreationWizard: React.FC = ({ ) : ( - )}
diff --git a/frontend/components/stream-creation/TopUpModal.tsx b/frontend/components/stream-creation/TopUpModal.tsx index 3065cd5f..251172b9 100644 --- a/frontend/components/stream-creation/TopUpModal.tsx +++ b/frontend/components/stream-creation/TopUpModal.tsx @@ -164,18 +164,8 @@ export const TopUpModal: React.FC = ({ - diff --git a/frontend/components/ui/Button.tsx b/frontend/components/ui/Button.tsx index 59925b4e..80abe340 100644 --- a/frontend/components/ui/Button.tsx +++ b/frontend/components/ui/Button.tsx @@ -4,6 +4,7 @@ interface ButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'outline' | 'ghost'; size?: 'sm' | 'md' | 'lg'; glow?: boolean; + loading?: boolean; } export const Button: React.FC = ({ @@ -12,6 +13,8 @@ export const Button: React.FC = ({ variant = 'primary', size = 'md', glow = false, + loading = false, + disabled, ...props }) => { const baseStyles = 'inline-flex items-center justify-center rounded-full font-semibold transition-all active:scale-95 disabled:opacity-50 disabled:pointer-events-none'; @@ -34,8 +37,31 @@ export const Button: React.FC = ({ return ( ); diff --git a/frontend/components/ui/Skeleton.tsx b/frontend/components/ui/Skeleton.tsx new file mode 100644 index 00000000..3377e22f --- /dev/null +++ b/frontend/components/ui/Skeleton.tsx @@ -0,0 +1,24 @@ +import React from "react"; + +export function Skeleton({ className = "" }: { className?: string }) { + return ( +
+ ); +} + +export function StreamListSkeleton() { + return ( +
+
+ +
+
+ + + +
+
+ ); +} From 9412503c78130f3826ea1853059a309fcd06ba90 Mon Sep 17 00:00:00 2001 From: Samuel1505 Date: Tue, 28 Apr 2026 04:08:47 +0100 Subject: [PATCH 4/4] feat:stream lifecycle enhancements and api integration test --- backend/package.json | 3 +- backend/prisma/schema.prisma | 3 + .../src/__tests__/integration/streams.test.ts | 221 ++ backend/src/controllers/sse.controller.ts | 2 +- backend/src/workers/soroban-event-worker.ts | 90 +- backend/vitest.config.ts | 2 +- docker-compose.yml | 2 +- package-lock.json | 1835 ++++++++++++++--- package.json | 3 +- 9 files changed, 1868 insertions(+), 293 deletions(-) create mode 100644 backend/src/__tests__/integration/streams.test.ts diff --git a/backend/package.json b/backend/package.json index 6996aa26..aad43d0a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,7 @@ "@stellar/stellar-sdk": "^14.5.0", "cors": "^2.8.6", "dotenv": "^17.3.1", + "esbuild": "0.28.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "ioredis": "^5.3.2", @@ -53,4 +54,4 @@ "typescript": "^5.9.3", "vitest": "^2.1.8" } -} \ No newline at end of file +} diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index a5ab55f1..b258e974 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -37,6 +37,9 @@ model Stream { startTime Int // Unix timestamp when stream started lastUpdateTime Int // Unix timestamp of last update isActive Boolean @default(true) + isPaused Boolean @default(false) + totalPausedSeconds Int @default(0) + lastPausedAt Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/backend/src/__tests__/integration/streams.test.ts b/backend/src/__tests__/integration/streams.test.ts new file mode 100644 index 00000000..8d3c0616 --- /dev/null +++ b/backend/src/__tests__/integration/streams.test.ts @@ -0,0 +1,221 @@ +import 'dotenv/config'; +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import request from 'supertest'; +import { nativeToScVal, xdr, StrKey, Keypair } from '@stellar/stellar-sdk'; +import app from '../../app.js'; +import { prisma } from '../../lib/prisma.js'; +import { sorobanEventWorker } from '../../workers/soroban-event-worker.js'; +import { sseService } from '../../services/sse.service.js'; + +describe('Stream Lifecycle Integration Tests', () => { + const senderPair = Keypair.random(); + const recipientPair = Keypair.random(); + const sender = senderPair.publicKey(); + const recipient = recipientPair.publicKey(); + const tokenAddress = StrKey.encodeContract(Buffer.alloc(32)); + const streamId = 999; + let sseEvents: any[] = []; + + beforeAll(async () => { + // Set up a test SSE client + const mockRes = { + write: (chunk: string) => { + const lines = chunk.split('\n'); + let eventName = ''; + let data: any = null; + for (const line of lines) { + if (line.startsWith('event: ')) eventName = line.slice(7).trim(); + if (line.startsWith('data: ')) { + try { + data = JSON.parse(line.slice(6).trim()); + } catch (e) {} + } + } + if (eventName && data) { + sseEvents.push({ event: eventName, data }); + } + }, + on: () => {}, + } as any; + + sseService.addClient('test-integration-client', mockRes, ['*']); + + // Clean up DB before test + await prisma.streamEvent.deleteMany({ where: { streamId } }).catch(() => {}); + await prisma.stream.deleteMany({ where: { streamId } }).catch(() => {}); + }); + + afterAll(async () => { + await prisma.streamEvent.deleteMany({ where: { streamId } }).catch(() => {}); + await prisma.stream.deleteMany({ where: { streamId } }).catch(() => {}); + }); + + it('Indexer processes stream_created event -> stream appears in GET /v1/streams/{id}', async () => { + sseEvents = []; + + const event = { + id: 'created-event-1', + txHash: 'hash-created', + ledger: 100, + inSuccessfulContractCall: true, + topic: [ + xdr.ScVal.scvSymbol('stream_created'), + nativeToScVal(BigInt(streamId), { type: 'u64' }), + ], + value: xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('sender'), + val: nativeToScVal(sender, { type: 'address' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('recipient'), + val: nativeToScVal(recipient, { type: 'address' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('token_address'), + val: nativeToScVal(tokenAddress, { type: 'address' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('rate_per_second'), + val: nativeToScVal(BigInt(10), { type: 'i128' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('deposited_amount'), + val: nativeToScVal(BigInt(1000), { type: 'i128' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('start_time'), + val: nativeToScVal(BigInt(Math.floor(Date.now() / 1000)), { type: 'u64' }), + }), + ]), + } as any; + + await sorobanEventWorker.processEvent(event); + + // Verify stream appears in GET API + const res = await request(app).get(`/v1/streams/${streamId}`); + expect(res.status).toBe(200); + expect(res.body.streamId).toBe(streamId); + expect(res.body.depositedAmount).toBe('1000'); + + // Verify SSE + expect(sseEvents.some(e => e.event === 'stream.created')).toBe(true); + }); + + it('Indexer processes stream_topped_up -> depositedAmount updated in DB', async () => { + sseEvents = []; + + const event = { + id: 'topped-up-event-1', + txHash: 'hash-topped-up', + ledger: 101, + inSuccessfulContractCall: true, + topic: [ + xdr.ScVal.scvSymbol('stream_topped_up'), + nativeToScVal(BigInt(streamId), { type: 'u64' }), + ], + value: xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('amount'), + val: nativeToScVal(BigInt(500), { type: 'i128' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('new_deposited_amount'), + val: nativeToScVal(BigInt(1500), { type: 'i128' }), + }), + ]), + } as any; + + await sorobanEventWorker.processEvent(event); + + const dbStream = await prisma.stream.findUnique({ where: { streamId } }); + expect(dbStream?.depositedAmount).toBe('1500'); + + expect(sseEvents.some(e => e.event === 'stream.topped_up')).toBe(true); + }); + + it('Indexer processes stream_paused -> isPaused = true', async () => { + sseEvents = []; + + const event = { + id: 'paused-event-1', + txHash: 'hash-paused', + ledger: 102, + inSuccessfulContractCall: true, + topic: [ + xdr.ScVal.scvSymbol('stream_paused'), + nativeToScVal(BigInt(streamId), { type: 'u64' }), + ], + value: xdr.ScVal.scvMap([]), + } as any; + + await sorobanEventWorker.processEvent(event); + + const dbStream = await prisma.stream.findUnique({ where: { streamId } }); + expect(dbStream?.isPaused).toBe(true); + + expect(sseEvents.some(e => e.event === 'stream.paused')).toBe(true); + }); + + it('Indexer processes stream_resumed -> isPaused = false', async () => { + sseEvents = []; + + const event = { + id: 'resumed-event-1', + txHash: 'hash-resumed', + ledger: 103, + inSuccessfulContractCall: true, + topic: [ + xdr.ScVal.scvSymbol('stream_resumed'), + nativeToScVal(BigInt(streamId), { type: 'u64' }), + ], + value: xdr.ScVal.scvMap([]), + } as any; + + await sorobanEventWorker.processEvent(event); + + const dbStream = await prisma.stream.findUnique({ where: { streamId } }); + expect(dbStream?.isPaused).toBe(false); + + expect(sseEvents.some(e => e.event === 'stream.resumed')).toBe(true); + }); + + it('Indexer processes stream_cancelled -> stream isActive = false', async () => { + sseEvents = []; + + const event = { + id: 'cancelled-event-1', + txHash: 'hash-cancelled', + ledger: 104, + inSuccessfulContractCall: true, + topic: [ + xdr.ScVal.scvSymbol('stream_cancelled'), + nativeToScVal(BigInt(streamId), { type: 'u64' }), + ], + value: xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('amount_withdrawn'), + val: nativeToScVal(BigInt(100), { type: 'i128' }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('refunded_amount'), + val: nativeToScVal(BigInt(1400), { type: 'i128' }), + }), + ]), + } as any; + + await sorobanEventWorker.processEvent(event); + + const dbStream = await prisma.stream.findUnique({ where: { streamId } }); + expect(dbStream?.isActive).toBe(false); + + expect(sseEvents.some(e => e.event === 'stream.cancelled')).toBe(true); + }); + + it('GET /v1/streams/{id}/events returns all lifecycle events', async () => { + const res = await request(app).get(`/v1/streams/${streamId}/events`); + expect(res.status).toBe(200); + expect(Array.isArray(res.body.data)).toBe(true); + expect(res.body.data.length).toBeGreaterThan(0); + }); +}); diff --git a/backend/src/controllers/sse.controller.ts b/backend/src/controllers/sse.controller.ts index 438ca518..24356bab 100644 --- a/backend/src/controllers/sse.controller.ts +++ b/backend/src/controllers/sse.controller.ts @@ -9,7 +9,7 @@ const subscribeSchema = z.object({ all: z.boolean().optional().default(false), }); -export const subscribe = (req: Request, res: Response) => { +export const subscribe = async (req: Request, res: Response) => { if (sseService.isShuttingDown()) { return res.status(503).json({ message: 'Server is shutting down, please reconnect shortly.' }); } diff --git a/backend/src/workers/soroban-event-worker.ts b/backend/src/workers/soroban-event-worker.ts index 3716d676..7fa034cb 100644 --- a/backend/src/workers/soroban-event-worker.ts +++ b/backend/src/workers/soroban-event-worker.ts @@ -222,7 +222,7 @@ export class SorobanEventWorker { * Dispatch a single contract event to the appropriate handler based on the * first topic symbol. */ - private async processEvent( + public async processEvent( event: rpc.Api.EventResponse, ): Promise { if (!event.topic || event.topic.length < 2) return; @@ -243,6 +243,12 @@ export class SorobanEventWorker { case 'tokens_withdrawn': await this.handleTokensWithdrawn(event, topic1); break; + case 'stream_paused': + await this.handleStreamPaused(event, topic1); + break; + case 'stream_resumed': + await this.handleStreamResumed(event, topic1); + break; case 'stream_cancelled': await this.handleStreamCancelled(event, topic1); break; @@ -391,6 +397,88 @@ export class SorobanEventWorker { }); } + private async handleStreamPaused( + event: rpc.Api.EventResponse, + streamIdTopic: xdr.ScVal, + ): Promise { + const streamId = Number(decodeU64(streamIdTopic)); + const timestamp = Math.floor(Date.now() / 1000); + + await prisma.$transaction(async (tx: any) => { + await tx.stream.update({ + where: { streamId }, + data: { + isPaused: true, + lastPausedAt: timestamp, + }, + }); + + await tx.streamEvent.create({ + data: { + streamId, + eventType: 'PAUSED', + transactionHash: event.txHash, + ledgerSequence: event.ledger, + timestamp, + }, + }); + }); + + sseService.broadcastToStream(String(streamId), 'stream.paused', { + streamId, + isPaused: true, + transactionHash: event.txHash, + ledger: event.ledger, + timestamp, + }); + } + + private async handleStreamResumed( + event: rpc.Api.EventResponse, + streamIdTopic: xdr.ScVal, + ): Promise { + const streamId = Number(decodeU64(streamIdTopic)); + const timestamp = Math.floor(Date.now() / 1000); + + await prisma.$transaction(async (tx: any) => { + const stream = await tx.stream.findUniqueOrThrow({ + where: { streamId }, + select: { totalPausedSeconds: true, lastPausedAt: true }, + }); + + const lastPausedAt = stream.lastPausedAt ?? timestamp; + const pausedDuration = Math.max(0, timestamp - lastPausedAt); + const nextTotalPausedSeconds = stream.totalPausedSeconds + pausedDuration; + + await tx.stream.update({ + where: { streamId }, + data: { + isPaused: false, + totalPausedSeconds: nextTotalPausedSeconds, + lastPausedAt: null, + }, + }); + + await tx.streamEvent.create({ + data: { + streamId, + eventType: 'RESUMED', + transactionHash: event.txHash, + ledgerSequence: event.ledger, + timestamp, + }, + }); + }); + + sseService.broadcastToStream(String(streamId), 'stream.resumed', { + streamId, + isPaused: false, + transactionHash: event.txHash, + ledger: event.ledger, + timestamp, + }); + } + private async handleTokensWithdrawn( event: rpc.Api.EventResponse, streamIdTopic: xdr.ScVal, diff --git a/backend/vitest.config.ts b/backend/vitest.config.ts index 13b4bb71..151f7739 100644 --- a/backend/vitest.config.ts +++ b/backend/vitest.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ environment: 'node', globals: true, setupFiles: [], - include: ['tests/**/*.{test,spec}.ts'], + include: ['tests/**/*.{test,spec}.ts', 'src/__tests__/**/*.{test,spec}.ts'], coverage: { reporter: ['text', 'json', 'html'], }, diff --git a/docker-compose.yml b/docker-compose.yml index c05b2f7a..2eb1e671 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: POSTGRES_PASSWORD: flowfi_dev_password POSTGRES_DB: flowfi ports: - - "5432:5432" + - "5433:5432" volumes: - postgres_data:/var/lib/postgresql/data healthcheck: diff --git a/package-lock.json b/package-lock.json index c7b19666..f56a27a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "backend" ], "dependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.2", "react-hot-toast": "^2.6.0" }, "devDependencies": { @@ -33,8 +34,10 @@ "@stellar/stellar-sdk": "^14.5.0", "cors": "^2.8.6", "dotenv": "^17.3.1", + "esbuild": "0.28.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", + "ioredis": "^5.3.2", "pg": "^8.18.0", "stellar-sdk": "^13.3.0", "swagger-jsdoc": "^6.2.8", @@ -60,7 +63,754 @@ "vitest": "^2.1.8" } }, + "backend/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, "backend/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/@types/node": { + "version": "25.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "backend/node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "backend/node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "backend/node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "backend/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "backend/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "backend/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "backend/node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "backend/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "backend/node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", @@ -77,145 +827,313 @@ "node": ">=12" } }, - "backend/node_modules/@types/node": { - "version": "25.3.0", + "backend/node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/expect": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", - "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "backend/node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "@vitest/utils": "2.1.9", - "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "backend/node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/mocker": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", - "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "backend/node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/spy": "2.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/pretty-format": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", - "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "backend/node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/runner": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", - "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "backend/node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/utils": "2.1.9", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/snapshot": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", - "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "backend/node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/spy": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", - "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "backend/node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^3.0.2" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/@vitest/utils": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", - "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "backend/node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "2.1.9", - "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" } }, - "backend/node_modules/chai": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", - "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "backend/node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^2.0.1", - "check-error": "^2.1.1", - "deep-eql": "^5.0.1", - "loupe": "^3.1.0", - "pathval": "^2.0.0" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=18" + "node": ">=12" } }, - "backend/node_modules/esbuild": { + "backend/node_modules/vite/node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", @@ -254,90 +1172,6 @@ "@esbuild/win32-x64": "0.21.5" } }, - "backend/node_modules/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", - "dev": true, - "license": "MIT" - }, - "backend/node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "backend/node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "backend/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, "backend/node_modules/vitest": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", @@ -1435,6 +2269,12 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@ioredis/commands": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz", + "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==", + "license": "MIT" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "dev": true, @@ -1623,135 +2463,469 @@ "node_modules/@prisma/client-runtime-utils": { "version": "7.4.1", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0" + }, + "node_modules/@prisma/config": { + "version": "7.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.18.4", + "empathic": "2.0.0" + } + }, + "node_modules/@prisma/debug": { + "version": "7.4.1", + "license": "Apache-2.0" + }, + "node_modules/@prisma/dev": { + "version": "0.20.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@electric-sql/pglite": "0.3.15", + "@electric-sql/pglite-socket": "0.0.20", + "@electric-sql/pglite-tools": "0.2.20", + "@hono/node-server": "1.19.9", + "@mrleebo/prisma-ast": "0.13.1", + "@prisma/get-platform": "7.2.0", + "@prisma/query-plan-executor": "7.2.0", + "foreground-child": "3.3.1", + "get-port-please": "3.2.0", + "hono": "4.11.4", + "http-status-codes": "2.3.0", + "pathe": "2.0.3", + "proper-lockfile": "4.1.2", + "remeda": "2.33.4", + "std-env": "3.10.0", + "valibot": "1.2.0", + "zeptomatch": "2.1.0" + } + }, + "node_modules/@prisma/driver-adapter-utils": { + "version": "7.4.1", + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.4.1" + } + }, + "node_modules/@prisma/engines": { + "version": "7.4.1", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.4.1", + "@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", + "@prisma/fetch-engine": "7.4.1", + "@prisma/get-platform": "7.4.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { + "version": "7.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.4.1" + } + }, + "node_modules/@prisma/fetch-engine": { + "version": "7.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.4.1", + "@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", + "@prisma/get-platform": "7.4.1" + } + }, + "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { + "version": "7.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.4.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "7.2.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "7.2.0" + } + }, + "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { + "version": "7.2.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/query-plan-executor": { + "version": "7.2.0", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/studio-core": { + "version": "0.13.1", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "@types/react": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/config": { - "version": "7.4.1", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "c12": "3.1.0", - "deepmerge-ts": "7.1.5", - "effect": "3.18.4", - "empathic": "2.0.0" - } - }, - "node_modules/@prisma/debug": { - "version": "7.4.1", - "license": "Apache-2.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/dev": { - "version": "0.20.0", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@electric-sql/pglite": "0.3.15", - "@electric-sql/pglite-socket": "0.0.20", - "@electric-sql/pglite-tools": "0.2.20", - "@hono/node-server": "1.19.9", - "@mrleebo/prisma-ast": "0.13.1", - "@prisma/get-platform": "7.2.0", - "@prisma/query-plan-executor": "7.2.0", - "foreground-child": "3.3.1", - "get-port-please": "3.2.0", - "hono": "4.11.4", - "http-status-codes": "2.3.0", - "pathe": "2.0.3", - "proper-lockfile": "4.1.2", - "remeda": "2.33.4", - "std-env": "3.10.0", - "valibot": "1.2.0", - "zeptomatch": "2.1.0" - } - }, - "node_modules/@prisma/driver-adapter-utils": { - "version": "7.4.1", - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/engines": { - "version": "7.4.1", + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.1", - "@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", - "@prisma/fetch-engine": "7.4.1", - "@prisma/get-platform": "7.4.1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/engines-version": { - "version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/engines/node_modules/@prisma/get-platform": { - "version": "7.4.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.1" - } + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "os": [ + "linux" + ] }, - "node_modules/@prisma/fetch-engine": { - "version": "7.4.1", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.1", - "@prisma/engines-version": "7.5.0-4.55ae170b1ced7fc6ed07a15f110549408c501bb3", - "@prisma/get-platform": "7.4.1" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": { - "version": "7.4.1", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.4.1" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@prisma/get-platform": { - "version": "7.2.0", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@prisma/debug": "7.2.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@prisma/get-platform/node_modules/@prisma/debug": { - "version": "7.2.0", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@prisma/query-plan-executor": { - "version": "7.2.0", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@prisma/studio-core": { - "version": "0.13.1", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "@types/react": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@rollup/rollup-darwin-arm64": { + "node_modules/@rollup/rollup-win32-x64-msvc": { "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "darwin" + "win32" ] }, "node_modules/@rtsao/scc": { @@ -3201,6 +4375,15 @@ "version": "0.0.1", "license": "MIT" }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color": { "version": "5.0.3", "license": "MIT", @@ -3524,7 +4707,6 @@ }, "node_modules/denque": { "version": "2.1.0", - "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.10" @@ -5502,6 +6684,30 @@ "node": ">= 0.4" } }, + "node_modules/ioredis": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz", + "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==", + "license": "MIT", + "dependencies": { + "@ioredis/commands": "1.5.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ip-address": { "version": "10.0.1", "license": "MIT", @@ -6202,10 +7408,22 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "license": "MIT" + }, "node_modules/lodash.get": { "version": "4.4.2", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "license": "MIT" @@ -7420,6 +8638,8 @@ }, "node_modules/react-hot-toast": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", "license": "MIT", "dependencies": { "csstype": "^3.1.3", @@ -7461,6 +8681,27 @@ "node": ">=8.10.0" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "dev": true, @@ -7640,6 +8881,20 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/router": { "version": "2.2.0", "license": "MIT", @@ -8123,6 +9378,12 @@ "dev": true, "license": "MIT" }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "license": "MIT", diff --git a/package.json b/package.json index 8b4907ce..e49a4a74 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ ] }, "dependencies": { + "@rollup/rollup-linux-x64-gnu": "4.60.2", "react-hot-toast": "^2.6.0" }, "optionalDependencies": { @@ -30,4 +31,4 @@ "lightningcss-darwin-arm64": "^1.31.1", "lightningcss-linux-x64-gnu": "^1.31.1" } -} \ No newline at end of file +}