diff --git a/src/backend/base/langflow/alembic/versions/f3b2d1f1002d_add_column_access_type_to_flow.py b/src/backend/base/langflow/alembic/versions/f3b2d1f1002d_add_column_access_type_to_flow.py
new file mode 100644
index 000000000..575a151b7
--- /dev/null
+++ b/src/backend/base/langflow/alembic/versions/f3b2d1f1002d_add_column_access_type_to_flow.py
@@ -0,0 +1,33 @@
+"""add column 'access_type' to flow
+
+Revision ID: f3b2d1f1002d
+Revises: 93e2705fa8d6
+Create Date: 2025-02-05 14:35:29.658101
+
+"""
+from typing import Sequence, Union
+
+from alembic import op
+import sqlalchemy as sa
+from langflow.utils import migration
+
+
+# revision identifiers, used by Alembic.
+revision: str = 'f3b2d1f1002d'
+down_revision: Union[str, None] = '93e2705fa8d6'
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ conn = op.get_bind()
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if not migration.column_exists(table_name='flow', column_name='access_type', conn=conn):
+ batch_op.add_column(sa.Column('access_type', sa.Enum('PRIVATE', 'PUBLIC', name='access_type_enum'), server_default='private', nullable=False))
+
+
+def downgrade() -> None:
+ conn = op.get_bind()
+ with op.batch_alter_table('flow', schema=None) as batch_op:
+ if migration.column_exists(table_name='flow', column_name='access_type', conn=conn):
+ batch_op.drop_column('access_type')
diff --git a/src/backend/base/langflow/api/build.py b/src/backend/base/langflow/api/build.py
index 586d1112f..a68b538c0 100644
--- a/src/backend/base/langflow/api/build.py
+++ b/src/backend/base/langflow/api/build.py
@@ -50,6 +50,7 @@ async def start_flow_build(
log_builds: bool,
current_user: CurrentActiveUser,
queue_service: JobQueueService,
+ flow_name: str | None = None,
) -> str:
"""Start the flow build process by setting up the queue and starting the build task.
@@ -70,6 +71,7 @@ async def start_flow_build(
start_component_id=start_component_id,
log_builds=log_builds,
current_user=current_user,
+ flow_name=flow_name,
)
queue_service.start_job(job_id, task_coro)
except Exception as e:
@@ -154,6 +156,7 @@ async def generate_flow_events(
start_component_id: str | None,
log_builds: bool,
current_user: CurrentActiveUser,
+ flow_name: str | None = None,
) -> None:
"""Generate events for flow building process.
@@ -175,7 +178,7 @@ async def generate_flow_events(
flow_id_str = str(flow_id)
# Create a fresh session for database operations
async with session_scope() as fresh_session:
- graph = await create_graph(fresh_session, flow_id_str)
+ graph = await create_graph(fresh_session, flow_id_str, flow_name)
graph.validate_stream()
first_layer = sort_vertices(graph)
@@ -214,7 +217,7 @@ async def generate_flow_events(
),
)
- async def create_graph(fresh_session, flow_id_str: str) -> Graph:
+ async def create_graph(fresh_session, flow_id_str: str, flow_name: str | None) -> Graph:
if inputs is not None and getattr(inputs, "session", None) is not None:
effective_session_id = inputs.session
else:
@@ -229,8 +232,9 @@ async def generate_flow_events(
session_id=effective_session_id,
)
- result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
- flow_name = result.first()
+ if not flow_name:
+ result = await fresh_session.exec(select(Flow.name).where(Flow.id == flow_id))
+ flow_name = result.first()
return await build_graph_from_data(
flow_id=flow_id_str,
diff --git a/src/backend/base/langflow/api/utils.py b/src/backend/base/langflow/api/utils.py
index 48549fd57..6a8258f0a 100644
--- a/src/backend/base/langflow/api/utils.py
+++ b/src/backend/base/langflow/api/utils.py
@@ -303,3 +303,63 @@ def custom_params(
if page is None and size is None:
return None
return Params(page=page or MIN_PAGE_SIZE, size=size or MAX_PAGE_SIZE)
+
+
+async def verify_public_flow_and_get_user(flow_id: uuid.UUID, client_id: str | None) -> tuple[User, uuid.UUID]:
+ """Verify a public flow request and generate a deterministic flow ID.
+
+ This utility function:
+ 1. Checks that a client_id cookie is provided
+ 2. Verifies the flow exists and is marked as PUBLIC
+ 3. Creates a deterministic UUID based on client_id and original flow_id
+ 4. Retrieves the flow owner user for permission purposes
+
+ This function is used to support public flow endpoints that don't require
+ authentication but still need to operate within the permission model.
+
+ Args:
+ flow_id: The original flow ID to verify
+ client_id: The client ID from the request cookie
+
+ Returns:
+ tuple: (flow owner user, deterministic flow ID for tracking)
+
+ Raises:
+ HTTPException:
+ - 400 if no client_id is provided
+ - 403 if flow doesn't exist or isn't public
+ - 403 if unable to retrieve the flow owner user
+ - 403 if user is not found for public flow
+ """
+ if not client_id:
+ raise HTTPException(status_code=400, detail="No client_id cookie found")
+
+ # Check if the flow is public
+ async with session_scope() as session:
+ from sqlmodel import select
+
+ from langflow.services.database.models.flow.model import AccessTypeEnum, Flow
+
+ flow = (await session.exec(select(Flow).where(Flow.id == flow_id))).first()
+ if not flow or flow.access_type is not AccessTypeEnum.PUBLIC:
+ raise HTTPException(status_code=403, detail="Flow is not public")
+
+ # Create a new flow ID using the client_id and flow_id
+ new_id = f"{client_id}_{flow_id}"
+ new_flow_id = uuid.uuid5(uuid.NAMESPACE_DNS, new_id)
+
+ # Get the user associated with the flow
+ try:
+ from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
+
+ user = await get_user_by_flow_id_or_endpoint_name(str(flow_id))
+
+ except Exception as exc:
+ logger.exception(f"Error getting user for public flow {flow_id}")
+ raise HTTPException(status_code=403, detail="Flow is not accessible") from exc
+
+ if not user:
+ msg = f"User not found for public flow {flow_id}"
+ raise HTTPException(status_code=403, detail=msg)
+
+ return user, new_flow_id
diff --git a/src/backend/base/langflow/api/v1/chat.py b/src/backend/base/langflow/api/v1/chat.py
index 0f2fed846..0af57def0 100644
--- a/src/backend/base/langflow/api/v1/chat.py
+++ b/src/backend/base/langflow/api/v1/chat.py
@@ -6,7 +6,15 @@ import traceback
import uuid
from typing import TYPE_CHECKING, Annotated
-from fastapi import APIRouter, BackgroundTasks, Body, Depends, HTTPException, status
+from fastapi import (
+ APIRouter,
+ BackgroundTasks,
+ Body,
+ Depends,
+ HTTPException,
+ Request,
+ status,
+)
from fastapi.responses import StreamingResponse
from loguru import logger
@@ -25,6 +33,7 @@ from langflow.api.utils import (
format_exception_message,
get_top_level_vertices,
parse_exception,
+ verify_public_flow_and_get_user,
)
from langflow.api.v1.schemas import (
CancelFlowResponse,
@@ -142,8 +151,29 @@ async def build_flow(
log_builds: bool = True,
current_user: CurrentActiveUser,
queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
+ flow_name: str | None = None,
):
- """Build and process a flow, returning a job ID for event polling."""
+ """Build and process a flow, returning a job ID for event polling.
+
+ This endpoint requires authentication through the CurrentActiveUser dependency.
+ For public flows that don't require authentication, use the /build_public_tmp/{flow_id}/flow endpoint.
+
+ Args:
+ flow_id: UUID of the flow to build
+ background_tasks: Background tasks manager
+ inputs: Optional input values for the flow
+ data: Optional flow data
+ files: Optional files to include
+ stop_component_id: Optional ID of component to stop at
+ start_component_id: Optional ID of component to start from
+ log_builds: Whether to log the build process
+ current_user: The authenticated user
+ queue_service: Queue service for job management
+ flow_name: Optional name for the flow
+
+ Returns:
+ Dict with job_id that can be used to poll for build status
+ """
# First verify the flow exists
async with session_scope() as session:
flow = await session.get(Flow, flow_id)
@@ -161,6 +191,7 @@ async def build_flow(
log_builds=log_builds,
current_user=current_user,
queue_service=queue_service,
+ flow_name=flow_name,
)
return {"job_id": job_id}
@@ -254,7 +285,9 @@ async def build_vertex(
# If there's no cache
logger.warning(f"No cache found for {flow_id_str}. Building graph starting at {vertex_id}")
graph = await build_graph_from_db(
- flow_id=flow_id, session=await anext(get_session()), chat_service=chat_service
+ flow_id=flow_id,
+ session=await anext(get_session()),
+ chat_service=chat_service,
)
else:
graph = cache.get("result")
@@ -450,7 +483,11 @@ async def _stream_vertex(flow_id: str, vertex_id: str, chat_service: ChatService
yield str(StreamData(event="close", data={"message": "Stream closed"}))
-@router.get("/build/{flow_id}/{vertex_id}/stream", response_class=StreamingResponse, deprecated=True)
+@router.get(
+ "/build/{flow_id}/{vertex_id}/stream",
+ response_class=StreamingResponse,
+ deprecated=True,
+)
async def build_vertex_stream(
flow_id: uuid.UUID,
vertex_id: str,
@@ -482,7 +519,80 @@ async def build_vertex_stream(
"""
try:
return StreamingResponse(
- _stream_vertex(str(flow_id), vertex_id, get_chat_service()), media_type="text/event-stream"
+ _stream_vertex(str(flow_id), vertex_id, get_chat_service()),
+ media_type="text/event-stream",
)
except Exception as exc:
raise HTTPException(status_code=500, detail="Error building Component") from exc
+
+
+@router.post("/build_public_tmp/{flow_id}/flow")
+async def build_public_tmp(
+ *,
+ background_tasks: LimitVertexBuildBackgroundTasks,
+ flow_id: uuid.UUID,
+ inputs: Annotated[InputValueRequest | None, Body(embed=True)] = None,
+ data: Annotated[FlowDataRequest | None, Body(embed=True)] = None,
+ files: list[str] | None = None,
+ stop_component_id: str | None = None,
+ start_component_id: str | None = None,
+ log_builds: bool | None = True,
+ flow_name: str | None = None,
+ request: Request,
+ queue_service: Annotated[JobQueueService, Depends(get_queue_service)],
+):
+ """Build a public flow without requiring authentication.
+
+ This endpoint is specifically for public flows that don't require authentication.
+ It uses a client_id cookie to create a deterministic flow ID for tracking purposes.
+
+ The endpoint:
+ 1. Verifies the requested flow is marked as public in the database
+ 2. Creates a deterministic UUID based on client_id and flow_id
+ 3. Uses the flow owner's permissions to build the flow
+
+ Requirements:
+ - The flow must be marked as PUBLIC in the database
+ - The request must include a client_id cookie
+
+ Args:
+ flow_id: UUID of the public flow to build
+ background_tasks: Background tasks manager
+ inputs: Optional input values for the flow
+ data: Optional flow data
+ files: Optional files to include
+ stop_component_id: Optional ID of component to stop at
+ start_component_id: Optional ID of component to start from
+ log_builds: Whether to log the build process
+ flow_name: Optional name for the flow
+ request: FastAPI request object (needed for cookie access)
+ queue_service: Queue service for job management
+
+ Returns:
+ Dict with job_id that can be used to poll for build status
+ """
+ try:
+ # Verify this is a public flow and get the associated user
+ client_id = request.cookies.get("client_id")
+ owner_user, new_flow_id = await verify_public_flow_and_get_user(flow_id=flow_id, client_id=client_id)
+
+ # Start the flow build using the new flow ID
+ job_id = await start_flow_build(
+ flow_id=new_flow_id,
+ background_tasks=background_tasks,
+ inputs=inputs,
+ data=data,
+ files=files,
+ stop_component_id=stop_component_id,
+ start_component_id=start_component_id,
+ log_builds=log_builds or False,
+ current_user=owner_user,
+ queue_service=queue_service,
+ flow_name=flow_name or f"{client_id}_{flow_id}",
+ )
+ except Exception as exc:
+ logger.exception("Error building public flow")
+ if isinstance(exc, HTTPException):
+ raise
+ raise HTTPException(status_code=500, detail=str(exc)) from exc
+ return {"job_id": job_id}
diff --git a/src/backend/base/langflow/api/v1/flows.py b/src/backend/base/langflow/api/v1/flows.py
index 3995788a6..8519f9be6 100644
--- a/src/backend/base/langflow/api/v1/flows.py
+++ b/src/backend/base/langflow/api/v1/flows.py
@@ -21,10 +21,11 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from langflow.api.utils import CurrentActiveUser, DbSession, cascade_delete_flow, remove_api_keys, validate_is_component
from langflow.api.v1.schemas import FlowListCreate
+from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
from langflow.initial_setup.constants import STARTER_FOLDER_NAME
from langflow.logging import logger
from langflow.services.database.models.flow import Flow, FlowCreate, FlowRead, FlowUpdate
-from langflow.services.database.models.flow.model import FlowHeader
+from langflow.services.database.models.flow.model import AccessTypeEnum, FlowHeader
from langflow.services.database.models.flow.utils import get_webhook_component_in_flow
from langflow.services.database.models.folder.constants import DEFAULT_FOLDER_NAME
from langflow.services.database.models.folder.model import Folder
@@ -278,6 +279,21 @@ async def read_flow(
raise HTTPException(status_code=404, detail="Flow not found")
+@router.get("/public_flow/{flow_id}", response_model=FlowRead, status_code=200)
+async def read_public_flow(
+ *,
+ session: DbSession,
+ flow_id: UUID,
+):
+ """Read a public flow."""
+ access_type = (await session.exec(select(Flow.access_type).where(Flow.id == flow_id))).first()
+ if access_type is not AccessTypeEnum.PUBLIC:
+ raise HTTPException(status_code=403, detail="Flow is not public")
+
+ current_user = await get_user_by_flow_id_or_endpoint_name(str(flow_id))
+ return await read_flow(session=session, flow_id=flow_id, current_user=current_user)
+
+
@router.patch("/{flow_id}", response_model=FlowRead, status_code=200)
async def update_flow(
*,
diff --git a/src/backend/base/langflow/api/v1/schemas.py b/src/backend/base/langflow/api/v1/schemas.py
index 777390a12..08e0ade10 100644
--- a/src/backend/base/langflow/api/v1/schemas.py
+++ b/src/backend/base/langflow/api/v1/schemas.py
@@ -4,7 +4,14 @@ from pathlib import Path
from typing import Any, Literal
from uuid import UUID
-from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator, model_serializer
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ field_serializer,
+ field_validator,
+ model_serializer,
+)
from langflow.graph.schema import RunOutputs
from langflow.schema import dotdict
@@ -377,6 +384,8 @@ class ConfigResponse(BaseModel):
health_check_max_retries: int
max_file_size_upload: int
webhook_polling_interval: int
+ public_flow_cleanup_interval: int
+ public_flow_expiration: int
event_delivery: Literal["polling", "streaming"]
diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py
index df79ac575..32a613a5d 100644
--- a/src/backend/base/langflow/graph/graph/base.py
+++ b/src/backend/base/langflow/graph/graph/base.py
@@ -341,6 +341,7 @@ class Graph:
self,
inputs: list[dict] | None = None,
max_iterations: int | None = None,
+ config: StartConfigDict | None = None,
event_manager: EventManager | None = None,
):
if not self._prepared:
@@ -348,6 +349,8 @@ class Graph:
raise ValueError(msg)
# The idea is for this to return a generator that yields the result of
# each step call and raise StopIteration when the graph is done
+ if config is not None:
+ self.__apply_config(config)
for _input in inputs or []:
for key, value in _input.items():
vertex = self.get_vertex(key)
diff --git a/src/backend/base/langflow/main.py b/src/backend/base/langflow/main.py
index 684564ae0..290284cbe 100644
--- a/src/backend/base/langflow/main.py
+++ b/src/backend/base/langflow/main.py
@@ -274,6 +274,7 @@ def create_app():
FastAPIInstrumentor.instrument_app(app)
add_pagination(app)
+
return app
diff --git a/src/backend/base/langflow/memory.py b/src/backend/base/langflow/memory.py
index cdb438fbe..66cfabd87 100644
--- a/src/backend/base/langflow/memory.py
+++ b/src/backend/base/langflow/memory.py
@@ -1,3 +1,4 @@
+import asyncio
import json
from collections.abc import Sequence
from uuid import UUID
@@ -154,9 +155,19 @@ async def aadd_messagetables(messages: list[MessageTable], session: AsyncSession
try:
for message in messages:
session.add(message)
- await session.commit()
+ try:
+ await session.commit()
+ # This is a hack.
+ # We are doing this because build_public_tmp causes the CancelledError to be raised
+ # while build_flow does not.
+ except asyncio.CancelledError:
+ await session.commit()
for message in messages:
await session.refresh(message)
+ except asyncio.CancelledError as e:
+ logger.exception(e)
+ error_msg = "Operation cancelled"
+ raise ValueError(error_msg) from e
except Exception as e:
logger.exception(e)
raise
diff --git a/src/backend/base/langflow/services/database/models/flow/model.py b/src/backend/base/langflow/services/database/models/flow/model.py
index 1dbbd54ec..13ec269df 100644
--- a/src/backend/base/langflow/services/database/models/flow/model.py
+++ b/src/backend/base/langflow/services/database/models/flow/model.py
@@ -2,6 +2,7 @@
import re
from datetime import datetime, timezone
+from enum import Enum
from typing import TYPE_CHECKING, Optional
from uuid import UUID, uuid4
@@ -15,7 +16,8 @@ from pydantic import (
field_serializer,
field_validator,
)
-from sqlalchemy import Text, UniqueConstraint
+from sqlalchemy import Enum as SQLEnum
+from sqlalchemy import Text, UniqueConstraint, text
from sqlmodel import JSON, Column, Field, Relationship, SQLModel
from langflow.schema import Data
@@ -30,6 +32,11 @@ if TYPE_CHECKING:
HEX_COLOR_LENGTH = 7
+class AccessTypeEnum(str, Enum):
+ PRIVATE = "private"
+ PUBLIC = "public"
+
+
class FlowBase(SQLModel):
name: str = Field(index=True)
description: str | None = Field(default=None, sa_column=Column(Text, index=True, nullable=True))
@@ -43,6 +50,18 @@ class FlowBase(SQLModel):
endpoint_name: str | None = Field(default=None, nullable=True, index=True)
tags: list[str] | None = None
locked: bool | None = Field(default=False, nullable=True)
+ access_type: AccessTypeEnum = Field(
+ default=AccessTypeEnum.PRIVATE,
+ sa_column=Column(
+ SQLEnum(
+ AccessTypeEnum,
+ name="access_type_enum",
+ values_callable=lambda enum: [member.value for member in enum],
+ ),
+ nullable=False,
+ server_default=text("'private'"),
+ ),
+ )
@field_validator("endpoint_name")
@classmethod
@@ -218,6 +237,7 @@ class FlowHeader(BaseModel):
endpoint_name: str | None = Field(None, description="The name of the endpoint associated with this flow")
description: str | None = Field(None, description="A description of the flow")
data: dict | None = Field(None, description="The data of the component, if is_component is True")
+ access_type: AccessTypeEnum | None = Field(None, description="The access type of the flow")
tags: list[str] | None = Field(None, description="The tags of the flow")
@field_validator("data", mode="before")
@@ -235,6 +255,7 @@ class FlowUpdate(SQLModel):
folder_id: UUID | None = None
endpoint_name: str | None = None
locked: bool | None = None
+ access_type: AccessTypeEnum | None = None
fs_path: str | None = None
@field_validator("endpoint_name")
diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py
index c97f37fab..c2e930f42 100644
--- a/src/backend/base/langflow/services/settings/base.py
+++ b/src/backend/base/langflow/services/settings/base.py
@@ -10,9 +10,14 @@ import orjson
import yaml
from aiofile import async_open
from loguru import logger
-from pydantic import field_validator
+from pydantic import Field, field_validator
from pydantic.fields import FieldInfo
-from pydantic_settings import BaseSettings, EnvSettingsSource, PydanticBaseSettingsSource, SettingsConfigDict
+from pydantic_settings import (
+ BaseSettings,
+ EnvSettingsSource,
+ PydanticBaseSettingsSource,
+ SettingsConfigDict,
+)
from typing_extensions import override
from langflow.services.settings.constants import VARIABLES_TO_GET_FROM_ENVIRONMENT
@@ -224,6 +229,13 @@ class Settings(BaseSettings):
mcp_server_enable_progress_notifications: bool = False
"""If set to False, Langflow will not send progress notifications in the MCP server."""
+ # Public Flow Settings
+ public_flow_cleanup_interval: int = Field(default=3600, gt=600)
+ """The interval in seconds at which public temporary flows will be cleaned up.
+ Default is 1 hour (3600 seconds). Minimum is 600 seconds (10 minutes)."""
+ public_flow_expiration: int = Field(default=86400, gt=600)
+ """The time in seconds after which a public temporary flow will be considered expired and eligible for cleanup.
+ Default is 24 hours (86400 seconds). Minimum is 600 seconds (10 minutes)."""
event_delivery: Literal["polling", "streaming"] = "polling"
"""How to deliver build events to the frontend. Can be 'polling' or 'streaming'."""
diff --git a/src/backend/base/langflow/services/storage/local.py b/src/backend/base/langflow/services/storage/local.py
index fed79b354..428593f81 100644
--- a/src/backend/base/langflow/services/storage/local.py
+++ b/src/backend/base/langflow/services/storage/local.py
@@ -11,7 +11,6 @@ class LocalStorageService(StorageService):
def __init__(self, session_service, settings_service) -> None:
"""Initialize the local storage service with session and settings services."""
super().__init__(session_service, settings_service)
- self.data_dir = anyio.Path(settings_service.settings.config_dir)
self.set_ready()
def build_full_path(self, flow_id: str, file_name: str) -> str:
diff --git a/src/backend/base/langflow/services/storage/service.py b/src/backend/base/langflow/services/storage/service.py
index e139b73c4..4cdd2696b 100644
--- a/src/backend/base/langflow/services/storage/service.py
+++ b/src/backend/base/langflow/services/storage/service.py
@@ -3,6 +3,8 @@ from __future__ import annotations
from abc import abstractmethod
from typing import TYPE_CHECKING
+import anyio
+
from langflow.services.base import Service
if TYPE_CHECKING:
@@ -16,6 +18,7 @@ class StorageService(Service):
def __init__(self, session_service: SessionService, settings_service: SettingsService):
self.settings_service = settings_service
self.session_service = session_service
+ self.data_dir: anyio.Path = anyio.Path(settings_service.settings.config_dir)
self.set_ready()
def build_full_path(self, flow_id: str, file_name: str) -> str:
diff --git a/src/backend/base/langflow/services/task/temp_flow_cleanup.py b/src/backend/base/langflow/services/task/temp_flow_cleanup.py
new file mode 100644
index 000000000..3f7317c10
--- /dev/null
+++ b/src/backend/base/langflow/services/task/temp_flow_cleanup.py
@@ -0,0 +1,136 @@
+from __future__ import annotations
+
+import asyncio
+import contextlib
+from typing import TYPE_CHECKING
+
+from loguru import logger
+from sqlmodel import col, delete, select
+
+from langflow.services.database.models.message.model import MessageTable
+from langflow.services.database.models.transactions.model import TransactionTable
+from langflow.services.database.models.vertex_builds.model import VertexBuildTable
+from langflow.services.deps import get_settings_service, get_storage_service, session_scope
+
+if TYPE_CHECKING:
+ from langflow.services.storage.service import StorageService
+
+
+async def cleanup_orphaned_records() -> None:
+ """Clean up all records that reference non-existent flows."""
+ from langflow.services.database.models.flow.model import Flow
+
+ async with session_scope() as session:
+ # Create a subquery of existing flow IDs
+ flow_ids_subquery = select(Flow.id)
+
+ # Tables that have flow_id foreign keys
+ tables: list[type[VertexBuildTable | MessageTable | TransactionTable]] = [
+ MessageTable,
+ VertexBuildTable,
+ TransactionTable,
+ ]
+
+ for table in tables:
+ try:
+ # Get distinct orphaned flow IDs from the table
+ orphaned_flow_ids = (
+ await session.exec(
+ select(col(table.flow_id).distinct()).where(col(table.flow_id).not_in(flow_ids_subquery))
+ )
+ ).all()
+
+ if orphaned_flow_ids:
+ logger.debug(f"Found {len(orphaned_flow_ids)} orphaned flow IDs in {table.__name__}")
+
+ # Delete all orphaned records in a single query
+ await session.exec(delete(table).where(col(table.flow_id).in_(orphaned_flow_ids)))
+
+ # Clean up any associated storage files
+ storage_service: StorageService = get_storage_service()
+ for flow_id in orphaned_flow_ids:
+ try:
+ files = await storage_service.list_files(str(flow_id))
+ for file in files:
+ try:
+ await storage_service.delete_file(str(flow_id), file)
+ except Exception as exc: # noqa: BLE001
+ logger.error(f"Failed to delete file {file} for flow {flow_id}: {exc!s}")
+ # Delete the flow directory after all files are deleted
+ flow_dir = storage_service.data_dir / str(flow_id)
+ if await flow_dir.exists():
+ await flow_dir.rmdir()
+ except Exception as exc: # noqa: BLE001
+ logger.error(f"Failed to list files for flow {flow_id}: {exc!s}")
+
+ await session.commit()
+ logger.debug(f"Successfully deleted orphaned records from {table.__name__}")
+
+ except Exception as exc: # noqa: BLE001
+ logger.error(f"Error cleaning up orphaned records in {table.__name__}: {exc!s}")
+ await session.rollback()
+
+
+class CleanupWorker:
+ def __init__(self) -> None:
+ self._stop_event = asyncio.Event()
+ self._task: asyncio.Task | None = None
+
+ async def start(self):
+ """Start the cleanup worker."""
+ if self._task is not None:
+ logger.warning("Cleanup worker is already running")
+ return
+
+ self._task = asyncio.create_task(self._run())
+ logger.debug("Started database cleanup worker")
+
+ async def stop(self):
+ """Stop the cleanup worker gracefully."""
+ if self._task is None:
+ logger.warning("Cleanup worker is not running")
+ return
+
+ logger.debug("Stopping database cleanup worker...")
+ self._stop_event.set()
+ await self._task
+ self._task = None
+ logger.debug("Database cleanup worker stopped")
+
+ async def _run(self):
+ """Run the cleanup worker until stopped."""
+ settings = get_settings_service().settings
+ while not self._stop_event.is_set():
+ try:
+ # Clean up any orphaned records
+ await cleanup_orphaned_records()
+ except Exception as exc: # noqa: BLE001
+ logger.error(f"Error in cleanup worker: {exc!s}")
+
+ try:
+ # Create a task for the timeout
+ sleep_task = asyncio.create_task(asyncio.sleep(settings.public_flow_cleanup_interval))
+ # Create a task for the stop event
+ stop_task = asyncio.create_task(self._stop_event.wait())
+
+ # Wait for either the timeout or the stop event
+ done, pending = await asyncio.wait([sleep_task, stop_task], return_when=asyncio.FIRST_COMPLETED)
+
+ # Cancel any pending tasks
+ for task in pending:
+ task.cancel()
+ with contextlib.suppress(asyncio.CancelledError):
+ await task
+
+ # If the stop event completed, break the loop
+ if stop_task in done:
+ break
+
+ except Exception as exc: # noqa: BLE001
+ logger.error(f"Error in cleanup worker sleep: {exc!s}")
+ # Sleep a minimum amount in case of errors
+ await asyncio.sleep(60)
+
+
+# Create a global instance of the worker
+cleanup_worker = CleanupWorker()
diff --git a/src/backend/tests/unit/services/tasks/__init__.py b/src/backend/tests/unit/services/tasks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/backend/tests/unit/services/tasks/test_temp_flow_cleanup.py b/src/backend/tests/unit/services/tasks/test_temp_flow_cleanup.py
new file mode 100644
index 000000000..5b1b0a8c9
--- /dev/null
+++ b/src/backend/tests/unit/services/tasks/test_temp_flow_cleanup.py
@@ -0,0 +1,109 @@
+from __future__ import annotations
+
+import datetime
+from datetime import timezone
+from uuid import uuid4
+
+import pytest
+from langflow.services.database.models.flow import Flow as FlowTable
+from langflow.services.database.models.message.model import MessageTable
+from langflow.services.deps import get_settings_service, get_storage_service, session_scope
+from langflow.services.task.temp_flow_cleanup import (
+ CleanupWorker,
+ cleanup_orphaned_records,
+)
+
+
+@pytest.mark.usefixtures("client")
+async def test_cleanup_orphaned_records_no_orphans():
+ """Test cleanup when there are no orphaned records."""
+ storage_service = get_storage_service()
+ flow_id = uuid4()
+
+ async with session_scope() as session:
+ # Create a flow and associated message
+ flow = FlowTable(
+ id=flow_id,
+ name="Test Flow",
+ data="null",
+ updated_at=datetime.datetime.now(timezone.utc),
+ )
+ message = MessageTable(
+ id=uuid4(),
+ flow_id=flow_id,
+ sender="test_user",
+ sender_name="Test User",
+ timestamp=datetime.datetime.now(timezone.utc),
+ session_id=str(uuid4()),
+ )
+ session.add(flow)
+ session.add(message)
+ await session.commit()
+
+ # Write a file for the flow
+ await storage_service.save_file(str(flow_id), "test.json", b"test data")
+
+ # Run cleanup
+ async with session_scope() as session:
+ await cleanup_orphaned_records()
+
+ # Verify message still exists
+ async with session_scope() as session:
+ message = await session.get(MessageTable, message.id)
+ assert message is not None
+
+
+@pytest.mark.usefixtures("client")
+async def test_cleanup_orphaned_records_with_orphans():
+ """Test cleanup when there are orphaned records."""
+ orphaned_flow_id = uuid4()
+
+ async with session_scope() as session:
+ # Create orphaned records without an associated flow
+ message = MessageTable(
+ id=uuid4(),
+ flow_id=orphaned_flow_id,
+ sender="test_user",
+ sender_name="Test User",
+ timestamp=datetime.datetime.now(timezone.utc),
+ session_id=str(uuid4()),
+ )
+ session.add(message)
+ await session.commit()
+
+ # Run cleanup
+ async with session_scope() as session:
+ await cleanup_orphaned_records()
+
+ # Verify orphaned message was deleted
+ async with session_scope() as session:
+ message = await session.get(MessageTable, message.id)
+ assert message is None
+
+
+@pytest.mark.asyncio
+async def test_cleanup_worker_start_stop():
+ """Test CleanupWorker start and stop functionality."""
+ worker = CleanupWorker()
+ await worker.start()
+ assert worker._task is not None
+ assert not worker._stop_event.is_set()
+ await worker.stop()
+ assert worker._task is None
+ assert worker._stop_event.is_set()
+
+
+@pytest.mark.asyncio
+async def test_cleanup_worker_run_with_exception(caplog):
+ """Test CleanupWorker handles exceptions gracefully."""
+ settings = get_settings_service().settings
+ settings.public_flow_cleanup_interval = 601 # Minimum valid interval
+ worker = CleanupWorker()
+
+ # Start worker and let it run briefly
+ await worker.start()
+ await worker.stop()
+
+ # Check logs for expected messages
+ assert any("Started database cleanup worker" in record.message for record in caplog.records)
+ assert any("Stopping database cleanup worker" in record.message for record in caplog.records)
diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json
index 44a4d1e1f..a6ae05e95 100644
--- a/src/frontend/package-lock.json
+++ b/src/frontend/package-lock.json
@@ -862,246 +862,6 @@
"integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
"peer": true
},
- "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"
- ],
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@esbuild/linux-x64": {
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
@@ -1117,96 +877,6 @@
"node": ">=12"
}
},
- "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"
- ],
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "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"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.0.tgz",
@@ -1768,246 +1438,6 @@
"node": ">=6.9.0"
}
},
- "node_modules/@million/lint/node_modules/@esbuild/aix-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
- "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/android-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
- "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/android-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
- "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/android-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
- "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/darwin-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
- "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/darwin-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
- "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/freebsd-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
- "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/freebsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
- "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
- "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
- "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
- "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-loong64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
- "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
- "cpu": [
- "loong64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-mips64el": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
- "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
- "cpu": [
- "mips64el"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
- "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-riscv64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
- "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/linux-s390x": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
- "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@million/lint/node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
@@ -2023,96 +1453,6 @@
"node": ">=12"
}
},
- "node_modules/@million/lint/node_modules/@esbuild/netbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
- "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/openbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
- "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/sunos-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
- "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/win32-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
- "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/win32-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
- "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@million/lint/node_modules/@esbuild/win32-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
- "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@million/lint/node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
@@ -2206,171 +1546,6 @@
"@napi-rs/nice-win32-x64-msvc": "1.0.1"
}
},
- "node_modules/@napi-rs/nice-android-arm-eabi": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.1.tgz",
- "integrity": "sha512-5qpvOu5IGwDo7MEKVqqyAxF90I6aLj4n07OzpARdgDRfz8UbBztTByBp0RC59r3J1Ij8uzYi6jI7r5Lws7nn6w==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-android-arm64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.1.tgz",
- "integrity": "sha512-GqvXL0P8fZ+mQqG1g0o4AO9hJjQaeYG84FRfZaYjyJtZZZcMjXW5TwkL8Y8UApheJgyE13TQ4YNUssQaTgTyvA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-darwin-arm64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.1.tgz",
- "integrity": "sha512-91k3HEqUl2fsrz/sKkuEkscj6EAj3/eZNCLqzD2AA0TtVbkQi8nqxZCZDMkfklULmxLkMxuUdKe7RvG/T6s2AA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-darwin-x64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.1.tgz",
- "integrity": "sha512-jXnMleYSIR/+TAN/p5u+NkCA7yidgswx5ftqzXdD5wgy/hNR92oerTXHc0jrlBisbd7DpzoaGY4cFD7Sm5GlgQ==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-freebsd-x64": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.1.tgz",
- "integrity": "sha512-j+iJ/ezONXRQsVIB/FJfwjeQXX7A2tf3gEXs4WUGFrJjpe/z2KB7sOv6zpkm08PofF36C9S7wTNuzHZ/Iiccfw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-arm-gnueabihf": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.1.tgz",
- "integrity": "sha512-G8RgJ8FYXYkkSGQwywAUh84m946UTn6l03/vmEXBYNJxQJcD+I3B3k5jmjFG/OPiU8DfvxutOP8bi+F89MCV7Q==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-arm64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.1.tgz",
- "integrity": "sha512-IMDak59/W5JSab1oZvmNbrms3mHqcreaCeClUjwlwDr0m3BoR09ZiN8cKFBzuSlXgRdZ4PNqCYNeGQv7YMTjuA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-arm64-musl": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.1.tgz",
- "integrity": "sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-ppc64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.1.tgz",
- "integrity": "sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-riscv64-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.1.tgz",
- "integrity": "sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-linux-s390x-gnu": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.1.tgz",
- "integrity": "sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@napi-rs/nice-linux-x64-gnu": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.0.1.tgz",
@@ -2401,51 +1576,6 @@
"node": ">= 10"
}
},
- "node_modules/@napi-rs/nice-win32-arm64-msvc": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.1.tgz",
- "integrity": "sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-win32-ia32-msvc": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.1.tgz",
- "integrity": "sha512-t7eBAyPUrWL8su3gDxw9xxxqNwZzAqKo0Szv3IjVQd1GpXXVkb6vBBQUuxfIYaXMzZLwlxRQ7uzM2vdUE9ULGw==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@napi-rs/nice-win32-x64-msvc": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.1.tgz",
- "integrity": "sha512-JlF+uDcatt3St2ntBG8H02F1mM45i5SF9W+bIKiReVE6wiy3o16oBP/yxt+RZ+N6LbCImJXJ6bXNO2kn9AXicg==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
- },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3906,174 +3036,6 @@
}
}
},
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz",
- "integrity": "sha512-uYQ2WfPaqz5QtVgMxfN6NpLD+no0MYHDBywl7itPYd3K5TjjSghNKmX8ic9S8NU8w81NVhJv/XojcHptRly7qQ==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.35.0.tgz",
- "integrity": "sha512-FtKddj9XZudurLhdJnBl9fl6BwCJ3ky8riCXjEw3/UIbjmIY58ppWwPEvU3fNu+W7FUsAsB1CdH+7EQE6CXAPA==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.35.0.tgz",
- "integrity": "sha512-Uk+GjOJR6CY844/q6r5DR/6lkPFOw0hjfOIzVx22THJXMxktXG6CbejseJFznU8vHcEBLpiXKY3/6xc+cBm65Q==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.35.0.tgz",
- "integrity": "sha512-3IrHjfAS6Vkp+5bISNQnPogRAW5GAV1n+bNCrDwXmfMHbPl5EhTmWtfmwlJxFRUCBZ+tZ/OxDyU08aF6NI/N5Q==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.35.0.tgz",
- "integrity": "sha512-sxjoD/6F9cDLSELuLNnY0fOrM9WA0KrM0vWm57XhrIMf5FGiN8D0l7fn+bpUeBSU7dCgPV2oX4zHAsAXyHFGcQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.35.0.tgz",
- "integrity": "sha512-2mpHCeRuD1u/2kruUiHSsnjWtHjqVbzhBkNVQ1aVD63CcexKVcQGwJ2g5VphOd84GvxfSvnnlEyBtQCE5hxVVw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.35.0.tgz",
- "integrity": "sha512-mrA0v3QMy6ZSvEuLs0dMxcO2LnaCONs1Z73GUDBHWbY8tFFocM6yl7YyMu7rz4zS81NDSqhrUuolyZXGi8TEqg==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.35.0.tgz",
- "integrity": "sha512-DnYhhzcvTAKNexIql8pFajr0PiDGrIsBYPRvCKlA5ixSS3uwo/CWNZxB09jhIapEIg945KOzcYEAGGSmTSpk7A==",
- "cpu": [
- "arm"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.35.0.tgz",
- "integrity": "sha512-uagpnH2M2g2b5iLsCTZ35CL1FgyuzzJQ8L9VtlJ+FckBXroTwNOaD0z0/UF+k5K3aNQjbm8LIVpxykUOQt1m/A==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.35.0.tgz",
- "integrity": "sha512-XQxVOCd6VJeHQA/7YcqyV0/88N6ysSVzRjJ9I9UA/xXpEsjvAgDTgH3wQYz5bmr7SPtVK2TsP2fQ2N9L4ukoUg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.35.0.tgz",
- "integrity": "sha512-5pMT5PzfgwcXEwOaSrqVsz/LvjDZt+vQ8RT/70yhPU06PTuq8WaHhfT1LW+cdD7mW6i/J5/XIkX/1tCAkh1W6g==",
- "cpu": [
- "loong64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.35.0.tgz",
- "integrity": "sha512-c+zkcvbhbXF98f4CtEIP1EBA/lCic5xB0lToneZYvMeKu5Kamq3O8gqrxiYYLzlZH6E3Aq+TSW86E4ay8iD8EA==",
- "cpu": [
- "ppc64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.35.0.tgz",
- "integrity": "sha512-s91fuAHdOwH/Tad2tzTtPX7UZyytHIRR6V4+2IGlV0Cej5rkG0R61SX4l4y9sh0JBibMiploZx3oHKPnQBKe4g==",
- "cpu": [
- "riscv64"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.35.0.tgz",
- "integrity": "sha512-hQRkPQPLYJZYGP+Hj4fR9dDBMIM7zrzJDWFEMPdTnTy95Ljnv0/4w/ixFw3pTBMEuuEuoqtBINYND4M7ujcuQw==",
- "cpu": [
- "s390x"
- ],
- "optional": true,
- "os": [
- "linux"
- ]
- },
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.35.0.tgz",
@@ -4098,42 +3060,6 @@
"linux"
]
},
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.35.0.tgz",
- "integrity": "sha512-OUOlGqPkVJCdJETKOCEf1mw848ZyJ5w50/rZ/3IBQVdLfR5jk/6Sr5m3iO2tdPgwo0x7VcncYuOvMhBWZq8ayg==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.35.0.tgz",
- "integrity": "sha512-2/lsgejMrtwQe44glq7AFFHLfJBPafpsTa6JvP2NGef/ifOa4KBoglVf7AKN7EV9o32evBPRqfg96fEHzWo5kw==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.35.0",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.35.0.tgz",
- "integrity": "sha512-PIQeY5XDkrOysbQblSW7v3l1MDZzkTEzAfTPkj5VAu3FW8fS4ynyLg2sINp0fp3SjZ8xkRYpLqoKcYqAkhU1dw==",
- "cpu": [
- "x64"
- ],
- "optional": true,
- "os": [
- "win32"
- ]
- },
"node_modules/@rrweb/types": {
"version": "2.0.0-alpha.16",
"resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.0.0-alpha.16.tgz",
@@ -4494,86 +3420,6 @@
}
}
},
- "node_modules/@swc/core-darwin-arm64": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.8.tgz",
- "integrity": "sha512-rrSsunyJWpHN+5V1zumndwSSifmIeFQBK9i2RMQQp15PgbgUNxHK5qoET1n20pcUrmZeT6jmJaEWlQchkV//Og==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-darwin-x64": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.11.8.tgz",
- "integrity": "sha512-44goLqQuuo0HgWnG8qC+ZFw/qnjCVVeqffhzFr9WAXXotogVaxM8ze6egE58VWrfEc8me8yCcxOYL9RbtjhS/Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-linux-arm-gnueabihf": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.8.tgz",
- "integrity": "sha512-Mzo8umKlhTWwF1v8SLuTM1z2A+P43UVhf4R8RZDhzIRBuB2NkeyE+c0gexIOJBuGSIATryuAF4O4luDu727D1w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-linux-arm64-gnu": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.8.tgz",
- "integrity": "sha512-EyhO6U+QdoGYC1MeHOR0pyaaSaKYyNuT4FQNZ1eZIbnuueXpuICC7iNmLIOfr3LE5bVWcZ7NKGVPlM2StJEcgA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-linux-arm64-musl": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.8.tgz",
- "integrity": "sha512-QU6wOkZnS6/QuBN1MHD6G2BgFxB0AclvTVGbqYkRA7MsVkcC29PffESqzTXnypzB252/XkhQjoB2JIt9rPYf6A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.11.8",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.8.tgz",
@@ -4606,54 +3452,6 @@
"node": ">=10"
}
},
- "node_modules/@swc/core-win32-arm64-msvc": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.8.tgz",
- "integrity": "sha512-EbjOzQ+B85rumHyeesBYxZ+hq3ZQn+YAAT1ZNE9xW1/8SuLoBmHy/K9YniRGVDq/2NRmp5kI5+5h5TX0asIS9A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-win32-ia32-msvc": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.8.tgz",
- "integrity": "sha512-Z+FF5kgLHfQWIZ1KPdeInToXLzbY0sMAashjd/igKeP1Lz0qKXVAK+rpn6ASJi85Fn8wTftCGCyQUkRVn0bTDg==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@swc/core-win32-x64-msvc": {
- "version": "1.11.8",
- "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.8.tgz",
- "integrity": "sha512-j6B6N0hChCeAISS6xp/hh6zR5CSCr037BAjCxNLsT8TGe5D+gYZ57heswUWXRH8eMKiRDGiLCYpPB2pkTqxCSw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
@@ -8208,19 +7006,6 @@
"optional": true,
"peer": true
},
- "node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -15237,19 +14022,6 @@
}
}
},
- "node_modules/vite/node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz",
diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index 9c7a659e5..a30557cdc 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -1,10 +1,19 @@
import "@xyflow/react/dist/style.css";
-import { Suspense } from "react";
+import { Suspense, useEffect } from "react";
import { RouterProvider } from "react-router-dom";
import { LoadingPage } from "./pages/LoadingPage";
import router from "./routes";
+import { useDarkStore } from "./stores/darkStore";
export default function App() {
+ const dark = useDarkStore((state) => state.dark);
+ useEffect(() => {
+ if (!dark) {
+ document.getElementById("body")!.classList.remove("dark");
+ } else {
+ document.getElementById("body")!.classList.add("dark");
+ }
+ }, [dark]);
return (