From 48700dd07f14bcfd8b206dc3b2e2795d5531094d Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 23 Aug 2023 05:48:55 +0300 Subject: [PATCH 01/67] [ENH] CIP-2: Auth Providers Proposal (#986) ## Description of changes *Summarize the changes made by this PR.* - New functionality - Auth Provide Client and Server Side Abstractions - Basic Auth Provider ## Test plan Unit tests for authorized endpoints ## Documentation Changes Docs should change to describe how to use auth providers on the client and server. CIP added in `docs/` --- chromadb/api/fastapi.py | 47 +++- chromadb/auth/__init__.py | 207 ++++++++++++++ chromadb/auth/basic/__init__.py | 96 +++++++ chromadb/auth/fastapi.py | 121 ++++++++ chromadb/auth/providers.py | 171 ++++++++++++ chromadb/auth/registry.py | 107 ++++++++ chromadb/auth/token/__init__.py | 0 chromadb/config.py | 76 ++++- chromadb/server/fastapi/__init__.py | 11 +- chromadb/test/auth/test_basic_auth.py | 12 + chromadb/test/conftest.py | 185 +++++++++++-- chromadb/test/test_api.py | 1 + chromadb/utils/__init__.py | 12 + docker-compose.yml | 2 + docs/CIP_2_Auth_Providers_Proposal.md | 190 +++++++++++++ docs/assets/cip-2-arch.png | Bin 0 -> 46879 bytes docs/assets/cip-2-client-side-wf.png | Bin 0 -> 91473 bytes docs/assets/cip-2-seq.png | Bin 0 -> 161310 bytes docs/assets/cip-2-server-side-wf.png | Bin 0 -> 116941 bytes .../assets/auh-sequence.png | Bin 0 -> 139351 bytes .../assets/auth-architecture.png | Bin 0 -> 32974 bytes .../basic_functionality/client_auth.ipynb | 259 ++++++++++++++++++ pyproject.toml | 3 +- requirements.txt | 1 + 24 files changed, 1458 insertions(+), 43 deletions(-) create mode 100644 chromadb/auth/__init__.py create mode 100644 chromadb/auth/basic/__init__.py create mode 100644 chromadb/auth/fastapi.py create mode 100644 chromadb/auth/providers.py create mode 100644 chromadb/auth/registry.py create mode 100644 chromadb/auth/token/__init__.py create mode 100644 chromadb/test/auth/test_basic_auth.py create mode 100644 docs/CIP_2_Auth_Providers_Proposal.md create mode 100644 docs/assets/cip-2-arch.png create mode 100644 docs/assets/cip-2-client-side-wf.png create mode 100644 docs/assets/cip-2-seq.png create mode 100644 docs/assets/cip-2-server-side-wf.png create mode 100644 examples/basic_functionality/assets/auh-sequence.png create mode 100644 examples/basic_functionality/assets/auth-architecture.png create mode 100644 examples/basic_functionality/client_auth.ipynb diff --git a/chromadb/api/fastapi.py b/chromadb/api/fastapi.py index bb191cd59f33..5551b051370d 100644 --- a/chromadb/api/fastapi.py +++ b/chromadb/api/fastapi.py @@ -1,6 +1,15 @@ +import json from typing import Optional, cast +from typing import Sequence +from uuid import UUID + +import requests +from overrides import override + +import chromadb.errors as errors +import chromadb.utils.embedding_functions as ef from chromadb.api import API -from chromadb.config import Settings, System +from chromadb.api.models.Collection import Collection from chromadb.api.types import ( Documents, Embeddings, @@ -14,15 +23,13 @@ QueryResult, CollectionMetadata, ) -import chromadb.utils.embedding_functions as ef -import requests -import json -from typing import Sequence -from chromadb.api.models.Collection import Collection -import chromadb.errors as errors -from uuid import UUID +from chromadb.auth import ( + ClientAuthProvider, +) +from chromadb.auth.providers import RequestsClientAuthProtocolAdapter +from chromadb.auth.registry import resolve_provider +from chromadb.config import Settings, System from chromadb.telemetry import Telemetry -from overrides import override class FastAPI(API): @@ -47,7 +54,27 @@ def __init__(self, system: System): ) self._header = system.settings.chroma_server_headers - self._session = requests.Session() + if ( + system.settings.chroma_client_auth_provider + and system.settings.chroma_client_auth_protocol_adapter + ): + self._auth_provider = self.require( + resolve_provider( + system.settings.chroma_client_auth_provider, ClientAuthProvider + ) + ) + self._adapter = cast( + RequestsClientAuthProtocolAdapter, + system.require( + resolve_provider( + system.settings.chroma_client_auth_protocol_adapter, + RequestsClientAuthProtocolAdapter, + ) + ), + ) + self._session = self._adapter.session + else: + self._session = requests.Session() if self._header is not None: self._session.headers.update(self._header) diff --git a/chromadb/auth/__init__.py b/chromadb/auth/__init__.py new file mode 100644 index 000000000000..67f296bb35cc --- /dev/null +++ b/chromadb/auth/__init__.py @@ -0,0 +1,207 @@ +""" +Contains only Auth abstractions, no implementations. +""" +import base64 +import logging +from abc import ABC, abstractmethod +from enum import Enum +from typing import ( + Optional, + Dict, + TypeVar, + Tuple, + Generic, +) + +from overrides import EnforceOverrides, override +from pydantic import SecretStr + +from chromadb.config import ( + Component, + System, +) +from chromadb.errors import ChromaError + +logger = logging.getLogger(__name__) + +T = TypeVar("T") +S = TypeVar("S") + + +class AuthInfoType(Enum): + COOKIE = "cookie" + HEADER = "header" + URL = "url" + METADATA = "metadata" # gRPC + + +class ClientAuthResponse(EnforceOverrides, ABC): + @abstractmethod + def get_auth_info_type(self) -> AuthInfoType: + ... + + @abstractmethod + def get_auth_info(self) -> Tuple[str, SecretStr]: + ... + + +class ClientAuthProvider(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def authenticate(self) -> ClientAuthResponse: + pass + + +class ClientAuthConfigurationProvider(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def get_configuration(self) -> Optional[T]: + pass + + +class ClientAuthCredentialsProvider(Component, Generic[T]): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def get_credentials(self) -> T: + pass + + +class ClientAuthProtocolAdapter(Component, Generic[T]): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def inject_credentials(self, injection_context: T) -> None: + pass + + +# SERVER-SIDE Abstractions + + +class ServerAuthenticationRequest(EnforceOverrides, ABC, Generic[T]): + @abstractmethod + def get_auth_info( + self, auth_info_type: AuthInfoType, auth_info_id: Optional[str] = None + ) -> T: + """ + This method should return the necessary auth info based on the type of authentication (e.g. header, cookie, url) + and a given id for the respective auth type (e.g. name of the header, cookie, url param). + + :param auth_info_type: The type of auth info to return + :param auth_info_id: The id of the auth info to return + :return: The auth info which can be specific to the implementation + """ + pass + + +class ServerAuthenticationResponse(EnforceOverrides, ABC): + def success(self) -> bool: + raise NotImplementedError() + + +class ServerAuthProvider(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def authenticate(self, request: ServerAuthenticationRequest[T]) -> bool: + pass + + +class ChromaAuthMiddleware(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def authenticate( + self, request: ServerAuthenticationRequest[T] + ) -> Optional[ServerAuthenticationResponse]: + ... + + @abstractmethod + def ignore_operation(self, verb: str, path: str) -> bool: + ... + + @abstractmethod + def instrument_server(self, app: T) -> None: + ... + + +class ServerAuthConfigurationProvider(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def get_configuration(self) -> Optional[T]: + pass + + +class AuthenticationError(ChromaError): + @override + def code(self) -> int: + return 401 + + @classmethod + @override + def name(cls) -> str: + return "AuthenticationError" + + +class AbstractCredentials(EnforceOverrides, ABC, Generic[T]): + """ + The class is used by Auth Providers to encapsulate credentials received from the server + and pass them to a ServerAuthCredentialsProvider. + """ + + @abstractmethod + def get_credentials(self) -> Dict[str, T]: + """ + Returns the data encapsulated by the credentials object. + """ + pass + + +class SecretStrAbstractCredentials(AbstractCredentials[SecretStr]): + @abstractmethod + @override + def get_credentials(self) -> Dict[str, SecretStr]: + """ + Returns the data encapsulated by the credentials object. + """ + pass + + +class BasicAuthCredentials(SecretStrAbstractCredentials): + def __init__(self, username: SecretStr, password: SecretStr) -> None: + self.username = username + self.password = password + + @override + def get_credentials(self) -> Dict[str, SecretStr]: + return {"username": self.username, "password": self.password} + + @staticmethod + def from_header(header: str) -> "BasicAuthCredentials": + """ + Parses a basic auth header and returns a BasicAuthCredentials object. + """ + header = header.replace("Basic ", "") + header = header.strip() + base64_decoded = base64.b64decode(header).decode("utf-8") + username, password = base64_decoded.split(":") + return BasicAuthCredentials(SecretStr(username), SecretStr(password)) + + +class ServerAuthCredentialsProvider(Component): + def __init__(self, system: System) -> None: + super().__init__(system) + + @abstractmethod + def validate_credentials(self, credentials: AbstractCredentials[T]) -> bool: + pass diff --git a/chromadb/auth/basic/__init__.py b/chromadb/auth/basic/__init__.py new file mode 100644 index 000000000000..a03d195e8aed --- /dev/null +++ b/chromadb/auth/basic/__init__.py @@ -0,0 +1,96 @@ +import base64 +import logging +from typing import Tuple, Any, cast + +from overrides import override +from pydantic import SecretStr + +from chromadb.auth import ( + ServerAuthProvider, + ClientAuthProvider, + ServerAuthenticationRequest, + ServerAuthCredentialsProvider, + AuthInfoType, + BasicAuthCredentials, + ClientAuthCredentialsProvider, + ClientAuthResponse, +) +from chromadb.auth.registry import register_provider, resolve_provider +from chromadb.config import System +from chromadb.utils import get_class + +logger = logging.getLogger(__name__) + +__all__ = ["BasicAuthServerProvider", "BasicAuthClientProvider"] + + +class BasicAuthClientAuthResponse(ClientAuthResponse): + def __init__(self, credentials: SecretStr) -> None: + self._credentials = credentials + + @override + def get_auth_info_type(self) -> AuthInfoType: + return AuthInfoType.HEADER + + @override + def get_auth_info(self) -> Tuple[str, SecretStr]: + return "Authorization", SecretStr( + f"Basic {self._credentials.get_secret_value()}" + ) + + +@register_provider("basic") +class BasicAuthClientProvider(ClientAuthProvider): + _credentials_provider: ClientAuthCredentialsProvider[Any] + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + system.settings.require("chroma_client_auth_credentials_provider") + self._credentials_provider = system.require( + get_class( + str(system.settings.chroma_client_auth_credentials_provider), + ClientAuthCredentialsProvider, + ) + ) + + @override + def authenticate(self) -> ClientAuthResponse: + _creds = self._credentials_provider.get_credentials() + return BasicAuthClientAuthResponse( + SecretStr( + base64.b64encode(f"{_creds.get_secret_value()}".encode("utf-8")).decode( + "utf-8" + ) + ) + ) + + +@register_provider("basic") +class BasicAuthServerProvider(ServerAuthProvider): + _credentials_provider: ServerAuthCredentialsProvider + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + system.settings.require("chroma_server_auth_credentials_provider") + self._credentials_provider = cast( + ServerAuthCredentialsProvider, + system.require( + resolve_provider( + str(system.settings.chroma_server_auth_credentials_provider), + ServerAuthCredentialsProvider, + ) + ), + ) + + @override + def authenticate(self, request: ServerAuthenticationRequest[Any]) -> bool: + try: + _auth_header = request.get_auth_info(AuthInfoType.HEADER, "Authorization") + return self._credentials_provider.validate_credentials( + BasicAuthCredentials.from_header(_auth_header) + ) + except Exception as e: + logger.error(f"BasicAuthServerProvider.authenticate failed: {repr(e)}") + return False diff --git a/chromadb/auth/fastapi.py b/chromadb/auth/fastapi.py new file mode 100644 index 000000000000..a488ef5f2b36 --- /dev/null +++ b/chromadb/auth/fastapi.py @@ -0,0 +1,121 @@ +# FAST API code +import logging +from typing import Optional, Dict, List, cast, Any + +from overrides import override +from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint +from starlette.requests import Request +from starlette.responses import Response, JSONResponse +from starlette.types import ASGIApp + +from chromadb.config import System +from chromadb.auth import ( + ServerAuthenticationRequest, + AuthInfoType, + ServerAuthenticationResponse, + ServerAuthProvider, + ChromaAuthMiddleware, +) +from chromadb.auth.registry import resolve_provider + +logger = logging.getLogger(__name__) + + +class FastAPIServerAuthenticationRequest(ServerAuthenticationRequest[Optional[str]]): + def __init__(self, request: Request) -> None: + self._request = request + + @override + def get_auth_info( + self, auth_info_type: AuthInfoType, auth_info_id: Optional[str] = None + ) -> Optional[str]: + if auth_info_type == AuthInfoType.HEADER: + return str(self._request.headers[auth_info_id]) + elif auth_info_type == AuthInfoType.COOKIE: + return str(self._request.cookies[auth_info_id]) + elif auth_info_type == AuthInfoType.URL: + return str(self._request.query_params[auth_info_id]) + elif auth_info_type == AuthInfoType.METADATA: + raise ValueError("Metadata not supported for FastAPI") + else: + raise ValueError(f"Unknown auth info type: {auth_info_type}") + + +class FastAPIServerAuthenticationResponse(ServerAuthenticationResponse): + _auth_success: bool + + def __init__(self, auth_success: bool) -> None: + self._auth_success = auth_success + + @override + def success(self) -> bool: + return self._auth_success + + +class FastAPIChromaAuthMiddleware(ChromaAuthMiddleware): + _auth_provider: ServerAuthProvider + + def __init__(self, system: System) -> None: + super().__init__(system) + self._system = system + self._settings = system.settings + self._settings.require("chroma_server_auth_provider") + self._ignore_auth_paths: Dict[ + str, List[str] + ] = self._settings.chroma_server_auth_ignore_paths + if self._settings.chroma_server_auth_provider: + logger.debug( + f"Server Auth Provider: {self._settings.chroma_server_auth_provider}" + ) + _cls = resolve_provider( + self._settings.chroma_server_auth_provider, ServerAuthProvider + ) + self._auth_provider = cast(ServerAuthProvider, self.require(_cls)) + + @override + def authenticate( + self, request: ServerAuthenticationRequest[Any] + ) -> Optional[ServerAuthenticationResponse]: + return FastAPIServerAuthenticationResponse( + self._auth_provider.authenticate(request) + ) + + @override + def ignore_operation(self, verb: str, path: str) -> bool: + if ( + path in self._ignore_auth_paths.keys() + and verb.upper() in self._ignore_auth_paths[path] + ): + logger.debug(f"Skipping auth for path {path} and method {verb}") + return True + return False + + @override + def instrument_server(self, app: ASGIApp) -> None: + # We can potentially add an `/auth` endpoint to the server to allow for more complex auth flows + return + + +class FastAPIChromaAuthMiddlewareWrapper(BaseHTTPMiddleware): # type: ignore + def __init__( + self, app: ASGIApp, auth_middleware: FastAPIChromaAuthMiddleware + ) -> None: + super().__init__(app) + self._middleware = auth_middleware + self._middleware.instrument_server(app) + + @override + async def dispatch( + self, request: Request, call_next: RequestResponseEndpoint + ) -> Response: + if self._middleware.ignore_operation(request.method, request.url.path): + logger.debug( + f"Skipping auth for path {request.url.path} and method {request.method}" + ) + return await call_next(request) + response = self._middleware.authenticate( + FastAPIServerAuthenticationRequest(request) + ) + if not response or not response.success(): + return JSONResponse({"error": "Unauthorized"}, status_code=401) + return await call_next(request) diff --git a/chromadb/auth/providers.py b/chromadb/auth/providers.py new file mode 100644 index 000000000000..a3bb23616e28 --- /dev/null +++ b/chromadb/auth/providers.py @@ -0,0 +1,171 @@ +import importlib +import logging +from typing import cast, Dict, TypeVar, Any + +import requests +from overrides import override +from pydantic import SecretStr + +from chromadb.auth import ( + ServerAuthCredentialsProvider, + AbstractCredentials, + ClientAuthCredentialsProvider, + AuthInfoType, + ClientAuthProvider, + ClientAuthProtocolAdapter, +) +from chromadb.auth.registry import register_provider, resolve_provider +from chromadb.config import System + +T = TypeVar("T") + +logger = logging.getLogger(__name__) + + +class HtpasswdServerAuthCredentialsProvider(ServerAuthCredentialsProvider): + _creds: Dict[str, SecretStr] + + def __init__(self, system: System) -> None: + super().__init__(system) + try: + # Equivalent to import onnxruntime + self.bc = importlib.import_module("bcrypt") + except ImportError: + raise ValueError( + "The bcrypt python package is not installed. Please install it with `pip install bcrypt`" + ) + + @override + def validate_credentials(self, credentials: AbstractCredentials[T]) -> bool: + _creds = cast(Dict[str, SecretStr], credentials.get_credentials()) + if len(_creds) != 2: + logger.error( + "Returned credentials did match expected format: dict[username:SecretStr, password: SecretStr]" + ) + return False + if "username" not in _creds or "password" not in _creds: + logger.error("Returned credentials do not contain username or password") + return False + _usr_check = bool( + _creds["username"].get_secret_value() + == self._creds["username"].get_secret_value() + ) + return _usr_check and self.bc.checkpw( + _creds["password"].get_secret_value().encode("utf-8"), + self._creds["password"].get_secret_value().encode("utf-8"), + ) + + +@register_provider("htpasswd_file") +class HtpasswdFileServerAuthCredentialsProvider(HtpasswdServerAuthCredentialsProvider): + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_server_auth_credentials_file") + _file = str(system.settings.chroma_server_auth_credentials_file) + with open(_file) as f: + _raw_creds = [v for v in f.readline().strip().split(":")] + self._creds = { + "username": SecretStr(_raw_creds[0]), + "password": SecretStr(_raw_creds[1]), + } + if ( + len(self._creds) != 2 + or "username" not in self._creds + or "password" not in self._creds + ): + raise ValueError( + "Invalid Htpasswd credentials found in [chroma_server_auth_credentials]. " + "Must be :." + ) + + +class HtpasswdConfigurationServerAuthCredentialsProvider( + HtpasswdServerAuthCredentialsProvider +): + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_server_auth_credentials") + _raw_creds = ( + str(system.settings.chroma_server_auth_credentials).strip().split(":") + ) + self._creds = { + "username": SecretStr(_raw_creds[0]), + "password": SecretStr(_raw_creds[1]), + } + if ( + len(self._creds) != 2 + or "username" not in self._creds + or "password" not in self._creds + ): + raise ValueError( + "Invalid Htpasswd credentials found in [chroma_server_auth_credentials]. " + "Must be :." + ) + + +class RequestsClientAuthProtocolAdapter( + ClientAuthProtocolAdapter[requests.PreparedRequest] +): + class _Session(requests.Session): + _protocol_adapter: ClientAuthProtocolAdapter[requests.PreparedRequest] + + def __init__( + self, protocol_adapter: ClientAuthProtocolAdapter[requests.PreparedRequest] + ) -> None: + super().__init__() + self._protocol_adapter = protocol_adapter + + @override + def send( + self, request: requests.PreparedRequest, **kwargs: Any + ) -> requests.Response: + self._protocol_adapter.inject_credentials(request) + return super().send(request, **kwargs) + + _session: _Session + _auth_provider: ClientAuthProvider + + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_client_auth_provider") + self._auth_provider = cast( + ClientAuthProvider, + system.require( + resolve_provider( + str(system.settings.chroma_client_auth_provider), ClientAuthProvider + ), + ), + ) + self._session = self._Session(self) + self._auth_header = self._auth_provider.authenticate() + + @property + def session(self) -> requests.Session: + return self._session + + @override + def inject_credentials(self, injection_context: requests.PreparedRequest) -> None: + if self._auth_header.get_auth_info_type() == AuthInfoType.HEADER: + _header_info = self._auth_header.get_auth_info() + injection_context.headers[_header_info[0]] = _header_info[ + 1 + ].get_secret_value() + else: + raise ValueError( + f"Unsupported auth type: {self._auth_header.get_auth_info_type()}" + ) + + +class ConfigurationClientAuthCredentialsProvider( + ClientAuthCredentialsProvider[SecretStr] +): + _creds: SecretStr + + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_client_auth_credentials") + self._creds = SecretStr(str(system.settings.chroma_client_auth_credentials)) + + @override + def get_credentials(self) -> SecretStr: + return self._creds diff --git a/chromadb/auth/registry.py b/chromadb/auth/registry.py new file mode 100644 index 000000000000..cebf66671756 --- /dev/null +++ b/chromadb/auth/registry.py @@ -0,0 +1,107 @@ +import importlib +import logging +import pkgutil +from typing import Union, Dict, Type, Callable + +from chromadb.auth import ( + ClientAuthConfigurationProvider, + ClientAuthCredentialsProvider, + ClientAuthProtocolAdapter, + ServerAuthProvider, + ServerAuthConfigurationProvider, + ServerAuthCredentialsProvider, + ClientAuthProvider, +) +from chromadb.utils import get_class + +logger = logging.getLogger(__name__) +ProviderTypes = Union[ + "ClientAuthProvider", + "ClientAuthConfigurationProvider", + "ClientAuthCredentialsProvider", + "ServerAuthProvider", + "ServerAuthConfigurationProvider", + "ServerAuthCredentialsProvider", + "ClientAuthProtocolAdapter", +] + +_provider_registry = { + "client_auth_providers": {}, + "client_auth_config_providers": {}, + "client_auth_credentials_providers": {}, + "client_auth_protocol_adapters": {}, + "server_auth_providers": {}, + "server_auth_config_providers": {}, + "server_auth_credentials_providers": {}, +} # type: Dict[str, Dict[str, Type[ProviderTypes]]] + + +def register_classes_from_package(package_name: str) -> None: + package = importlib.import_module(package_name) + for _, module_name, _ in pkgutil.iter_modules(package.__path__): + full_module_name = f"{package_name}.{module_name}" + _ = importlib.import_module(full_module_name) + + +def register_provider( + short_hand: str, +) -> Callable[[Type[ProviderTypes]], Type[ProviderTypes]]: + def decorator(cls: Type[ProviderTypes]) -> Type[ProviderTypes]: + logger.error("Registering provider: %s", short_hand) + global _provider_registry + if issubclass(cls, ClientAuthProvider): + _provider_registry["client_auth_providers"][short_hand] = cls + elif issubclass(cls, ClientAuthConfigurationProvider): + _provider_registry["client_auth_config_providers"][short_hand] = cls + elif issubclass(cls, ClientAuthCredentialsProvider): + _provider_registry["client_auth_credentials_providers"][short_hand] = cls + elif issubclass(cls, ClientAuthProtocolAdapter): + _provider_registry["client_auth_protocol_adapters"][short_hand] = cls + elif issubclass(cls, ServerAuthProvider): + _provider_registry["server_auth_providers"][short_hand] = cls + elif issubclass(cls, ServerAuthConfigurationProvider): + _provider_registry["server_auth_config_providers"][short_hand] = cls + elif issubclass(cls, ServerAuthCredentialsProvider): + _provider_registry["server_auth_credentials_providers"][short_hand] = cls + else: + raise ValueError( + "Only ClientAuthProvider, ClientAuthConfigurationProvider, " + "ClientAuthCredentialsProvider, ServerAuthProvider, " + "ServerAuthConfigurationProvider, and ServerAuthCredentialsProvider, ClientAuthProtocolAdapter " + "can be registered." + ) + return cls + + return decorator + + +def resolve_provider( + class_or_name: str, cls: Type[ProviderTypes] +) -> Type[ProviderTypes]: + register_classes_from_package("chromadb.auth") + global _provider_registry + if issubclass(cls, ClientAuthProvider): + _key = "client_auth_providers" + elif issubclass(cls, ClientAuthConfigurationProvider): + _key = "client_auth_config_providers" + elif issubclass(cls, ClientAuthCredentialsProvider): + _key = "client_auth_credentials_providers" + elif issubclass(cls, ClientAuthProtocolAdapter): + _key = "client_auth_protocol_adapters" + elif issubclass(cls, ServerAuthProvider): + _key = "server_auth_providers" + elif issubclass(cls, ServerAuthConfigurationProvider): + _key = "server_auth_config_providers" + elif issubclass(cls, ServerAuthCredentialsProvider): + _key = "server_auth_credentials_providers" + else: + raise ValueError( + "Only ClientAuthProvider, ClientAuthConfigurationProvider, " + "ClientAuthCredentialsProvider, ServerAuthProvider, " + "ServerAuthConfigurationProvider, and ServerAuthCredentialsProvider,ClientAuthProtocolAdapter " + "can be registered." + ) + if class_or_name in _provider_registry[_key]: + return _provider_registry[_key][class_or_name] + else: + return get_class(class_or_name, cls) # type: ignore diff --git a/chromadb/auth/token/__init__.py b/chromadb/auth/token/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chromadb/config.py b/chromadb/config.py index 5306d0912c7c..67dbfe8ef402 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -1,12 +1,16 @@ -from pydantic import BaseSettings -from typing import Optional, List, Any, Dict, TypeVar, Set, cast, Iterable, Type -from typing_extensions import Literal -from abc import ABC import importlib +import inspect import logging -from overrides import EnforceOverrides, override +import os +from abc import ABC from graphlib import TopologicalSorter -import inspect +from typing import Optional, List, Any, Dict, Set, Iterable +from typing import Type, TypeVar, cast + +from overrides import EnforceOverrides +from overrides import override +from pydantic import BaseSettings, validator +from typing_extensions import Literal # The thin client will have a flag to control which implementations to use is_thin_client = False @@ -15,10 +19,8 @@ except ImportError: is_thin_client = False - logger = logging.getLogger(__name__) - LEGACY_ERROR = """\033[91mYou are using a deprecated configuration of Chroma. \033[94mIf you do not have data you wish to migrate, you only need to change how you construct @@ -59,7 +61,7 @@ } -class Settings(BaseSettings): +class Settings(BaseSettings): # type: ignore environment: str = "" # Legacy config has to be kept around because pydantic will error on nonexisting keys @@ -89,6 +91,54 @@ class Settings(BaseSettings): chroma_server_grpc_port: Optional[str] = None chroma_server_cors_allow_origins: List[str] = [] # eg ["http://localhost:3000"] + chroma_server_auth_provider: Optional[str] = None + + @validator("chroma_server_auth_provider", pre=True, always=True) + def chroma_server_auth_provider_non_empty( + cls: Type["Settings"], v: str + ) -> Optional[str]: + if v and not v.strip(): + raise ValueError( + "chroma_server_auth_provider cannot be empty or just whitespace" + ) + return v + + chroma_server_auth_configuration_provider: Optional[str] = None + chroma_server_auth_configuration_file: Optional[str] = None + chroma_server_auth_credentials_provider: Optional[str] = None + chroma_server_auth_credentials_file: Optional[str] = None + chroma_server_auth_credentials: Optional[str] = None + + @validator("chroma_server_auth_credentials_file", pre=True, always=True) + def chroma_server_auth_credentials_file_non_empty_file_exists( + cls: Type["Settings"], v: str + ) -> Optional[str]: + if v and not v.strip(): + raise ValueError( + "chroma_server_auth_credentials_file cannot be empty or just whitespace" + ) + if v and not os.path.isfile(os.path.join(v)): + raise ValueError( + f"chroma_server_auth_credentials_file [{v}] does not exist" + ) + return v + + chroma_client_auth_provider: Optional[str] = None + chroma_server_auth_ignore_paths: Dict[str, List[str]] = { + "/api/v1": ["GET"], + "/api/v1/heartbeat": ["GET"], + "/api/v1/version": ["GET"], + } + + chroma_client_auth_credentials_provider: Optional[ + str + ] = "chromadb.auth.providers.ConfigurationClientAuthCredentialsProvider" + chroma_client_auth_protocol_adapter: Optional[ + str + ] = "chromadb.auth.providers.RequestsClientAuthProtocolAdapter" + chroma_client_auth_credentials_file: Optional[str] = None + chroma_client_auth_credentials: Optional[str] = None + anonymized_telemetry: bool = True allow_reset: bool = False @@ -106,7 +156,7 @@ def require(self, key: str) -> Any: def __getitem__(self, key: str) -> Any: val = getattr(self, key) # Error on legacy config values - if val in _legacy_config_values: + if isinstance(val, str) and val in _legacy_config_values: raise ValueError(LEGACY_ERROR) return val @@ -158,7 +208,6 @@ def reset_state(self) -> None: class System(Component): settings: Settings - _instances: Dict[Type[Component], Component] def __init__(self, settings: Settings): @@ -169,7 +218,6 @@ def __init__(self, settings: Settings): "Chroma is running in http-only client mode, and can only be run with 'chromadb.api.fastapi.FastAPI' as the chroma_api_impl. \ see https://docs.trychroma.com/usage-guide?lang=py#using-the-python-http-only-client for more information." ) - # Validate settings don't contain any legacy config values for key in _legacy_config_keys: if settings[key] is not None: @@ -225,7 +273,9 @@ def stop(self) -> None: def reset_state(self) -> None: """Reset the state of this system and all constituents in reverse dependency order""" if not self.settings.allow_reset: - raise ValueError("Resetting is not allowed by this configuration (to enable it, set `allow_reset` to `True` in your Settings() or include `ALLOW_RESET=TRUE` in your environment variables)") + raise ValueError( + "Resetting is not allowed by this configuration (to enable it, set `allow_reset` to `True` in your Settings() or include `ALLOW_RESET=TRUE` in your environment variables)" + ) for component in reversed(list(self.components())): component.reset_state() diff --git a/chromadb/server/fastapi/__init__.py b/chromadb/server/fastapi/__init__.py index 249bcae167dd..d8e43c51081e 100644 --- a/chromadb/server/fastapi/__init__.py +++ b/chromadb/server/fastapi/__init__.py @@ -8,10 +8,13 @@ from fastapi import HTTPException, status from uuid import UUID - import chromadb from chromadb.api.models.Collection import Collection from chromadb.api.types import GetResult, QueryResult +from chromadb.auth.fastapi import ( + FastAPIChromaAuthMiddleware, + FastAPIChromaAuthMiddlewareWrapper, +) from chromadb.config import Settings import chromadb.server import chromadb.api @@ -110,6 +113,12 @@ def __init__(self, settings: Settings): allow_origins=settings.chroma_server_cors_allow_origins, allow_methods=["*"], ) + if settings.chroma_server_auth_provider: + self._auth_middleware = self._api.require(FastAPIChromaAuthMiddleware) + self._app.add_middleware( + FastAPIChromaAuthMiddlewareWrapper, + auth_middleware=self._auth_middleware, + ) self.router = ChromaAPIRouter() diff --git a/chromadb/test/auth/test_basic_auth.py b/chromadb/test/auth/test_basic_auth.py new file mode 100644 index 000000000000..f064be663358 --- /dev/null +++ b/chromadb/test/auth/test_basic_auth.py @@ -0,0 +1,12 @@ +import pytest + + +def test_invalid_auth_cred(api_wrong_cred): + with pytest.raises(Exception) as e: + api_wrong_cred.list_collections() + assert "Unauthorized" in str(e.value) + + +def test_server_basic_auth(api_with_server_auth): + cols = api_with_server_auth.list_collections() + assert len(cols) == 0 diff --git a/chromadb/test/conftest.py b/chromadb/test/conftest.py index b8b9fc864b54..5ec6cdf1a684 100644 --- a/chromadb/test/conftest.py +++ b/chromadb/test/conftest.py @@ -1,14 +1,10 @@ -from chromadb.config import Settings, System -from chromadb.api import API -from chromadb.ingest import Producer -import chromadb.server.fastapi -from requests.exceptions import ConnectionError -import hypothesis -import tempfile +import logging +import multiprocessing import os -import uvicorn +import shutil +import socket +import tempfile import time -import pytest from typing import ( Generator, Iterator, @@ -18,19 +14,23 @@ Tuple, Callable, ) + +import hypothesis +import pytest +import uvicorn +from requests.exceptions import ConnectionError from typing_extensions import Protocol -import shutil -import logging -import socket -import multiprocessing +import chromadb.server.fastapi +from chromadb.api import API +from chromadb.config import Settings, System +from chromadb.ingest import Producer from chromadb.types import SeqId, SubmitEmbeddingRecord from chromadb.db.mixins import embeddings_queue root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # This will only run when testing - logger = logging.getLogger(__name__) hypothesis.settings.register_profile( @@ -53,7 +53,12 @@ def find_free_port() -> int: def _run_server( - port: int, is_persistent: bool = False, persist_directory: Optional[str] = None + port: int, + is_persistent: bool = False, + persist_directory: Optional[str] = None, + chroma_server_auth_provider: Optional[str] = None, + chroma_server_auth_credentials_provider: Optional[str] = None, + chroma_server_auth_credentials_file: Optional[str] = None, ) -> None: """Run a Chroma server locally""" if is_persistent and persist_directory: @@ -66,6 +71,9 @@ def _run_server( is_persistent=is_persistent, persist_directory=persist_directory, allow_reset=True, + chroma_server_auth_provider=chroma_server_auth_provider, + chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, + chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, ) else: settings = Settings( @@ -76,6 +84,9 @@ def _run_server( chroma_segment_manager_impl="chromadb.segment.impl.manager.local.LocalSegmentManager", is_persistent=False, allow_reset=True, + chroma_server_auth_provider=chroma_server_auth_provider, + chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, + chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, ) server = chromadb.server.fastapi.FastAPI(settings) uvicorn.run(server.app(), host="0.0.0.0", port=port, log_level="error") @@ -94,18 +105,41 @@ def _await_server(api: API, attempts: int = 0) -> None: _await_server(api, attempts + 1) -def _fastapi_fixture(is_persistent: bool = False) -> Generator[System, None, None]: +def _fastapi_fixture( + is_persistent: bool = False, + chroma_server_auth_provider: Optional[str] = None, + chroma_server_auth_credentials_provider: Optional[str] = None, + chroma_client_auth_provider: Optional[str] = None, + chroma_server_auth_credentials_file: Optional[str] = None, + chroma_client_auth_credentials: Optional[str] = None, +) -> Generator[System, None, None]: """Fixture generator that launches a server in a separate process, and yields a fastapi client connect to it""" port = find_free_port() logger.info(f"Running test FastAPI server on port {port}") ctx = multiprocessing.get_context("spawn") - args: Tuple[int, bool, Optional[str]] = (port, False, None) + args: Tuple[ + int, bool, Optional[str], Optional[str], Optional[str], Optional[str] + ] = ( + port, + False, + None, + chroma_server_auth_provider, + chroma_server_auth_credentials_provider, + chroma_server_auth_credentials_file, + ) persist_directory = None if is_persistent: persist_directory = tempfile.mkdtemp() - args = (port, is_persistent, persist_directory) + args = ( + port, + is_persistent, + persist_directory, + chroma_server_auth_provider, + chroma_server_auth_credentials_provider, + chroma_server_auth_credentials_file, + ) proc = ctx.Process(target=_run_server, args=args, daemon=True) proc.start() settings = Settings( @@ -113,6 +147,8 @@ def _fastapi_fixture(is_persistent: bool = False) -> Generator[System, None, Non chroma_server_host="localhost", chroma_server_http_port=str(port), allow_reset=True, + chroma_client_auth_provider=chroma_client_auth_provider, + chroma_client_auth_credentials=chroma_client_auth_credentials, ) system = System(settings) api = system.instance(API) @@ -134,6 +170,88 @@ def fastapi_persistent() -> Generator[System, None, None]: return _fastapi_fixture(is_persistent=True) +def fastapi_server_auth() -> Generator[System, None, None]: + server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) + with open(server_auth_file, "w") as f: + f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") + for item in _fastapi_fixture( + is_persistent=False, + chroma_server_auth_provider="chromadb.auth.basic.BasicAuthServerProvider", + chroma_server_auth_credentials_provider="chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider", + chroma_server_auth_credentials_file="./server.htpasswd", + chroma_client_auth_provider="chromadb.auth.basic.BasicAuthClientProvider", + chroma_client_auth_credentials="admin:admin", + ): + yield item + os.remove(server_auth_file) + + +def fastapi_server_auth_param() -> Generator[System, None, None]: + server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) + with open(server_auth_file, "w") as f: + f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") + for item in _fastapi_fixture( + is_persistent=False, + chroma_server_auth_provider="chromadb.auth.basic.BasicAuthServerProvider", + chroma_server_auth_credentials_provider="chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider", + chroma_server_auth_credentials_file="./server.htpasswd", + chroma_client_auth_provider="chromadb.auth.basic.BasicAuthClientProvider", + chroma_client_auth_credentials="admin:admin", + ): + yield item + os.remove(server_auth_file) + + +# TODO we need a generator for auth providers +def fastapi_server_auth_file() -> Generator[System, None, None]: + server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) + with open(server_auth_file, "w") as f: + f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") + for item in _fastapi_fixture( + is_persistent=False, + chroma_server_auth_provider="chromadb.auth.basic.BasicAuthServerProvider", + chroma_server_auth_credentials_provider="chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider", + chroma_server_auth_credentials_file="./server.htpasswd", + chroma_client_auth_provider="chromadb.auth.basic.BasicAuthClientProvider", + chroma_client_auth_credentials="admin:admin", + ): + yield item + os.remove(server_auth_file) + + +def fastapi_server_auth_shorthand() -> Generator[System, None, None]: + server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) + with open(server_auth_file, "w") as f: + f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") + for item in _fastapi_fixture( + is_persistent=False, + chroma_server_auth_provider="basic", + chroma_server_auth_credentials_provider="htpasswd_file", + chroma_server_auth_credentials_file="./server.htpasswd", + chroma_client_auth_provider="basic", + chroma_client_auth_credentials="admin:admin", + ): + yield item + os.remove(server_auth_file) + + +@pytest.fixture(scope="function") +def fastapi_server_auth_invalid_cred() -> Generator[System, None, None]: + server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) + with open(server_auth_file, "w") as f: + f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") + for item in _fastapi_fixture( + is_persistent=False, + chroma_server_auth_provider="chromadb.auth.basic.BasicAuthServerProvider", + chroma_server_auth_credentials_provider="chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider", + chroma_server_auth_credentials_file="./server.htpasswd", + chroma_client_auth_provider="chromadb.auth.basic.BasicAuthClientProvider", + chroma_client_auth_credentials="admin:admin1", + ): + yield item + os.remove(server_auth_file) + + def integration() -> Generator[System, None, None]: """Fixture generator for returning a client configured via environmenet variables, intended for externally configured integration tests @@ -192,11 +310,25 @@ def system_fixtures() -> List[Callable[[], Generator[System, None, None]]]: return fixtures +def system_fixtures_auth() -> List[Callable[[], Generator[System, None, None]]]: + fixtures = [ + fastapi_server_auth_param, + fastapi_server_auth_file, + fastapi_server_auth_shorthand, + ] + return fixtures + + @pytest.fixture(scope="module", params=system_fixtures()) def system(request: pytest.FixtureRequest) -> Generator[API, None, None]: yield next(request.param()) +@pytest.fixture(scope="module", params=system_fixtures_auth()) +def system_auth(request: pytest.FixtureRequest) -> Generator[API, None, None]: + yield next(request.param()) + + @pytest.fixture(scope="function") def api(system: System) -> Generator[API, None, None]: system.reset_state() @@ -204,6 +336,23 @@ def api(system: System) -> Generator[API, None, None]: yield api +@pytest.fixture(scope="function") +def api_wrong_cred( + fastapi_server_auth_invalid_cred: System, +) -> Generator[API, None, None]: + fastapi_server_auth_invalid_cred.reset_state() + api = fastapi_server_auth_invalid_cred.instance(API) + yield api + + +@pytest.fixture(scope="function") +def api_with_server_auth(system_auth: System) -> Generator[API, None, None]: + _sys = system_auth + _sys.reset_state() + api = _sys.instance(API) + yield api + + # Producer / Consumer fixtures # diff --git a/chromadb/test/test_api.py b/chromadb/test/test_api.py index dc2a21f0467b..0583d6eede74 100644 --- a/chromadb/test/test_api.py +++ b/chromadb/test/test_api.py @@ -1,4 +1,5 @@ # type: ignore + import chromadb from chromadb.api.types import QueryResult from chromadb.config import Settings diff --git a/chromadb/utils/__init__.py b/chromadb/utils/__init__.py index e69de29bb2d1..fe6bb81853b1 100644 --- a/chromadb/utils/__init__.py +++ b/chromadb/utils/__init__.py @@ -0,0 +1,12 @@ +import importlib +from typing import Type, TypeVar, cast + +C = TypeVar("C") + + +def get_class(fqn: str, type: Type[C]) -> Type[C]: + """Given a fully qualifed class name, import the module and return the class""" + module_name, class_name = fqn.rsplit(".", 1) + module = importlib.import_module(module_name) + cls = getattr(module, class_name) + return cast(Type[C], cls) diff --git a/docker-compose.yml b/docker-compose.yml index 5f298f1e741e..db5f4d55862c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,8 @@ services: command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml environment: - IS_PERSISTENT=TRUE + - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} + - CHROMA_SERVER_AUTH_PROVIDER_CONFIG=${CHROMA_SERVER_AUTH_PROVIDER_CONFIG} ports: - 8000:8000 networks: diff --git a/docs/CIP_2_Auth_Providers_Proposal.md b/docs/CIP_2_Auth_Providers_Proposal.md new file mode 100644 index 000000000000..2652f6265230 --- /dev/null +++ b/docs/CIP_2_Auth_Providers_Proposal.md @@ -0,0 +1,190 @@ +# CIP-2: Auth Providers Proposal + +## Status + +Current Status: `Under Discussion` + +## **Motivation** + +Currently, Chroma does not provide any authentication mechanism. This CIP proposes to +to add authentication abstractions and basic authentication mechanisms to Chroma. + +There are intrinsic and extrinsic motivations for this CIP. The intrinsic motivation +is to provide a secure way to access Chroma as adoption grows and the team is gearing up to release a cloud offering. +The extrinsic motivation is driven by the community which is deploying Chroma in both public and private clouds and +in test and production environments. The community has expressed the need for authentication and authorization. + +> Observation: We consider the Auth to be applicable to client-server mode. + +## **Public Interfaces** + +Changes to the public interface are related to the `Settings` class where we introduce new optional attributes to +control server and client-side auth providers. + +## **Proposed Changes** + +We propose two abstraction groups, one for the server-side and another for the client-side. In +addition we also introduce a FastAPI/startlette middleware adapter which will allow using the server-side abstractions +in the context of FastAPI. For client-side we rely on `requests` + +### Architecture Overview + +Architecture Overview: + +![cip-2-arch.png](assets/cip-2-arch.png) + +Request Sequence: + +![cip-2-seq.png](assets/cip-2-seq.png) + +### Constraints + +This section provides teh architectural constraints for the authentication framework. The constraints are set of +restrictions we impose to make the design simpler and more robust. + +- There must be at most one active client-side auth provider +- There must be at most one active client-side credentials provider +- There must be at most one active server-side auth provider +- There must be at most one active server-side auth configuration provider +- There must be at most one active server-side auth credentials provider + +### Core Concepts + +- Auth Provider - an abstraction that provides authentication functionality for either client or server-side. The + provider is responsible for validating client credentials using (if available) configuration and credentials + providers. The auth provider is also responsible for carrying the Chroma-leg of any authentication flow. +- Auth Configuration Provider - an abstraction that provides configuration for auth providers. The configuration can be + loaded from a file, env vars or programmatically. The configuration is used for validating and/or accessing user + credentials. Examples: secret key for JWT token based auth, DB URL for DB based auth, etc. Depending on sensitivity of + the information stored in the configuration, the provider should implement the necessary interfaces to access such + information in a secure way. +- Auth Credentials Provider - an abstraction that provides credentials for auth providers. The credentials can be + loaded from a file, env vars or programmatically. The credentials are used for validating client-side credentials (for + sever-side auth) and retrieving or generating client-side credentials (for client-side auth). + +#### Abstractions + +##### Server-Side + +We suggest multiple abstractions on the server-side to allow for easy integration with different auth providers. +We suggest the following abstractions: + +> Note: All abstractions are defined under `chromadb.auth` package + +- `ServerAuthProvider` - this is the base server auth provider abstraction that allows any server implementation of + Chroma to support variety of auth providers. The main responsibility of the auth provider is to orchestrate the auth + flow by gluing together the auth configuration and credentials providers. +- `ChromaAuthMiddleware` - The auth middleware is effectively an adapter responsible for providing server specific + implementation of the auth middleware. This includes three general types of operations - forwarding authentication to + the auth provider, instrumenting the server if needed to support a specific auth flow, ignore certain + actions/operations (e.g. in REST this would be verb+path) that should not be authenticated. +- `ServerAuthenticationRequest` - An abstraction for querying for authentication data from server specific + implementation. +- `ServerAuthenticationResponse` - An abstraction for returning authentication data to server specific implementation. +- `ServerAuthConfigurationProvider` - this is the base abstraction for auth configuration providers. The provider is + responsible for loading auth configuration from a file, env vars or programmatically. +- `AbstractCredentials` - base abstraction for credentials encapsulation from server to Auth Credentials Provider. +- `ServerAuthCredentialsProvider` - this is the base abstraction for auth credentials providers. The provider is + responsible for verifying client credentials. + +##### Client-Side + +We suggest multiple abstractions on the client-side to allow for easy integration with different auth providers. + +- `ClientAuthProvider` - this is the base client auth provider abstraction that allows any client implementation of + Chroma to support variety of auth providers. The main responsibility of the auth provider is to orchestrate the auth + flow by gluing together the auth configuration and credentials providers, and any possible auth workflows (e.g. OAuth) +- `ClientAuthConfigurationProvider` - this is the base abstraction for auth configuration providers. The provider is + responsible for loading auth configuration from a file, env vars or programmatically. +- `ClientAuthCredentialsProvider` - this is the base abstraction for auth credentials providers. The provider is + responsible for verifying client credentials. +- `AbstractCredentials` - base abstraction for credentials encapsulation from client to Auth Credentials Provider. +- `ClientAuthProtocolAdapter` - this is an abstraction that allows for client-side auth providers to communicate with + backends using variety of protocols and libraries (e.g. `requests`, `gRPC` etc). The adapter is responsible for + translating the auth requests to generated by the credentials provider to a protocol specific message. + +#### Workflows + +##### Server-Side + +![cip-2-server-side-wf.png](assets/cip-2-server-side-wf.png) + +##### Client-Side + +![cip-2-client-side-wf.png](assets/cip-2-client-side-wf.png) + +### Configuration + +#### Server-side + +TBD + +#### Client-side + + + +### Reasoning + +- Server-side abstraction - it is very useful as the intention is to support a variety of auth providers. +- Client-side abstraction - similar reasoning but from client's perspective. It will allow for both standard and + non-standard auth provider plugins to be added without further impacting the client side +- Backend (fastAPI) adapter - this is a backend-specific way of loading server-side auth provider plugins. It will also + serve as a template/blueprint when it comes to introducing the auth plugins to another backend framework (e.g. Flask) + +We also propose that each auth provider on either side must be configurable via three main methods depending on +developer preference: + +- File-base - a configuration file that provides the requisite config and credentials (recommended for production) +- Env - configuration through environment variables (this can also apply for the file-based config, which can be + specified in env var) +- Programmatically - provide requisite configuration through CLI or directly in code (it is left for the developer to + decide how such configuration is loaded and made available to the auth provider) - this is possibly the least secure + and should be used for testing + +The intention is to start with two minimal but useful Auth providers: + +- Basic Auth - base64 encoded user and password credentials. The credentials will be static in nature and defined via + auth provider config +- Token - A simple static token implementation + +Both of the above providers will rely on the `Authorization` header to achieve their functionality. + +> Both initial providers are there to help introduce a bear minimum security but are not recommended for production use + +Further work: + +- Introduction of JWT and mTLS auth providers +- API Keys +- Chroma managed user store - this would be similar to what standard DBMS’ are doing today - maintain a table with users + and salted password hashes +- K8s RBAC integration (for cloud-native deployments) +- GCP service accounts? +- SPIFFE and SPIRE integrations +- Go and Java client-side auth providers (for other impl like Rust and Ruby, we need to discuss with respective + maintainers) + +> Note: this CIP intentionally does not tackle authZ but acknowledges that authN and authZ must work in tandem in future +> releases + +## **Compatibility, Deprecation, and Migration Plan** + +This change, introducing a pluggable auth framework is no impacting compatibility of existing deployments and users can +upgrade and use the new framework without the need for migration. + +No deprecations. + +## **Test Plan** + +We will introduce a new set of tests to verify both client and server-side auth providers. + +## **Rejected Alternatives** + +We have considered direct middleware Auth or existing third-party libraries for FastAPI integration with auth providers, +but that will create a dependency for Chroma on FastAPI itself. + +We have also considered using OAuth 2.0 or OIDC however the challenge there is that both of these protocols are +generally intended for User (human) auth whereas in our case we have a system-to-system auth. That said there still +might be room for either of these protocols, but further more in-depth use case analysis is required. + +Relying entirely on external providers, while this is possible not providing out-of-the-box integrated auth capabilities +is a non-starter for many enterprise customers. diff --git a/docs/assets/cip-2-arch.png b/docs/assets/cip-2-arch.png new file mode 100644 index 0000000000000000000000000000000000000000..68f30ac6c5cd5b2209c0f1529b2d7235c831034c GIT binary patch literal 46879 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�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

    `2Fs}PE9W_}M`x6S*A`zh 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 T1}DA)9-n^}`j6VZ$N%}CojS_) literal 0 HcmV?d00001 diff --git a/docs/assets/cip-2-server-side-wf.png b/docs/assets/cip-2-server-side-wf.png new file mode 100644 index 0000000000000000000000000000000000000000..a4acb6d8893a63105cde4180be44104eb48949c7 GIT binary patch literal 116941 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 literal 0 HcmV?d00001 diff --git a/examples/basic_functionality/assets/auth-architecture.png b/examples/basic_functionality/assets/auth-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..33d049acdd36d4a19132fff5673853311f4f47f1 GIT binary patch literal 32974 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 literal 0 HcmV?d00001 diff --git a/examples/basic_functionality/client_auth.ipynb b/examples/basic_functionality/client_auth.ipynb new file mode 100644 index 000000000000..0fdb82e7af8c --- /dev/null +++ b/examples/basic_functionality/client_auth.ipynb @@ -0,0 +1,259 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Chroma Authentication\n", + "\n", + "This tutorial aims to explain how authentication can be setup in Chroma.\n", + "\n", + "> **Important**: The concept of authentication is only applicable to Client/Server deployments. If you are using Chroma in a standalone mode, authentication is not applicable.\n", + "\n", + "## Concepts\n", + "\n", + "### Architecture Overview\n", + "\n", + "![Authentication Architecture](assets/auth-architecture.png \"Authentication Architecture\")\n", + "\n", + "### Authentication Flow (Sequence)\n", + "\n", + "The authentication sequence is applied for every request. It is important to understand that credential computation or retrieval (e.g. from external auth providers) is only done once for the first authenticated request. Subsequent requests will use the same credentials.\n", + "\n", + "The authentication flow is as follows:\n", + "\n", + "![Authentication Flow](assets/auh-sequence.png \"Authentication Flow\")\n", + "\n", + "### Preemptive Authentication\n", + "\n", + "In its current release the authentication in Chroma works in a preemptive mode. This means that the client is responsible for sending the authentication information on every request. The server will not challenge the client for authentication.\n", + "\n", + "> **Warning**: There are security risks involved with preemptive authentication in that the client might unintentionally send credentials to malicious or unintended server. When deploying authentication users are encouraged to use HTTPS (always verify server certs), to use secure providers (e.g. JWT) \n", + "> and apply good security practices.\n", + "\n", + "### Authentication Provider\n", + "\n", + "Authentication in Chroma is handled by Authentication Providers. Providers are pluggable modules that allow Chroma to abstract the authentication mechanism from the rest of the system.\n", + "\n", + "Chroma ships with the following build-in providers:\n", + "- Basic Authentication\n", + "- JWT Authentication (work in progress)\n", + "\n", + "### Client-side Authentication\n", + "\n", + "Client-side authentication refers to the process of preparing and communicating credentials information on the client-side and sending that information the Chroma server.\n", + "\n", + "### Server-side Authentication\n", + "\n", + "Server-side authentication refers to the process of validating the credentials information received from the client and authenticating the client.\n" + ], + "metadata": { + "collapsed": false + }, + "id": "eae631e46b4c1115" + }, + { + "cell_type": "markdown", + "source": [ + "## Configuration\n", + "\n", + "### Server Configuration\n", + "\n", + "In order for the server to provide auth it needs several pieces of information and depending on the authentication provider you may or may not need to provide all of them.\n", + "\n", + "- `CHROMA_SERVER_AUTH_PROVIDER` - It indicates the authentication provider class to use. In this case we are using the `chromadb.auth.basic.BasicAuthServerProvider` class (it is also possible to use `basic` as a shorthand).\n", + "- `CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER` - The credentials provider is a way for the server to validate the provided auth information from the client. You can use `chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider` to validate against a file in htpasswd format (user:password) - single line with bcrypt hash for password. Alternatively you can use a shorthand to load providers (e.g. `htpasswd_file` for `chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider`).\n", + "- `CHROMA_SERVER_AUTH_CREDENTIALS_FILE` - The path to the credentials file in case the credentials provider requires it. In this case we are using the `chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider` provider which requires a file path.\n", + "\n", + "\n", + "### Client Configuration\n", + "\n", + "Similarly on the client side we need to provide the following configuration parameters:\n", + "\n", + "- `CHROMA_CLIENT_AUTH_PROVIDER` - It indicates the authentication provider class to use. In this case we are using the `chromadb.auth.basic.BasicAuthClientProvider` class or `basic` shorthand.\n", + "- `CHROMA_CLIENT_AUTH_CREDENTIALS` - The auth credentials to be passed to the provider. In this case we are using the `admin:admin` credentials as we'll be using Basic Auth.\n" + ], + "metadata": { + "collapsed": false + }, + "id": "87d45f79aed65e21" + }, + { + "cell_type": "markdown", + "source": [ + "## Setting Up\n", + "\n", + "### Before You Begin\n", + "\n", + "Make sure you have either `chromadb` or `chromadb-client` installed. You can do that by running the following command:\n", + "\n", + "```bash\n", + "pip install chromadb\n", + "```\n", + "or\n", + "\n", + "```bash\n", + "pip install chromadb-client\n", + "```\n", + "\n", + "Make sure Chroma Server is running. Use one of the following methods to start the server:\n", + "\n", + "From the command line:\n", + "\n", + "> Note: The below options will configure the server to use Basic Authentication with the username `admin` and password `admin`.\n", + "\n", + "```bash\n", + "export CHROMA_USER=admin\n", + "export CHROMA_PASSWORD=admin\n", + "docker run --rm --entrypoint htpasswd httpd:2 -Bbn ${CHROMA_USER} ${CHROMA_PASSWORD} > server.htpasswd\n", + "CHROMA_SERVER_AUTH_CREDENTIALS_FILE=\"./server.htpasswd\" \\\n", + "CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' \\\n", + "CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' \\\n", + "uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --proxy-headers --log-config log_config.yml\n", + "```\n", + "\n", + "With Docker Compose:\n", + "\n", + "> Note: You need to clone the git repository first and run the command from the repository root.\n", + "\n", + "```bash\n", + "export CHROMA_USER=admin\n", + "export CHROMA_PASSWORD=admin\n", + "docker run --rm --entrypoint htpasswd httpd:2 -Bbn ${CHROMA_USER} ${CHROMA_PASSWORD} > server.htpasswd\n", + "cat << EOF > .env\n", + "CHROMA_SERVER_AUTH_CREDENTIALS_FILE=\"/chroma/server.htpasswd\"\n", + "CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider'\n", + "CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider'\n", + "EOF\n", + "docker-compose up -d --build \n", + "```\n" + ], + "metadata": { + "collapsed": false + }, + "id": "af49d8c78f2f7347" + }, + { + "cell_type": "markdown", + "source": [ + "### Basic Authentication" + ], + "metadata": { + "collapsed": false + }, + "id": "fc77d909233f2645" + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "data": { + "text/plain": "[]" + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import chromadb\n", + "from chromadb import Settings\n", + "\n", + "client = chromadb.HttpClient(\n", + " settings=Settings(chroma_client_auth_provider=\"chromadb.auth.basic.BasicAuthClientProvider\",chroma_client_auth_credentials=\"admin:admin\"))\n", + "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.get_version() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.list_collections() # this is a protected endpoint and requires authentication\n", + "\n" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-22T00:33:16.354523Z", + "start_time": "2023-08-22T00:33:15.715736Z" + } + }, + "id": "8f9307acce25f672" + }, + { + "cell_type": "markdown", + "source": [ + "#### Verifying Authentication (Negative Test)" + ], + "metadata": { + "collapsed": false + }, + "id": "6b75f04e59cb1d42" + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "As expected, you are not authorized to access protected endpoints.\n" + ] + } + ], + "source": [ + "# Try to access a protected endpoint without authentication\n", + "import sys\n", + "\n", + "client = chromadb.HttpClient()\n", + "try:\n", + " client.list_collections()\n", + "except Exception as e:\n", + " if \"Unauthorized\" in str(e):\n", + " print(\"As expected, you are not authorized to access protected endpoints.\", file=sys.stderr)\n", + " else:\n", + " raise e" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-22T00:33:19.119718Z", + "start_time": "2023-08-22T00:33:19.097558Z" + } + }, + "id": "c0c3240ed4d70a79" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "start_time": "2023-08-11T15:58:07.272237Z" + } + }, + "id": "ab8b90d83f02eda" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 4f8d34c760d4..b7ae444bf96e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,8 @@ dependencies = [ 'tqdm >= 4.65.0', 'overrides >= 7.3.1', 'importlib-resources', - 'graphlib_backport >= 1.0.3; python_version < "3.9"' + 'graphlib_backport >= 1.0.3; python_version < "3.9"', + 'bcrypt >= 4.0.1' ] [tool.black] diff --git a/requirements.txt b/requirements.txt index 04387f95cd0d..c23a24cafdb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +bcrypt==4.0.1 chroma-hnswlib==0.7.2 fastapi>=0.95.2, <0.100.0 graphlib_backport==1.0.3; python_version < '3.9' From 1ae9485759ab8594831e67e3ccdb31049bf53d26 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 23 Aug 2023 21:06:16 +0300 Subject: [PATCH 02/67] [BUG]: CIP-2 Auth Providers - Clean-up (#1025) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Updated logger level to avoid confusion ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Changed logger level message when registering new Auth providers to debug to avoid confusing developers. - Change the status of the CIP to `Accepted` - Added `/docs` and `/openapi.json` to the publicly accessible endpoints (this may or many not be a security concern - TBD) ## Test plan Local tests are passing—no new tests. ## Documentation Changes N/A --- chromadb/auth/registry.py | 4 ++-- docs/CIP_2_Auth_Providers_Proposal.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chromadb/auth/registry.py b/chromadb/auth/registry.py index cebf66671756..7e844753199b 100644 --- a/chromadb/auth/registry.py +++ b/chromadb/auth/registry.py @@ -1,7 +1,7 @@ import importlib import logging import pkgutil -from typing import Union, Dict, Type, Callable +from typing import Union, Dict, Type, Callable # noqa: F401 from chromadb.auth import ( ClientAuthConfigurationProvider, @@ -47,7 +47,7 @@ def register_provider( short_hand: str, ) -> Callable[[Type[ProviderTypes]], Type[ProviderTypes]]: def decorator(cls: Type[ProviderTypes]) -> Type[ProviderTypes]: - logger.error("Registering provider: %s", short_hand) + logger.debug("Registering provider: %s", short_hand) global _provider_registry if issubclass(cls, ClientAuthProvider): _provider_registry["client_auth_providers"][short_hand] = cls diff --git a/docs/CIP_2_Auth_Providers_Proposal.md b/docs/CIP_2_Auth_Providers_Proposal.md index 2652f6265230..95df2d9b6c38 100644 --- a/docs/CIP_2_Auth_Providers_Proposal.md +++ b/docs/CIP_2_Auth_Providers_Proposal.md @@ -2,7 +2,7 @@ ## Status -Current Status: `Under Discussion` +Current Status: `Accepted` ## **Motivation** From bb170607ab6f722a02d957406b7f337c96dce31f Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 23 Aug 2023 23:00:43 +0300 Subject: [PATCH 03/67] [BUG]: CIP-2: Fixed docker-compose.yaml to work with env file (#1030) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - The docker-compose will now pick up ENV vars from supplied `--env-file` ## Test plan Manual tests done for both with and without env file scenarios ## Documentation Changes Docs updated accordingly --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index db5f4d55862c..ba95516721c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,8 @@ services: environment: - IS_PERSISTENT=TRUE - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} - - CHROMA_SERVER_AUTH_PROVIDER_CONFIG=${CHROMA_SERVER_AUTH_PROVIDER_CONFIG} + - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=${CHROMA_SERVER_AUTH_CREDENTIALS_FILE} + - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=${CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER} ports: - 8000:8000 networks: From f71f1d6d7024582f7148aa234dee6c68502cf531 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Wed, 23 Aug 2023 14:24:10 -0700 Subject: [PATCH 04/67] Release 0.4.7 (#1031) Release 0.4.7 --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 566874fc0a95..3b0a96f5bdab 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.6" +__version__ = "0.4.7" # Workaround to deal with Colab's old sqlite3 version try: From bd6250c934b892fdd7b7449f1b73e450ab334e47 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 29 Aug 2023 00:34:57 +0300 Subject: [PATCH 05/67] [BUG]: Added allow_reuse=True to pydantic validators (#1054) Note: Cross-version testing was failing as it was loading config twice which caused the issue with pydantic. ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Fixing `chromadb/test/property/test_cross_version_persist.py` test failures. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes No docs need updating --- chromadb/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chromadb/config.py b/chromadb/config.py index 67dbfe8ef402..87aa9e7301ed 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -93,7 +93,7 @@ class Settings(BaseSettings): # type: ignore chroma_server_auth_provider: Optional[str] = None - @validator("chroma_server_auth_provider", pre=True, always=True) + @validator("chroma_server_auth_provider", pre=True, always=True, allow_reuse=True) def chroma_server_auth_provider_non_empty( cls: Type["Settings"], v: str ) -> Optional[str]: @@ -109,7 +109,9 @@ def chroma_server_auth_provider_non_empty( chroma_server_auth_credentials_file: Optional[str] = None chroma_server_auth_credentials: Optional[str] = None - @validator("chroma_server_auth_credentials_file", pre=True, always=True) + @validator( + "chroma_server_auth_credentials_file", pre=True, always=True, allow_reuse=True + ) def chroma_server_auth_credentials_file_non_empty_file_exists( cls: Type["Settings"], v: str ) -> Optional[str]: From fd014edf05598e1ca16813e6559a3501674e26aa Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Mon, 28 Aug 2023 15:36:46 -0700 Subject: [PATCH 06/67] [BUG] Add dep fix for pydantic in thin client (#1033) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - #799 introduced a max version for pydantic but this change was not propagated to the thin client. This makes the same change in the thin client. - New functionality - ... ## Test plan Existing tests for client - [ ] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None --- clients/python/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/python/pyproject.toml b/clients/python/pyproject.toml index 73623598f106..3afff14f4bec 100644 --- a/clients/python/pyproject.toml +++ b/clients/python/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ ] dependencies = [ 'requests >= 2.28', - 'pydantic >= 1.9', + 'pydantic>=1.9,<2.0', 'numpy >= 1.21.6', 'posthog >= 2.4.0', 'typing_extensions >= 4.5.0', From 35a367e02e435d9919fd671b59b9da09c1cc95f4 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 29 Aug 2023 02:51:41 +0300 Subject: [PATCH 07/67] [ENH]: Auth Providers - Static API Token (#1051) Refs: #1027 ## Description of changes *Summarize the changes made by this PR.* - New functionality - Baseline functionality and tests implemented - Example notebook updated - Minor refactor on the client creds provider to allow for user specific credentials fetching. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python (regression) - [x] New fixtures added for token-based auth ## Documentation Changes Docs should be updated to highlight the new supported auth method. --- chromadb/auth/providers.py | 2 +- chromadb/auth/token/__init__.py | 193 ++++++++++++++++++ chromadb/config.py | 2 + chromadb/test/auth/test_token_auth.py | 134 ++++++++++++ chromadb/test/conftest.py | 58 ++++-- docker-compose.yml | 1 + .../basic_functionality/client_auth.ipynb | 149 +++++++++++++- 7 files changed, 517 insertions(+), 22 deletions(-) create mode 100644 chromadb/test/auth/test_token_auth.py diff --git a/chromadb/auth/providers.py b/chromadb/auth/providers.py index a3bb23616e28..3123c52a54cc 100644 --- a/chromadb/auth/providers.py +++ b/chromadb/auth/providers.py @@ -1,6 +1,6 @@ import importlib import logging -from typing import cast, Dict, TypeVar, Any +from typing import cast, Dict, TypeVar, Any, Optional import requests from overrides import override diff --git a/chromadb/auth/token/__init__.py b/chromadb/auth/token/__init__.py index e69de29bb2d1..ec6dcdc45540 100644 --- a/chromadb/auth/token/__init__.py +++ b/chromadb/auth/token/__init__.py @@ -0,0 +1,193 @@ +import logging +import string +from enum import Enum +from typing import Tuple, Any, cast, Dict, TypeVar + +from overrides import override +from pydantic import SecretStr + +from chromadb.auth import ( + ServerAuthProvider, + ClientAuthProvider, + ServerAuthenticationRequest, + ServerAuthCredentialsProvider, + AuthInfoType, + ClientAuthCredentialsProvider, + ClientAuthResponse, + SecretStrAbstractCredentials, + AbstractCredentials, +) +from chromadb.auth.registry import register_provider, resolve_provider +from chromadb.config import System +from chromadb.utils import get_class + +T = TypeVar("T") + +logger = logging.getLogger(__name__) + +__all__ = ["TokenAuthServerProvider", "TokenAuthClientProvider"] + +_token_transport_headers = ["Authorization", "X-Chroma-Token"] + + +class TokenTransportHeader(Enum): + AUTHORIZATION = "Authorization" + X_CHROMA_TOKEN = "X-Chroma-Token" + + +class TokenAuthClientAuthResponse(ClientAuthResponse): + _token_transport_header: TokenTransportHeader + + def __init__( + self, + credentials: SecretStr, + token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION, + ) -> None: + self._credentials = credentials + self._token_transport_header = token_transport_header + + @override + def get_auth_info_type(self) -> AuthInfoType: + return AuthInfoType.HEADER + + @override + def get_auth_info(self) -> Tuple[str, SecretStr]: + if self._token_transport_header == TokenTransportHeader.AUTHORIZATION: + return "Authorization", SecretStr( + f"Bearer {self._credentials.get_secret_value()}" + ) + elif self._token_transport_header == TokenTransportHeader.X_CHROMA_TOKEN: + return "X-Chroma-Token", SecretStr( + f"{self._credentials.get_secret_value()}" + ) + else: + raise ValueError( + f"Invalid token transport header: {self._token_transport_header}" + ) + + +def check_token(token: str) -> None: + token_str = str(token) + if not all(c in string.ascii_letters + string.digits for c in token_str): + raise ValueError("Invalid token. Must contain only ASCII letters and digits.") + + +@register_provider("token_config") +class TokenConfigServerAuthCredentialsProvider(ServerAuthCredentialsProvider): + _token: SecretStr + + def __init__(self, system: System) -> None: + super().__init__(system) + system.settings.require("chroma_server_auth_credentials") + token_str = str(system.settings.chroma_server_auth_credentials) + check_token(token_str) + self._token = SecretStr(token_str) + + @override + def validate_credentials(self, credentials: AbstractCredentials[T]) -> bool: + _creds = cast(Dict[str, SecretStr], credentials.get_credentials()) + if "token" not in _creds: + logger.error("Returned credentials do not contain token") + return False + return _creds["token"].get_secret_value() == self._token.get_secret_value() + + +class TokenAuthCredentials(SecretStrAbstractCredentials): + _token: SecretStr + + def __init__(self, token: SecretStr) -> None: + self._token = token + + @override + def get_credentials(self) -> Dict[str, SecretStr]: + return {"token": self._token} + + @staticmethod + def from_header( + header: str, + token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION, + ) -> "TokenAuthCredentials": + """ + Extracts token from header and returns a TokenAuthCredentials object. + """ + if token_transport_header == TokenTransportHeader.AUTHORIZATION: + header = header.replace("Bearer ", "") + header = header.strip() + token = header + elif token_transport_header == TokenTransportHeader.X_CHROMA_TOKEN: + header = header.strip() + token = header + else: + raise ValueError( + f"Invalid token transport header: {token_transport_header}" + ) + return TokenAuthCredentials(SecretStr(token)) + + +@register_provider("token") +class TokenAuthServerProvider(ServerAuthProvider): + _credentials_provider: ServerAuthCredentialsProvider + _token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + system.settings.require("chroma_server_auth_credentials_provider") + self._credentials_provider = cast( + ServerAuthCredentialsProvider, + system.require( + resolve_provider( + str(system.settings.chroma_server_auth_credentials_provider), + ServerAuthCredentialsProvider, + ) + ), + ) + if system.settings.chroma_server_auth_token_transport_header: + self._token_transport_header = TokenTransportHeader[ + str(system.settings.chroma_server_auth_token_transport_header) + ] + + @override + def authenticate(self, request: ServerAuthenticationRequest[Any]) -> bool: + try: + _auth_header = request.get_auth_info( + AuthInfoType.HEADER, self._token_transport_header.value + ) + return self._credentials_provider.validate_credentials( + TokenAuthCredentials.from_header( + _auth_header, self._token_transport_header + ) + ) + except Exception as e: + logger.error(f"TokenAuthServerProvider.authenticate failed: {repr(e)}") + return False + + +@register_provider("token") +class TokenAuthClientProvider(ClientAuthProvider): + _credentials_provider: ClientAuthCredentialsProvider[Any] + _token_transport_header: TokenTransportHeader = TokenTransportHeader.AUTHORIZATION + + def __init__(self, system: System) -> None: + super().__init__(system) + self._settings = system.settings + + system.settings.require("chroma_client_auth_credentials_provider") + self._credentials_provider = system.require( + get_class( + str(system.settings.chroma_client_auth_credentials_provider), + ClientAuthCredentialsProvider, + ) + ) + _token = self._credentials_provider.get_credentials() + check_token(_token.get_secret_value()) + if system.settings.chroma_client_auth_token_transport_header: + self._token_transport_header = TokenTransportHeader[ + str(system.settings.chroma_client_auth_token_transport_header) + ] + + @override + def authenticate(self) -> ClientAuthResponse: + _token = self._credentials_provider.get_credentials() + + return TokenAuthClientAuthResponse(_token, self._token_transport_header) diff --git a/chromadb/config.py b/chromadb/config.py index 87aa9e7301ed..bc46114eb93e 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -140,6 +140,8 @@ def chroma_server_auth_credentials_file_non_empty_file_exists( ] = "chromadb.auth.providers.RequestsClientAuthProtocolAdapter" chroma_client_auth_credentials_file: Optional[str] = None chroma_client_auth_credentials: Optional[str] = None + chroma_client_auth_token_transport_header: Optional[str] = None + chroma_server_auth_token_transport_header: Optional[str] = None anonymized_telemetry: bool = True diff --git a/chromadb/test/auth/test_token_auth.py b/chromadb/test/auth/test_token_auth.py new file mode 100644 index 000000000000..cb372ec899c9 --- /dev/null +++ b/chromadb/test/auth/test_token_auth.py @@ -0,0 +1,134 @@ +import string +from typing import Dict, Any + +import hypothesis.strategies as st +import pytest +from hypothesis import given, settings + +from chromadb.api import API +from chromadb.config import System +from chromadb.test.conftest import _fastapi_fixture + + +@st.composite +def token_config(draw: st.DrawFn) -> Dict[str, Any]: + token_header = draw(st.sampled_from(["AUTHORIZATION", "X_CHROMA_TOKEN", None])) + server_provider = draw( + st.sampled_from(["token", "chromadb.auth.token.TokenAuthServerProvider"]) + ) + client_provider = draw( + st.sampled_from(["token", "chromadb.auth.token.TokenAuthClientProvider"]) + ) + server_credentials_provider = draw( + st.sampled_from( + ["chromadb.auth.token.TokenConfigServerAuthCredentialsProvider"] + ) + ) + token = draw( + st.text(alphabet=string.ascii_letters + string.digits, min_size=1, max_size=50) + ) + persistence = draw(st.booleans()) + return { + "token_transport_header": token_header, + "chroma_server_auth_credentials": token, + "chroma_client_auth_credentials": token, + "chroma_server_auth_provider": server_provider, + "chroma_client_auth_provider": client_provider, + "chroma_server_auth_credentials_provider": server_credentials_provider, + "is_persistent": persistence, + } + + +@settings(max_examples=10) +@given(token_config()) +def test_fastapi_server_token_auth(token_config: Dict[str, Any]) -> None: + api = _fastapi_fixture( + is_persistent=token_config["is_persistent"], + chroma_server_auth_provider=token_config["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=token_config[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=token_config["chroma_server_auth_credentials"], + chroma_client_auth_provider=token_config["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_server_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_credentials=token_config["chroma_client_auth_credentials"], + ) + _sys: System = next(api) + _sys.reset_state() + _api = _sys.instance(API) + _api.heartbeat() + assert _api.list_collections() == [] + + +@st.composite +def random_token(draw: st.DrawFn) -> str: + return draw( + st.text(alphabet=string.ascii_letters + string.digits, min_size=1, max_size=5) + ) + + +@st.composite +def invalid_token(draw: st.DrawFn) -> str: + opposite_alphabet = set(string.printable) - set( + string.ascii_letters + string.digits + ) + token = draw(st.text(alphabet=list(opposite_alphabet), min_size=1, max_size=50)) + return token + + +@settings(max_examples=10) +@given(tconf=token_config(), inval_tok=invalid_token()) +def test_invalid_token(tconf: Dict[str, Any], inval_tok: str) -> None: + api = _fastapi_fixture( + is_persistent=tconf["is_persistent"], + chroma_server_auth_provider=tconf["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=tconf[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=tconf["chroma_server_auth_credentials"], + chroma_server_auth_token_transport_header=tconf["token_transport_header"], + chroma_client_auth_provider=tconf["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=tconf["token_transport_header"], + chroma_client_auth_credentials=inval_tok, + ) + with pytest.raises(Exception) as e: + _sys: System = next(api) + _sys.reset_state() + _sys.instance(API) + assert "Invalid token" in str(e) + + +@settings(max_examples=10) +@given(token_config(), random_token()) +def test_fastapi_server_token_auth_wrong_token( + token_config: Dict[str, Any], random_token: str +) -> None: + api = _fastapi_fixture( + is_persistent=token_config["is_persistent"], + chroma_server_auth_provider=token_config["chroma_server_auth_provider"], + chroma_server_auth_credentials_provider=token_config[ + "chroma_server_auth_credentials_provider" + ], + chroma_server_auth_credentials=token_config["chroma_server_auth_credentials"], + chroma_server_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_provider=token_config["chroma_client_auth_provider"], + chroma_client_auth_token_transport_header=token_config[ + "token_transport_header" + ], + chroma_client_auth_credentials=token_config["chroma_client_auth_credentials"] + + random_token, + ) + _sys: System = next(api) + _sys.reset_state() + _api = _sys.instance(API) + _api.heartbeat() + with pytest.raises(Exception) as e: + _api.list_collections() + assert "Unauthorized" in str(e) diff --git a/chromadb/test/conftest.py b/chromadb/test/conftest.py index 5ec6cdf1a684..fe91622e3832 100644 --- a/chromadb/test/conftest.py +++ b/chromadb/test/conftest.py @@ -24,9 +24,9 @@ import chromadb.server.fastapi from chromadb.api import API from chromadb.config import Settings, System +from chromadb.db.mixins import embeddings_queue from chromadb.ingest import Producer from chromadb.types import SeqId, SubmitEmbeddingRecord -from chromadb.db.mixins import embeddings_queue root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # This will only run when testing @@ -59,6 +59,8 @@ def _run_server( chroma_server_auth_provider: Optional[str] = None, chroma_server_auth_credentials_provider: Optional[str] = None, chroma_server_auth_credentials_file: Optional[str] = None, + chroma_server_auth_credentials: Optional[str] = None, + chroma_server_auth_token_transport_header: Optional[str] = None, ) -> None: """Run a Chroma server locally""" if is_persistent and persist_directory: @@ -74,6 +76,8 @@ def _run_server( chroma_server_auth_provider=chroma_server_auth_provider, chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, + chroma_server_auth_credentials=chroma_server_auth_credentials, + chroma_server_auth_token_transport_header=chroma_server_auth_token_transport_header, ) else: settings = Settings( @@ -87,6 +91,8 @@ def _run_server( chroma_server_auth_provider=chroma_server_auth_provider, chroma_server_auth_credentials_provider=chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file=chroma_server_auth_credentials_file, + chroma_server_auth_credentials=chroma_server_auth_credentials, + chroma_server_auth_token_transport_header=chroma_server_auth_token_transport_header, ) server = chromadb.server.fastapi.FastAPI(settings) uvicorn.run(server.app(), host="0.0.0.0", port=port, log_level="error") @@ -112,6 +118,9 @@ def _fastapi_fixture( chroma_client_auth_provider: Optional[str] = None, chroma_server_auth_credentials_file: Optional[str] = None, chroma_client_auth_credentials: Optional[str] = None, + chroma_server_auth_credentials: Optional[str] = None, + chroma_client_auth_token_transport_header: Optional[str] = None, + chroma_server_auth_token_transport_header: Optional[str] = None, ) -> Generator[System, None, None]: """Fixture generator that launches a server in a separate process, and yields a fastapi client connect to it""" @@ -120,7 +129,14 @@ def _fastapi_fixture( logger.info(f"Running test FastAPI server on port {port}") ctx = multiprocessing.get_context("spawn") args: Tuple[ - int, bool, Optional[str], Optional[str], Optional[str], Optional[str] + int, + bool, + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[str], + Optional[str], ] = ( port, False, @@ -128,6 +144,8 @@ def _fastapi_fixture( chroma_server_auth_provider, chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file, + chroma_server_auth_credentials, + chroma_server_auth_token_transport_header, ) persist_directory = None if is_persistent: @@ -139,6 +157,8 @@ def _fastapi_fixture( chroma_server_auth_provider, chroma_server_auth_credentials_provider, chroma_server_auth_credentials_file, + chroma_server_auth_credentials, + chroma_server_auth_token_transport_header, ) proc = ctx.Process(target=_run_server, args=args, daemon=True) proc.start() @@ -149,6 +169,7 @@ def _fastapi_fixture( allow_reset=True, chroma_client_auth_provider=chroma_client_auth_provider, chroma_client_auth_credentials=chroma_client_auth_credentials, + chroma_client_auth_token_transport_header=chroma_client_auth_token_transport_header, ) system = System(settings) api = system.instance(API) @@ -170,7 +191,7 @@ def fastapi_persistent() -> Generator[System, None, None]: return _fastapi_fixture(is_persistent=True) -def fastapi_server_auth() -> Generator[System, None, None]: +def fastapi_server_basic_auth() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -186,7 +207,7 @@ def fastapi_server_auth() -> Generator[System, None, None]: os.remove(server_auth_file) -def fastapi_server_auth_param() -> Generator[System, None, None]: +def fastapi_server_basic_auth_param() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -203,7 +224,7 @@ def fastapi_server_auth_param() -> Generator[System, None, None]: # TODO we need a generator for auth providers -def fastapi_server_auth_file() -> Generator[System, None, None]: +def fastapi_server_basic_auth_file() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -219,7 +240,7 @@ def fastapi_server_auth_file() -> Generator[System, None, None]: os.remove(server_auth_file) -def fastapi_server_auth_shorthand() -> Generator[System, None, None]: +def fastapi_server_basic_auth_shorthand() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -235,8 +256,7 @@ def fastapi_server_auth_shorthand() -> Generator[System, None, None]: os.remove(server_auth_file) -@pytest.fixture(scope="function") -def fastapi_server_auth_invalid_cred() -> Generator[System, None, None]: +def fastapi_server_basic_auth_invalid_cred() -> Generator[System, None, None]: server_auth_file = os.path.abspath(os.path.join(".", "server.htpasswd")) with open(server_auth_file, "w") as f: f.write("admin:$2y$05$e5sRb6NCcSH3YfbIxe1AGu2h5K7OOd982OXKmd8WyQ3DRQ4MvpnZS\n") @@ -312,13 +332,23 @@ def system_fixtures() -> List[Callable[[], Generator[System, None, None]]]: def system_fixtures_auth() -> List[Callable[[], Generator[System, None, None]]]: fixtures = [ - fastapi_server_auth_param, - fastapi_server_auth_file, - fastapi_server_auth_shorthand, + fastapi_server_basic_auth_param, + fastapi_server_basic_auth_file, + fastapi_server_basic_auth_shorthand, ] return fixtures +def system_fixtures_wrong_auth() -> List[Callable[[], Generator[System, None, None]]]: + fixtures = [fastapi_server_basic_auth_invalid_cred] + return fixtures + + +@pytest.fixture(scope="module", params=system_fixtures_wrong_auth()) +def system_wrong_auth(request: pytest.FixtureRequest) -> Generator[API, None, None]: + yield next(request.param()) + + @pytest.fixture(scope="module", params=system_fixtures()) def system(request: pytest.FixtureRequest) -> Generator[API, None, None]: yield next(request.param()) @@ -338,10 +368,10 @@ def api(system: System) -> Generator[API, None, None]: @pytest.fixture(scope="function") def api_wrong_cred( - fastapi_server_auth_invalid_cred: System, + system_wrong_auth: System, ) -> Generator[API, None, None]: - fastapi_server_auth_invalid_cred.reset_state() - api = fastapi_server_auth_invalid_cred.instance(API) + system_wrong_auth.reset_state() + api = system_wrong_auth.instance(API) yield api diff --git a/docker-compose.yml b/docker-compose.yml index ba95516721c0..1d78773052bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: - IS_PERSISTENT=TRUE - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=${CHROMA_SERVER_AUTH_CREDENTIALS_FILE} + - CHROMA_SERVER_AUTH_CREDENTIALS=${CHROMA_SERVER_AUTH_CREDENTIALS} - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=${CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER} ports: - 8000:8000 diff --git a/examples/basic_functionality/client_auth.ipynb b/examples/basic_functionality/client_auth.ipynb index 0fdb82e7af8c..b856a9f95821 100644 --- a/examples/basic_functionality/client_auth.ipynb +++ b/examples/basic_functionality/client_auth.ipynb @@ -135,7 +135,7 @@ { "cell_type": "markdown", "source": [ - "### Basic Authentication" + "## Basic Authentication" ], "metadata": { "collapsed": false @@ -160,7 +160,8 @@ "from chromadb import Settings\n", "\n", "client = chromadb.HttpClient(\n", - " settings=Settings(chroma_client_auth_provider=\"chromadb.auth.basic.BasicAuthClientProvider\",chroma_client_auth_credentials=\"admin:admin\"))\n", + " settings=Settings(chroma_client_auth_provider=\"chromadb.auth.basic.BasicAuthClientProvider\",\n", + " chroma_client_auth_credentials=\"admin:admin\"))\n", "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", "\n", "client.get_version() # this should work with or without authentication - it is a public endpoint\n", @@ -221,18 +222,152 @@ }, "id": "c0c3240ed4d70a79" }, + { + "cell_type": "markdown", + "source": [ + "## Token Authentication\n", + "\n", + "> Note: Tokens must be valid ASCII strings.\n", + "\n", + "### Default Token (`Authorization` with `Bearer`)" + ], + "metadata": { + "collapsed": false + }, + "id": "390aed41f019649b" + }, { "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], + "execution_count": 3, + "outputs": [ + { + "ename": "ConnectionError", + "evalue": "HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mConnectionRefusedError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:174\u001B[0m, in \u001B[0;36mHTTPConnection._new_conn\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 173\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 174\u001B[0m conn \u001B[38;5;241m=\u001B[39m \u001B[43mconnection\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mcreate_connection\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 175\u001B[0m \u001B[43m \u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_dns_host\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mport\u001B[49m\u001B[43m)\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtimeout\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mextra_kw\u001B[49m\n\u001B[1;32m 176\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 178\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m SocketTimeout:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/connection.py:95\u001B[0m, in \u001B[0;36mcreate_connection\u001B[0;34m(address, timeout, source_address, socket_options)\u001B[0m\n\u001B[1;32m 94\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m err \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[0;32m---> 95\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m err\n\u001B[1;32m 97\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m socket\u001B[38;5;241m.\u001B[39merror(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mgetaddrinfo returns an empty list\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/connection.py:85\u001B[0m, in \u001B[0;36mcreate_connection\u001B[0;34m(address, timeout, source_address, socket_options)\u001B[0m\n\u001B[1;32m 84\u001B[0m sock\u001B[38;5;241m.\u001B[39mbind(source_address)\n\u001B[0;32m---> 85\u001B[0m \u001B[43msock\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mconnect\u001B[49m\u001B[43m(\u001B[49m\u001B[43msa\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 86\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m sock\n", + "\u001B[0;31mConnectionRefusedError\u001B[0m: [Errno 61] Connection refused", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mNewConnectionError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:714\u001B[0m, in \u001B[0;36mHTTPConnectionPool.urlopen\u001B[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001B[0m\n\u001B[1;32m 713\u001B[0m \u001B[38;5;66;03m# Make the request on the httplib connection object.\u001B[39;00m\n\u001B[0;32m--> 714\u001B[0m httplib_response \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_make_request\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 715\u001B[0m \u001B[43m \u001B[49m\u001B[43mconn\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 716\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 717\u001B[0m \u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 718\u001B[0m \u001B[43m \u001B[49m\u001B[43mtimeout\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtimeout_obj\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 719\u001B[0m \u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 720\u001B[0m \u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 721\u001B[0m \u001B[43m \u001B[49m\u001B[43mchunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mchunked\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 722\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 724\u001B[0m \u001B[38;5;66;03m# If we're going to release the connection in ``finally:``, then\u001B[39;00m\n\u001B[1;32m 725\u001B[0m \u001B[38;5;66;03m# the response doesn't need to know about the connection. Otherwise\u001B[39;00m\n\u001B[1;32m 726\u001B[0m \u001B[38;5;66;03m# it will also try to release it and we'll have a double-release\u001B[39;00m\n\u001B[1;32m 727\u001B[0m \u001B[38;5;66;03m# mess.\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:415\u001B[0m, in \u001B[0;36mHTTPConnectionPool._make_request\u001B[0;34m(self, conn, method, url, timeout, chunked, **httplib_request_kw)\u001B[0m\n\u001B[1;32m 414\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[0;32m--> 415\u001B[0m \u001B[43mconn\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mhttplib_request_kw\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 417\u001B[0m \u001B[38;5;66;03m# We are swallowing BrokenPipeError (errno.EPIPE) since the server is\u001B[39;00m\n\u001B[1;32m 418\u001B[0m \u001B[38;5;66;03m# legitimately able to close the connection after sending a valid response.\u001B[39;00m\n\u001B[1;32m 419\u001B[0m \u001B[38;5;66;03m# With this behaviour, the received response is still readable.\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:244\u001B[0m, in \u001B[0;36mHTTPConnection.request\u001B[0;34m(self, method, url, body, headers)\u001B[0m\n\u001B[1;32m 243\u001B[0m headers[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mUser-Agent\u001B[39m\u001B[38;5;124m\"\u001B[39m] \u001B[38;5;241m=\u001B[39m _get_default_user_agent()\n\u001B[0;32m--> 244\u001B[0m \u001B[38;5;28;43msuper\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mHTTPConnection\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1282\u001B[0m, in \u001B[0;36mHTTPConnection.request\u001B[0;34m(self, method, url, body, headers, encode_chunked)\u001B[0m\n\u001B[1;32m 1281\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Send a complete request to the server.\"\"\"\u001B[39;00m\n\u001B[0;32m-> 1282\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_send_request\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1328\u001B[0m, in \u001B[0;36mHTTPConnection._send_request\u001B[0;34m(self, method, url, body, headers, encode_chunked)\u001B[0m\n\u001B[1;32m 1327\u001B[0m body \u001B[38;5;241m=\u001B[39m _encode(body, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mbody\u001B[39m\u001B[38;5;124m'\u001B[39m)\n\u001B[0;32m-> 1328\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mendheaders\u001B[49m\u001B[43m(\u001B[49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1277\u001B[0m, in \u001B[0;36mHTTPConnection.endheaders\u001B[0;34m(self, message_body, encode_chunked)\u001B[0m\n\u001B[1;32m 1276\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m CannotSendHeader()\n\u001B[0;32m-> 1277\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_send_output\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmessage_body\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mencode_chunked\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mencode_chunked\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:1037\u001B[0m, in \u001B[0;36mHTTPConnection._send_output\u001B[0;34m(self, message_body, encode_chunked)\u001B[0m\n\u001B[1;32m 1036\u001B[0m \u001B[38;5;28;01mdel\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_buffer[:]\n\u001B[0;32m-> 1037\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mmsg\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 1039\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m message_body \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m 1040\u001B[0m \n\u001B[1;32m 1041\u001B[0m \u001B[38;5;66;03m# create a consistent interface to message_body\u001B[39;00m\n", + "File \u001B[0;32m~/.pyenv/versions/3.10.10/lib/python3.10/http/client.py:975\u001B[0m, in \u001B[0;36mHTTPConnection.send\u001B[0;34m(self, data)\u001B[0m\n\u001B[1;32m 974\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mauto_open:\n\u001B[0;32m--> 975\u001B[0m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mconnect\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 976\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:205\u001B[0m, in \u001B[0;36mHTTPConnection.connect\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 204\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mconnect\u001B[39m(\u001B[38;5;28mself\u001B[39m):\n\u001B[0;32m--> 205\u001B[0m conn \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_new_conn\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 206\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_prepare_conn(conn)\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connection.py:186\u001B[0m, in \u001B[0;36mHTTPConnection._new_conn\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 185\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m SocketError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[0;32m--> 186\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m NewConnectionError(\n\u001B[1;32m 187\u001B[0m \u001B[38;5;28mself\u001B[39m, \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mFailed to establish a new connection: \u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;124m\"\u001B[39m \u001B[38;5;241m%\u001B[39m e\n\u001B[1;32m 188\u001B[0m )\n\u001B[1;32m 190\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m conn\n", + "\u001B[0;31mNewConnectionError\u001B[0m: : Failed to establish a new connection: [Errno 61] Connection refused", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mMaxRetryError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/adapters.py:489\u001B[0m, in \u001B[0;36mHTTPAdapter.send\u001B[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001B[0m\n\u001B[1;32m 488\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m chunked:\n\u001B[0;32m--> 489\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[43mconn\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43murlopen\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 490\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 491\u001B[0m \u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 492\u001B[0m \u001B[43m \u001B[49m\u001B[43mbody\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbody\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 493\u001B[0m \u001B[43m \u001B[49m\u001B[43mheaders\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mheaders\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 494\u001B[0m \u001B[43m \u001B[49m\u001B[43mredirect\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 495\u001B[0m \u001B[43m \u001B[49m\u001B[43massert_same_host\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 496\u001B[0m \u001B[43m \u001B[49m\u001B[43mpreload_content\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 497\u001B[0m \u001B[43m \u001B[49m\u001B[43mdecode_content\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43;01mFalse\u001B[39;49;00m\u001B[43m,\u001B[49m\n\u001B[1;32m 498\u001B[0m \u001B[43m \u001B[49m\u001B[43mretries\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmax_retries\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 499\u001B[0m \u001B[43m \u001B[49m\u001B[43mtimeout\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mtimeout\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 500\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 502\u001B[0m \u001B[38;5;66;03m# Send the request.\u001B[39;00m\n\u001B[1;32m 503\u001B[0m \u001B[38;5;28;01melse\u001B[39;00m:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/connectionpool.py:798\u001B[0m, in \u001B[0;36mHTTPConnectionPool.urlopen\u001B[0;34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, **response_kw)\u001B[0m\n\u001B[1;32m 796\u001B[0m e \u001B[38;5;241m=\u001B[39m ProtocolError(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mConnection aborted.\u001B[39m\u001B[38;5;124m\"\u001B[39m, e)\n\u001B[0;32m--> 798\u001B[0m retries \u001B[38;5;241m=\u001B[39m \u001B[43mretries\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mincrement\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 799\u001B[0m \u001B[43m \u001B[49m\u001B[43mmethod\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43merror\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43me\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43m_pool\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43m_stacktrace\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43msys\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mexc_info\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m[\u001B[49m\u001B[38;5;241;43m2\u001B[39;49m\u001B[43m]\u001B[49m\n\u001B[1;32m 800\u001B[0m \u001B[43m\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 801\u001B[0m retries\u001B[38;5;241m.\u001B[39msleep()\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/urllib3/util/retry.py:592\u001B[0m, in \u001B[0;36mRetry.increment\u001B[0;34m(self, method, url, response, error, _pool, _stacktrace)\u001B[0m\n\u001B[1;32m 591\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m new_retry\u001B[38;5;241m.\u001B[39mis_exhausted():\n\u001B[0;32m--> 592\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m MaxRetryError(_pool, url, error \u001B[38;5;129;01mor\u001B[39;00m ResponseError(cause))\n\u001B[1;32m 594\u001B[0m log\u001B[38;5;241m.\u001B[39mdebug(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mIncremented Retry for (url=\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;132;01m%s\u001B[39;00m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124m): \u001B[39m\u001B[38;5;132;01m%r\u001B[39;00m\u001B[38;5;124m\"\u001B[39m, url, new_retry)\n", + "\u001B[0;31mMaxRetryError\u001B[0m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mConnectionError\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[3], line 6\u001B[0m\n\u001B[1;32m 2\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mchromadb\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m Settings\n\u001B[1;32m 4\u001B[0m client \u001B[38;5;241m=\u001B[39m chromadb\u001B[38;5;241m.\u001B[39mHttpClient(\n\u001B[1;32m 5\u001B[0m settings\u001B[38;5;241m=\u001B[39mSettings(chroma_client_auth_provider\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtoken\u001B[39m\u001B[38;5;124m\"\u001B[39m, chroma_client_auth_credentials\u001B[38;5;241m=\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtest-token\u001B[39m\u001B[38;5;124m\"\u001B[39m))\n\u001B[0;32m----> 6\u001B[0m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mheartbeat\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 8\u001B[0m client\u001B[38;5;241m.\u001B[39mget_version() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 10\u001B[0m client\u001B[38;5;241m.\u001B[39mlist_collections() \u001B[38;5;66;03m# this is a protected endpoint and requires authentication\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:84\u001B[0m, in \u001B[0;36mFastAPI.heartbeat\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 81\u001B[0m \u001B[38;5;129m@override\u001B[39m\n\u001B[1;32m 82\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mheartbeat\u001B[39m(\u001B[38;5;28mself\u001B[39m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[1;32m 83\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Returns the current server time in nanoseconds to check if the server is alive\"\"\"\u001B[39;00m\n\u001B[0;32m---> 84\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_session\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mget\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_api_url\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 85\u001B[0m raise_chroma_error(resp)\n\u001B[1;32m 86\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mint\u001B[39m(resp\u001B[38;5;241m.\u001B[39mjson()[\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mnanosecond heartbeat\u001B[39m\u001B[38;5;124m\"\u001B[39m])\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:600\u001B[0m, in \u001B[0;36mSession.get\u001B[0;34m(self, url, **kwargs)\u001B[0m\n\u001B[1;32m 592\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124mr\u001B[39m\u001B[38;5;124;03m\"\"\"Sends a GET request. Returns :class:`Response` object.\u001B[39;00m\n\u001B[1;32m 593\u001B[0m \n\u001B[1;32m 594\u001B[0m \u001B[38;5;124;03m:param url: URL for the new :class:`Request` object.\u001B[39;00m\n\u001B[1;32m 595\u001B[0m \u001B[38;5;124;03m:param \\*\\*kwargs: Optional arguments that ``request`` takes.\u001B[39;00m\n\u001B[1;32m 596\u001B[0m \u001B[38;5;124;03m:rtype: requests.Response\u001B[39;00m\n\u001B[1;32m 597\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 599\u001B[0m kwargs\u001B[38;5;241m.\u001B[39msetdefault(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mallow_redirects\u001B[39m\u001B[38;5;124m\"\u001B[39m, \u001B[38;5;28;01mTrue\u001B[39;00m)\n\u001B[0;32m--> 600\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mrequest\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;124;43mGET\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43murl\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:587\u001B[0m, in \u001B[0;36mSession.request\u001B[0;34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001B[0m\n\u001B[1;32m 582\u001B[0m send_kwargs \u001B[38;5;241m=\u001B[39m {\n\u001B[1;32m 583\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mtimeout\u001B[39m\u001B[38;5;124m\"\u001B[39m: timeout,\n\u001B[1;32m 584\u001B[0m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mallow_redirects\u001B[39m\u001B[38;5;124m\"\u001B[39m: allow_redirects,\n\u001B[1;32m 585\u001B[0m }\n\u001B[1;32m 586\u001B[0m send_kwargs\u001B[38;5;241m.\u001B[39mupdate(settings)\n\u001B[0;32m--> 587\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mprep\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43msend_kwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 589\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m resp\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/auth/providers.py:123\u001B[0m, in \u001B[0;36mRequestsClientAuthProtocolAdapter._Session.send\u001B[0;34m(self, request, **kwargs)\u001B[0m\n\u001B[1;32m 118\u001B[0m \u001B[38;5;129m@override\u001B[39m\n\u001B[1;32m 119\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21msend\u001B[39m(\n\u001B[1;32m 120\u001B[0m \u001B[38;5;28mself\u001B[39m, request: requests\u001B[38;5;241m.\u001B[39mPreparedRequest, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs: Any\n\u001B[1;32m 121\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m requests\u001B[38;5;241m.\u001B[39mResponse:\n\u001B[1;32m 122\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_protocol_adapter\u001B[38;5;241m.\u001B[39minject_credentials(request)\n\u001B[0;32m--> 123\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43msuper\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mrequest\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/sessions.py:701\u001B[0m, in \u001B[0;36mSession.send\u001B[0;34m(self, request, **kwargs)\u001B[0m\n\u001B[1;32m 698\u001B[0m start \u001B[38;5;241m=\u001B[39m preferred_clock()\n\u001B[1;32m 700\u001B[0m \u001B[38;5;66;03m# Send the request\u001B[39;00m\n\u001B[0;32m--> 701\u001B[0m r \u001B[38;5;241m=\u001B[39m \u001B[43madapter\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msend\u001B[49m\u001B[43m(\u001B[49m\u001B[43mrequest\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 703\u001B[0m \u001B[38;5;66;03m# Total elapsed time of the request (approximately)\u001B[39;00m\n\u001B[1;32m 704\u001B[0m elapsed \u001B[38;5;241m=\u001B[39m preferred_clock() \u001B[38;5;241m-\u001B[39m start\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/adapters.py:565\u001B[0m, in \u001B[0;36mHTTPAdapter.send\u001B[0;34m(self, request, stream, timeout, verify, cert, proxies)\u001B[0m\n\u001B[1;32m 561\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(e\u001B[38;5;241m.\u001B[39mreason, _SSLError):\n\u001B[1;32m 562\u001B[0m \u001B[38;5;66;03m# This branch is for urllib3 v1.22 and later.\u001B[39;00m\n\u001B[1;32m 563\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m SSLError(e, request\u001B[38;5;241m=\u001B[39mrequest)\n\u001B[0;32m--> 565\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mConnectionError\u001B[39;00m(e, request\u001B[38;5;241m=\u001B[39mrequest)\n\u001B[1;32m 567\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m ClosedPoolError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[1;32m 568\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mConnectionError\u001B[39;00m(e, request\u001B[38;5;241m=\u001B[39mrequest)\n", + "\u001B[0;31mConnectionError\u001B[0m: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/v1 (Caused by NewConnectionError(': Failed to establish a new connection: [Errno 61] Connection refused'))" + ] + } + ], + "source": [ + "import chromadb\n", + "from chromadb import Settings\n", + "\n", + "client = chromadb.HttpClient(\n", + " settings=Settings(chroma_client_auth_provider=\"token\", chroma_client_auth_credentials=\"test-token\"))\n", + "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.get_version() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.list_collections() # this is a protected endpoint and requires authentication\n" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-28T16:44:30.289045Z", + "start_time": "2023-08-28T16:44:29.878090Z" + } + }, + "id": "b218beb03ae1582e" + }, + { + "cell_type": "markdown", + "source": [ + "### X-Chroma-Token" + ], + "metadata": { + "collapsed": false + }, + "id": "c8234687c5afe521" + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "ename": "Exception", + "evalue": "{\"error\":\"Unauthorized\"}", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mHTTPError\u001B[0m Traceback (most recent call last)", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:410\u001B[0m, in \u001B[0;36mraise_chroma_error\u001B[0;34m(resp)\u001B[0m\n\u001B[1;32m 409\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 410\u001B[0m \u001B[43mresp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mraise_for_status\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 411\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m requests\u001B[38;5;241m.\u001B[39mHTTPError:\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/venv/lib/python3.10/site-packages/requests/models.py:1021\u001B[0m, in \u001B[0;36mResponse.raise_for_status\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 1020\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m http_error_msg:\n\u001B[0;32m-> 1021\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m HTTPError(http_error_msg, response\u001B[38;5;241m=\u001B[39m\u001B[38;5;28mself\u001B[39m)\n", + "\u001B[0;31mHTTPError\u001B[0m: 401 Client Error: Unauthorized for url: http://localhost:8000/api/v1/collections", + "\nDuring handling of the above exception, another exception occurred:\n", + "\u001B[0;31mException\u001B[0m Traceback (most recent call last)", + "Cell \u001B[0;32mIn[2], line 11\u001B[0m\n\u001B[1;32m 7\u001B[0m client\u001B[38;5;241m.\u001B[39mheartbeat() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[1;32m 9\u001B[0m client\u001B[38;5;241m.\u001B[39mget_version() \u001B[38;5;66;03m# this should work with or without authentication - it is a public endpoint\u001B[39;00m\n\u001B[0;32m---> 11\u001B[0m \u001B[43mclient\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mlist_collections\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m \u001B[38;5;66;03m# this is a protected endpoint and requires authentication\u001B[39;00m\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:92\u001B[0m, in \u001B[0;36mFastAPI.list_collections\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 90\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Returns a list of all collections\"\"\"\u001B[39;00m\n\u001B[1;32m 91\u001B[0m resp \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_session\u001B[38;5;241m.\u001B[39mget(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_api_url \u001B[38;5;241m+\u001B[39m \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m/collections\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m---> 92\u001B[0m \u001B[43mraise_chroma_error\u001B[49m\u001B[43m(\u001B[49m\u001B[43mresp\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 93\u001B[0m json_collections \u001B[38;5;241m=\u001B[39m resp\u001B[38;5;241m.\u001B[39mjson()\n\u001B[1;32m 94\u001B[0m collections \u001B[38;5;241m=\u001B[39m []\n", + "File \u001B[0;32m~/PycharmProjects/chroma-core/chromadb/api/fastapi.py:412\u001B[0m, in \u001B[0;36mraise_chroma_error\u001B[0;34m(resp)\u001B[0m\n\u001B[1;32m 410\u001B[0m resp\u001B[38;5;241m.\u001B[39mraise_for_status()\n\u001B[1;32m 411\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m requests\u001B[38;5;241m.\u001B[39mHTTPError:\n\u001B[0;32m--> 412\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m (\u001B[38;5;167;01mException\u001B[39;00m(resp\u001B[38;5;241m.\u001B[39mtext))\n", + "\u001B[0;31mException\u001B[0m: {\"error\":\"Unauthorized\"}" + ] + } + ], + "source": [ + "import chromadb\n", + "from chromadb import Settings\n", + "\n", + "client = chromadb.HttpClient(\n", + " settings=Settings(chroma_client_auth_provider=\"token\", chroma_client_auth_credentials=\"test-token\",\n", + " chroma_client_auth_token_transport_header=\"X_CHROMA_TOKEN\"))\n", + "client.heartbeat() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.get_version() # this should work with or without authentication - it is a public endpoint\n", + "\n", + "client.list_collections() # this is a protected endpoint and requires authentication" + ], "metadata": { "collapsed": false, "ExecuteTime": { - "start_time": "2023-08-11T15:58:07.272237Z" + "end_time": "2023-08-28T14:25:12.858416Z", + "start_time": "2023-08-28T14:25:12.629618Z" } }, - "id": "ab8b90d83f02eda" + "id": "93485c3175d1e2c7" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "29d28a25e85f95af" } ], "metadata": { From 4332adfde59e9f2dff6e436218c47c51628a32c6 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Mon, 28 Aug 2023 19:04:51 -0700 Subject: [PATCH 08/67] Release 0.4.8 (#1056) Release 0.4.8 --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 3b0a96f5bdab..0f5c86300264 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.7" +__version__ = "0.4.8" # Workaround to deal with Colab's old sqlite3 version try: From d9a8109d23e62531dd0c389d0758ac767337900c Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 29 Aug 2023 20:18:53 +0300 Subject: [PATCH 09/67] [ENH] Added providers to onnx runtime session (#1006) Refs: https://onnxruntime.ai/docs/api/python/api_summary.html Refs: #1004 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - In multi-provider envs Onnyx Runtime requires that providers are specified (with order of preference) as input to the InferrenceSession ## Test plan Ideally, we'll need a GPU runners for testing - https://github.com/github/roadmap/issues/505 ## Documentation Changes No change to docs is required. > Note: Sorry for the unsigned commit, I tried using GH Codespaces for this one. --- chromadb/test/ef/test_default_ef.py | 62 +++++++++++++++++++++++++++ chromadb/utils/embedding_functions.py | 36 +++++++++++++++- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 chromadb/test/ef/test_default_ef.py diff --git a/chromadb/test/ef/test_default_ef.py b/chromadb/test/ef/test_default_ef.py new file mode 100644 index 000000000000..e1ed55206602 --- /dev/null +++ b/chromadb/test/ef/test_default_ef.py @@ -0,0 +1,62 @@ +from typing import List, Hashable + +import hypothesis.strategies as st +import onnxruntime +import pytest +from hypothesis import given + +from chromadb.utils.embedding_functions import ONNXMiniLM_L6_V2 + + +def unique_by(x: Hashable) -> Hashable: + return x + + +@given( + providers=st.lists( + st.sampled_from(onnxruntime.get_all_providers()).filter( + lambda x: x not in onnxruntime.get_available_providers() + ), + unique_by=unique_by, + min_size=1, + ) +) +def test_unavailable_provider_multiple(providers: List[str]) -> None: + with pytest.raises(ValueError) as e: + ef = ONNXMiniLM_L6_V2(preferred_providers=providers) + ef(["test"]) + assert "Preferred providers must be subset of available providers" in str(e.value) + + +@given( + providers=st.lists( + st.sampled_from(onnxruntime.get_all_providers()).filter( + lambda x: x in onnxruntime.get_available_providers() + ), + min_size=1, + unique_by=unique_by, + ) +) +def test_available_provider(providers: List[str]) -> None: + ef = ONNXMiniLM_L6_V2(preferred_providers=providers) + ef(["test"]) + + +def test_warning_no_providers_supplied() -> None: + ef = ONNXMiniLM_L6_V2() + ef(["test"]) + + +@given( + providers=st.lists( + st.sampled_from(onnxruntime.get_all_providers()).filter( + lambda x: x in onnxruntime.get_available_providers() + ), + min_size=1, + ).filter(lambda x: len(x) > len(set(x))) +) +def test_provider_repeating(providers: List[str]) -> None: + with pytest.raises(ValueError) as e: + ef = ONNXMiniLM_L6_V2(preferred_providers=providers) + ef(["test"]) + assert "Preferred providers must be unique" in str(e.value) diff --git a/chromadb/utils/embedding_functions.py b/chromadb/utils/embedding_functions.py index c51995c9460d..124213c365b1 100644 --- a/chromadb/utils/embedding_functions.py +++ b/chromadb/utils/embedding_functions.py @@ -1,3 +1,5 @@ +import logging + from chromadb.api.types import Documents, EmbeddingFunction, Embeddings from pathlib import Path import os @@ -14,6 +16,8 @@ except ImportError: is_thin_client = False +logger = logging.getLogger(__name__) + class SentenceTransformerEmbeddingFunction(EmbeddingFunction): # Since we do dynamic imports we have to type this as Any @@ -244,9 +248,20 @@ class ONNXMiniLM_L6_V2(EmbeddingFunction): # https://github.com/python/mypy/issues/7291 mypy makes you type the constructor if # no args - def __init__(self) -> None: + def __init__(self, preferred_providers: Optional[List[str]] = None) -> None: # Import dependencies on demand to mirror other embedding functions. This # breaks typechecking, thus the ignores. + # convert the list to set for unique values + if preferred_providers and not all( + [isinstance(i, str) for i in preferred_providers] + ): + raise ValueError("Preferred providers must be a list of strings") + # check for duplicate providers + if preferred_providers and len(preferred_providers) != len( + set(preferred_providers) + ): + raise ValueError("Preferred providers must be unique") + self._preferred_providers = preferred_providers try: # Equivalent to import onnxruntime self.ort = importlib.import_module("onnxruntime") @@ -334,10 +349,27 @@ def _init_model_and_tokenizer(self) -> None: # https://github.com/UKPLab/sentence-transformers/blob/3e1929fddef16df94f8bc6e3b10598a98f46e62d/docs/_static/html/models_en_sentence_embeddings.html#LL480 self.tokenizer.enable_truncation(max_length=256) self.tokenizer.enable_padding(pad_id=0, pad_token="[PAD]", length=256) + + if self._preferred_providers is None or len(self._preferred_providers) == 0: + if len(self.ort.get_available_providers()) > 0: + logger.debug( + f"WARNING: No ONNX providers provided, defaulting to available providers: " + f"{self.ort.get_available_providers()}" + ) + self._preferred_providers = self.ort.get_available_providers() + elif not set(self._preferred_providers).issubset( + set(self.ort.get_available_providers()) + ): + raise ValueError( + f"Preferred providers must be subset of available providers: {self.ort.get_available_providers()}" + ) self.model = self.ort.InferenceSession( os.path.join( self.DOWNLOAD_PATH, self.EXTRACTED_FOLDER_NAME, "model.onnx" - ) + ), + # Since 1.9 onnyx runtime requires providers to be specified when there are multiple available - https://onnxruntime.ai/docs/api/python/api_summary.html + # This is probably not ideal but will improve DX as no exceptions will be raised in multi-provider envs + providers=self._preferred_providers, ) def __call__(self, texts: Documents) -> Embeddings: From 59761c872c4e0046d4ab2af3415641418ffbc278 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Tue, 29 Aug 2023 12:29:19 -0700 Subject: [PATCH 10/67] multiplatform docker release builds (#857) Currently our ghrc (and future dockerhub) builds will not run on Mac. This should build `linux/amd64` and `linux/arm64` in serial. In the future, we can add ` linux/amd64/v2, linux/amd64/v3` and parallelize the builds. https://docs.docker.com/build/ci/github-actions/multi-platform/ Plan 1. let tests pass 2. merge into `main` and see if `latest` release branch works --- .github/workflows/chroma-release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index 6c747637af87..7533cf4b48fc 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -10,6 +10,7 @@ on: env: REGISTRY: ghcr.io IMAGE_NAME: "ghcr.io/chroma-core/chroma" + PLATFORMS: linux/amd64,linux/arm64 #linux/riscv64, linux/arm/v7 jobs: build-and-release: @@ -26,6 +27,13 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 0 + # https://github.com/docker/setup-qemu-action - for multiplatform builds + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + # https://github.com/docker/setup-buildx-action - for multiplatform builds + - name: Set up Docker Buildx + id: buildx + uses: docker/setup-buildx-action@v2 - name: Set up Python uses: actions/setup-python@v4 with: @@ -52,6 +60,7 @@ jobs: uses: docker/build-push-action@v3.2.0 with: context: . + platforms: ${{ env.PLATFORMS }} push: true tags: ${{ steps.tag.outputs.tag_name}} - name: Build and push release Docker image @@ -59,6 +68,7 @@ jobs: uses: docker/build-push-action@v3.2.0 with: context: . + platforms: ${{ env.PLATFORMS }} push: true tags: "${{ steps.tag.outputs.tag_name }},${{ env.IMAGE_NAME }}:latest" - name: Get Release Version From ffff00bb9c23eb60ef472c9c54b856a8473d8f7d Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 30 Aug 2023 20:29:20 +0300 Subject: [PATCH 11/67] [ENH]: JS Client Support for Auth (#1050) Refs: #1026 ## Description of changes *Summarize the changes made by this PR.* - New functionality - Auth abstractions - Basic auth supported ## Test plan *How are these changes tested?* - [x] Tests pass locally with `yarn test` for js (Regression) - [x] Add new tests for auth ## Documentation Changes Yes docs need to be updated to provide information how to configure auth with the JS client. --- bin/integration-test | 20 +++ clients/js/package.json | 11 +- clients/js/src/ChromaClient.ts | 22 ++- clients/js/src/auth.ts | 228 ++++++++++++++++++++++++++ clients/js/test/auth.basic.test.ts | 33 ++++ clients/js/test/initClientWithAuth.ts | 7 + docker-compose.test-auth.yml | 32 ++++ 7 files changed, 345 insertions(+), 8 deletions(-) create mode 100644 clients/js/src/auth.ts create mode 100644 clients/js/test/auth.basic.test.ts create mode 100644 clients/js/test/initClientWithAuth.ts create mode 100644 docker-compose.test-auth.yml diff --git a/bin/integration-test b/bin/integration-test index 1432a96656ab..e91e45e93c58 100755 --- a/bin/integration-test +++ b/bin/integration-test @@ -6,6 +6,18 @@ export CHROMA_PORT=8000 function cleanup { docker compose -f docker-compose.test.yml down --rmi local --volumes + rm server.htpasswd .chroma_env +} + +function setup_basic_auth { + # Generate htpasswd file + docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin admin > server.htpasswd + # Create .chroma_env file + cat < .chroma_env +CHROMA_SERVER_AUTH_CREDENTIALS_FILE="/chroma/server.htpasswd" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' +EOF } trap cleanup EXIT @@ -23,4 +35,12 @@ python -m pytest "$@" cd clients/js yarn yarn test:run +docker compose down +cd ../.. +echo "Testing auth" +setup_basic_auth #this is specific to the auth type, later on we'll have other auth types +cd clients/js +# Start docker compose - this should be auth agnostic +docker compose --env-file ../../.chroma_env -f ../../docker-compose.test-auth.yml up --build -d +yarn test:run-auth cd ../.. diff --git a/clients/js/package.json b/clients/js/package.json index 663042b775ce..8ed595623b18 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -19,7 +19,7 @@ "typescript": "^5.0.4" }, "main": "dist/main/index.js", - "module": "dist/module/index.js", + "module": "dist/module/index.js", "exports": { "require": "./dist/main/index.js", "import": "./dist/module/index.js" @@ -29,13 +29,16 @@ "dist" ], "scripts": { - "test": "run-s db:clean db:run test:runfull db:clean", + "test": "run-s db:clean db:run test:runfull db:clean db:run-auth test:runfull-authonly db:clean", "test:set-port": "cross-env URL=localhost:8001", - "test:run": "jest --runInBand", - "test:runfull": "PORT=8001 jest --runInBand", + "test:run": "jest --runInBand --testPathIgnorePatterns=test/auth.basic.test.ts", + "test:run-auth": "jest --runInBand --testPathPattern=test/auth.basic.test.ts", + "test:runfull": "PORT=8001 jest --runInBand --testPathIgnorePatterns=test/auth.basic.test.ts", + "test:runfull-authonly": "PORT=8001 jest --runInBand --testPathPattern=test/auth.basic.test.ts", "test:update": "run-s db:clean db:run && jest --runInBand --updateSnapshot && run-s db:clean", "db:clean": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml down --volumes", "db:run": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml up --detach && sleep 5", + "db:run-auth": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test-auth.yml up --detach && sleep 5", "clean": "rimraf dist", "build": "run-s clean build:*", "build:main": "tsc -p tsconfig.json", diff --git a/clients/js/src/ChromaClient.ts b/clients/js/src/ChromaClient.ts index 8278c41a17ad..a5779dbba1ea 100644 --- a/clients/js/src/ChromaClient.ts +++ b/clients/js/src/ChromaClient.ts @@ -1,8 +1,13 @@ import { IEmbeddingFunction } from './embeddings/IEmbeddingFunction'; -import { Configuration, ApiApi as DefaultApi, Api } from "./generated"; +import { Configuration, ApiApi as DefaultApi } from "./generated"; import { handleSuccess, handleError } from "./utils"; import { Collection } from './Collection'; import { CollectionMetadata, CollectionType, ConfigOptions } from './types'; +import { + AuthOptions, + ClientAuthProtocolAdapter, + IsomorphicFetchClientAuthProtocolAdapter +} from "./auth"; export class ChromaClient { @@ -10,6 +15,7 @@ export class ChromaClient { * @ignore */ private api: DefaultApi & ConfigOptions; + private apiAdapter: ClientAuthProtocolAdapter|undefined; /** * Creates a new ChromaClient instance. @@ -26,16 +32,24 @@ export class ChromaClient { */ constructor({ path, - fetchOptions + fetchOptions, + auth, }: { path?: string, - fetchOptions?: RequestInit + fetchOptions?: RequestInit, + auth?: AuthOptions, } = {}) { if (path === undefined) path = "http://localhost:8000"; const apiConfig: Configuration = new Configuration({ basePath: path, }); - this.api = new DefaultApi(apiConfig); + if (auth !== undefined) { + this.apiAdapter = new IsomorphicFetchClientAuthProtocolAdapter(new DefaultApi(apiConfig), auth); + this.api = this.apiAdapter.getApi(); + } else { + this.api = new DefaultApi(apiConfig); + } + this.api.options = fetchOptions ?? {}; } diff --git a/clients/js/src/auth.ts b/clients/js/src/auth.ts new file mode 100644 index 000000000000..e7626988f5d8 --- /dev/null +++ b/clients/js/src/auth.ts @@ -0,0 +1,228 @@ +import {ApiApi as DefaultApi} from "./generated"; + +export interface ClientAuthProvider { + /** + * Abstract method for authenticating a client. + */ + authenticate(): ClientAuthResponse; +} + +export interface ClientAuthConfigurationProvider { + /** + * Abstract method for getting the configuration for the client. + */ + getConfig(): T; +} + +export interface ClientAuthCredentialsProvider { + /** + * Abstract method for getting the credentials for the client. + * @param user + */ + getCredentials(user?: string): T; +} + +enum AuthInfoType { + COOKIE = "cookie", + HEADER = "header", + URL = "url", + METADATA = "metadata" + +} + +export interface ClientAuthResponse { + getAuthInfoType(): AuthInfoType; + + getAuthInfo(): { key: string, value: string }; +} + + +export interface AbstractCredentials { + getCredentials(): T; +} + +export interface ClientAuthProtocolAdapter { + injectCredentials(injectionContext: T): T; + + getApi(): any; +} + + +class SecretStr { + constructor(private readonly secret: string) { + } + + getSecret(): string { + return this.secret; + } +} + +const base64Encode = (str: string): string => { + return Buffer.from(str).toString('base64'); +}; + +class BasicAuthCredentials implements AbstractCredentials { + private readonly credentials: SecretStr; + + constructor(_creds: string) { + this.credentials = new SecretStr(base64Encode(_creds)) + } + + getCredentials(): SecretStr { + //encode base64 + return this.credentials; + } +} + + +class BasicAuthClientAuthResponse implements ClientAuthResponse { + constructor(private readonly credentials: BasicAuthCredentials) { + } + + getAuthInfo(): { key: string; value: string } { + return {key: "Authorization", value: "Basic " + this.credentials.getCredentials().getSecret()}; + } + + getAuthInfoType(): AuthInfoType { + return AuthInfoType.HEADER; + } +} + +export class BasicAuthCredentialsProvider implements ClientAuthCredentialsProvider { + private readonly credentials: BasicAuthCredentials; + + /** + * Creates a new BasicAuthCredentialsProvider. This provider loads credentials from provided text credentials or from the environment variable CHROMA_CLIENT_AUTH_CREDENTIALS. + * @param _creds - The credentials + * @throws {Error} If neither credentials provider or text credentials are supplied. + */ + + constructor(_creds: string | undefined) { + if (_creds === undefined && !process.env.CHROMA_CLIENT_AUTH_CREDENTIALS) throw new Error("Credentials must be supplied via environment variable (CHROMA_CLIENT_AUTH_CREDENTIALS) or passed in as configuration."); + this.credentials = new BasicAuthCredentials((_creds ?? process.env.CHROMA_CLIENT_AUTH_CREDENTIALS) as string); + } + + getCredentials(): BasicAuthCredentials { + return this.credentials; + } +} + +class BasicAuthClientAuthProvider implements ClientAuthProvider { + private readonly credentialsProvider: ClientAuthCredentialsProvider; + + /** + * Creates a new BasicAuthClientAuthProvider. + * @param options - The options for the authentication provider. + * @param options.textCredentials - The credentials for the authentication provider. + * @param options.credentialsProvider - The credentials provider for the authentication provider. + * @throws {Error} If neither credentials provider or text credentials are supplied. + */ + + constructor(options: { textCredentials: any; credentialsProvider: ClientAuthCredentialsProvider | undefined }) { + if (!options.credentialsProvider && !options.textCredentials) { + throw new Error("Either credentials provider or text credentials must be supplied."); + } + this.credentialsProvider = options.credentialsProvider || new BasicAuthCredentialsProvider(options.textCredentials); + } + + authenticate(): ClientAuthResponse { + return new BasicAuthClientAuthResponse(this.credentialsProvider.getCredentials()); + } +} + +export class IsomorphicFetchClientAuthProtocolAdapter implements ClientAuthProtocolAdapter { + authProvider: ClientAuthProvider | undefined; + wrapperApi: DefaultApi | undefined; + + /** + * Creates a new adapter of IsomorphicFetchClientAuthProtocolAdapter. + * @param api - The API to wrap. + * @param authConfiguration - The configuration for the authentication provider. + */ + + constructor(private api: DefaultApi, authConfiguration: AuthOptions) { + + switch (authConfiguration.provider) { + case "basic": + this.authProvider = new BasicAuthClientAuthProvider({textCredentials: authConfiguration.credentials, credentialsProvider: authConfiguration.credentialsProvider}); + break; + default: + this.authProvider = undefined; + break; + } + if (this.authProvider !== undefined) { + this.wrapperApi = this.wrapMethods(this.api); + } + } + + getApi(): DefaultApi { + return this.wrapperApi ?? this.api; + } + + getAllMethods(obj: any): string[] { + let methods: string[] = []; + let currentObj = obj; + + do { + const objMethods = Object.getOwnPropertyNames(currentObj) + .filter(name => typeof currentObj[name] === 'function' && name !== 'constructor'); + + methods = methods.concat(objMethods); + currentObj = Object.getPrototypeOf(currentObj); + } while (currentObj); + + return methods; + } + + wrapMethods(obj: any): any { + let self = this; + const methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(obj)) + .filter(name => typeof obj[name] === 'function' && name !== 'constructor'); + + return new Proxy(obj, { + get(target, prop: string) { + if (methodNames.includes(prop)) { + return new Proxy(target[prop], { + apply(fn, thisArg, args) { + const modifiedArgs = args.map(arg => { + if (arg && typeof arg === 'object' && 'method' in arg) { + return self.injectCredentials(arg as RequestInit); + } + return arg; + }); + if (Object.keys(modifiedArgs[modifiedArgs.length - 1]).length === 0) { + modifiedArgs[modifiedArgs.length - 1] = self.injectCredentials({} as RequestInit); + } else { + modifiedArgs[modifiedArgs.length - 1] = self.injectCredentials(modifiedArgs[modifiedArgs.length - 1] as RequestInit); + } + return fn.apply(thisArg, modifiedArgs); + } + }); + } + return target[prop]; + } + }); + } + + injectCredentials(injectionContext: RequestInit): RequestInit { + const authInfo = this.authProvider?.authenticate().getAuthInfo(); + if (authInfo) { + const {key, value} = authInfo; + injectionContext = { + ...injectionContext, + headers: { + [key]: value + }, + } + } + return injectionContext; + } +} + + +export type AuthOptions = { + provider: ClientAuthProvider | string | undefined, + credentialsProvider?: ClientAuthCredentialsProvider | undefined, + configProvider?: ClientAuthConfigurationProvider | undefined, + credentials?: any | undefined, +} diff --git a/clients/js/test/auth.basic.test.ts b/clients/js/test/auth.basic.test.ts new file mode 100644 index 000000000000..a698e02c2da6 --- /dev/null +++ b/clients/js/test/auth.basic.test.ts @@ -0,0 +1,33 @@ +import {expect, test} from "@jest/globals"; +import {ChromaClient} from "../src/ChromaClient"; +import chroma from "./initClientWithAuth"; +import chromaNoAuth from "./initClient"; + +test("it should get the version without auth needed", async () => { + const version = await chromaNoAuth.version(); + expect(version).toBeDefined(); + expect(version).toMatch(/^[0-9]+\.[0-9]+\.[0-9]+$/); +}); + +test("it should get the heartbeat without auth needed", async () => { + const heartbeat = await chromaNoAuth.heartbeat(); + expect(heartbeat).toBeDefined(); + expect(heartbeat).toBeGreaterThan(0); +}); + +test("it should raise error when non authenticated", async () => { + await expect(chromaNoAuth.listCollections()).rejects.toMatchObject({ + status: 401 + }); +}); + +test('it should list collections', async () => { + await chroma.reset() + let collections = await chroma.listCollections() + expect(collections).toBeDefined() + expect(collections).toBeInstanceOf(Array) + expect(collections.length).toBe(0) + const collection = await chroma.createCollection({name: "test"}); + collections = await chroma.listCollections() + expect(collections.length).toBe(1) +}) diff --git a/clients/js/test/initClientWithAuth.ts b/clients/js/test/initClientWithAuth.ts new file mode 100644 index 000000000000..b24c9a48d1d1 --- /dev/null +++ b/clients/js/test/initClientWithAuth.ts @@ -0,0 +1,7 @@ +import {ChromaClient} from "../src/ChromaClient"; + +const PORT = process.env.PORT || "8000"; +const URL = "http://localhost:" + PORT; +const chroma = new ChromaClient({path: URL, auth: {provider: "basic", credentials: "admin:admin"}}); + +export default chroma; diff --git a/docker-compose.test-auth.yml b/docker-compose.test-auth.yml new file mode 100644 index 000000000000..945739782a1a --- /dev/null +++ b/docker-compose.test-auth.yml @@ -0,0 +1,32 @@ +version: '3.9' + +networks: + test_net: + driver: bridge + +services: + test_server: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/chroma + - test_index_data:/index_data + command: uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml + environment: + - ANONYMIZED_TELEMETRY=False + - ALLOW_RESET=True + - IS_PERSISTENT=TRUE + - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=/chroma/server.htpasswd + - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider + - CHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.basic.BasicAuthServerProvider + ports: + - ${CHROMA_PORT}:8000 + networks: + - test_net + +volumes: + test_index_data: + driver: local + test_backups: + driver: local From a640e8aecc56a8afbc6ae04f7e6c71cea66d368a Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 30 Aug 2023 20:29:31 +0300 Subject: [PATCH 12/67] [ENH]: OpenAI npm package v4 breaking changes (#1039) Refs: #1038 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Added support for both v3 and v4 of the OpenAI node library ## Test plan `yarn test` ## Documentation Changes TBD --- .../src/embeddings/OpenAIEmbeddingFunction.ts | 121 ++++++++++++++---- clients/js/test/add.collections.test.ts | 24 +++- 2 files changed, 122 insertions(+), 23 deletions(-) diff --git a/clients/js/src/embeddings/OpenAIEmbeddingFunction.ts b/clients/js/src/embeddings/OpenAIEmbeddingFunction.ts index 334a5a03ef68..3a87ae1f9071 100644 --- a/clients/js/src/embeddings/OpenAIEmbeddingFunction.ts +++ b/clients/js/src/embeddings/OpenAIEmbeddingFunction.ts @@ -1,16 +1,83 @@ -import { IEmbeddingFunction } from "./IEmbeddingFunction"; +import {IEmbeddingFunction} from "./IEmbeddingFunction"; + let OpenAIApi: any; +let openAiVersion = null; +let openAiMajorVersion = null; + +interface OpenAIAPI { + createEmbedding: (params: { + model: string; + input: string[]; + user?: string; + }) => Promise; +} + +class OpenAIAPIv3 implements OpenAIAPI { + private readonly configuration: any; + private openai: any; + + constructor(configuration: { organization: string, apiKey: string }) { + this.configuration = new OpenAIApi.Configuration({ + organization: configuration.organization, + apiKey: configuration.apiKey, + }); + this.openai = new OpenAIApi.OpenAIApi(this.configuration); + } + + public async createEmbedding(params: { + model: string, + input: string[], + user?: string + }): Promise { + const embeddings: number[][] = []; + const response = await this.openai.createEmbedding({ + model: params.model, + input: params.input, + }).catch((error: any) => { + throw error; + }); + // @ts-ignore + const data = response.data["data"]; + for (let i = 0; i < data.length; i += 1) { + embeddings.push(data[i]["embedding"]); + } + return embeddings + } +} + +class OpenAIAPIv4 implements OpenAIAPI { + private readonly apiKey: any; + private openai: any; + + constructor(apiKey: any) { + this.apiKey = apiKey; + this.openai = new OpenAIApi({ + apiKey: this.apiKey, + }); + } + + public async createEmbedding(params: { + model: string, + input: string[], + user?: string + }): Promise { + const embeddings: number[][] = []; + const response = await this.openai.embeddings.create(params); + const data = response["data"]; + for (let i = 0; i < data.length; i += 1) { + embeddings.push(data[i]["embedding"]); + } + return embeddings + } +} export class OpenAIEmbeddingFunction implements IEmbeddingFunction { private api_key: string; private org_id: string; private model: string; + private openaiApi: OpenAIAPI; - constructor({ - openai_api_key, - openai_model, - openai_organization_id, - }: { + constructor({openai_api_key, openai_model, openai_organization_id}: { openai_api_key: string, openai_model?: string, openai_organization_id?: string @@ -18,31 +85,41 @@ export class OpenAIEmbeddingFunction implements IEmbeddingFunction { try { // eslint-disable-next-line global-require,import/no-extraneous-dependencies OpenAIApi = require("openai"); - } catch { - throw new Error( - "Please install the openai package to use the OpenAIEmbeddingFunction, `npm install -S openai`" - ); + let version = null; + try { + const {VERSION} = require('openai/version'); + version = VERSION; + } catch (e) { + version = "3.x"; + } + openAiVersion = version.replace(/[^0-9.]/g, ''); + openAiMajorVersion = openAiVersion.split('.')[0]; + } catch (_a) { + // @ts-ignore + if (_a.code === 'MODULE_NOT_FOUND') { + throw new Error("Please install the openai package to use the OpenAIEmbeddingFunction, `npm install -S openai`"); + } + throw _a; // Re-throw other errors } this.api_key = openai_api_key; this.org_id = openai_organization_id || ""; this.model = openai_model || "text-embedding-ada-002"; + if (openAiMajorVersion > 3) { + this.openaiApi = new OpenAIAPIv4(this.api_key); + } else { + this.openaiApi = new OpenAIAPIv3({ + organization: this.org_id, + apiKey: this.api_key, + }); + } } public async generate(texts: string[]): Promise { - const configuration = new OpenAIApi.Configuration({ - organization: this.org_id, - apiKey: this.api_key, - }); - const openai = new OpenAIApi.OpenAIApi(configuration); - const embeddings = []; - const response = await openai.createEmbedding({ + return await this.openaiApi.createEmbedding({ model: this.model, input: texts, + }).catch((error: any) => { + throw error; }); - const data = response.data["data"]; - for (let i = 0; i < data.length; i += 1) { - embeddings.push(data[i]["embedding"]); - } - return embeddings; } } diff --git a/clients/js/test/add.collections.test.ts b/clients/js/test/add.collections.test.ts index d18a8085bc9d..32f76c89a304 100644 --- a/clients/js/test/add.collections.test.ts +++ b/clients/js/test/add.collections.test.ts @@ -3,7 +3,7 @@ import chroma from './initClient' import { DOCUMENTS, EMBEDDINGS, IDS } from './data'; import { METADATAS } from './data'; import { IncludeEnum } from "../src/types"; - +import {OpenAIEmbeddingFunction} from "../src/embeddings/OpenAIEmbeddingFunction"; test("it should add single embeddings to a collection", async () => { await chroma.reset(); const collection = await chroma.createCollection({ name: "test" }); @@ -35,6 +35,28 @@ test("it should add batch embeddings to a collection", async () => { expect(res.embeddings).toEqual(EMBEDDINGS); // reverse because of the order of the ids }); + +if (!process.env.OPENAI_API_KEY) { + test.skip("it should add OpenAI embeddings", async () => { + }); +} else { + test("it should add OpenAI embeddings", async () => { + await chroma.reset(); + const embedder = new OpenAIEmbeddingFunction({ openai_api_key: process.env.OPENAI_API_KEY || "" }) + const collection = await chroma.createCollection({ name: "test" ,embeddingFunction: embedder}); + const embeddings = await embedder.generate(DOCUMENTS); + await collection.add({ ids: IDS, embeddings: embeddings }); + const count = await collection.count(); + expect(count).toBe(3); + var res = await collection.get({ + ids: IDS, include: [ + IncludeEnum.Embeddings, + ] + }); + expect(res.embeddings).toEqual(embeddings); // reverse because of the order of the ids + }); +} + test("add documents", async () => { await chroma.reset(); const collection = await chroma.createCollection({ name: "test" }); From 9d444d76e38cee87faaaafca1dadfa33e778f9d5 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Wed, 30 Aug 2023 16:31:02 -0700 Subject: [PATCH 13/67] [BUG] fix: numpy version compat for 3.7 vs > for cross_version_tests (#1064) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Newer versions of onnxruntime include a version of numpy that breaks the dep tree on old python versions. This boxes the nympy version appropriately. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None required. --- pyproject.toml | 3 ++- requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b7ae444bf96e..3d62663ca86b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,8 @@ dependencies = [ 'chroma-hnswlib==0.7.2', 'fastapi>=0.95.2, <0.100.0', 'uvicorn[standard] >= 0.18.3', - 'numpy >= 1.21.6', + 'numpy == 1.21.6; python_version < "3.8"', + 'numpy >= 1.22.5; python_version >= "3.8"', 'posthog >= 2.4.0', 'typing_extensions >= 4.5.0', 'pulsar-client>=3.1.0', diff --git a/requirements.txt b/requirements.txt index c23a24cafdb8..abf333a95c04 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,8 @@ chroma-hnswlib==0.7.2 fastapi>=0.95.2, <0.100.0 graphlib_backport==1.0.3; python_version < '3.9' importlib-resources -numpy==1.21.6 +numpy==1.21.6; python_version < '3.8' +numpy==1.22.4; python_version >= '3.8' onnxruntime==1.14.1 overrides==7.3.1 posthog==2.4.0 From 81ab0b112492b26d302df446a850779196c5af98 Mon Sep 17 00:00:00 2001 From: Pascal M <11357019+perzeuss@users.noreply.github.com> Date: Thu, 31 Aug 2023 19:59:27 +0200 Subject: [PATCH 14/67] fix: use workaround for dynamic import (#956) resolves #953 ## Description of changes - Bug fixes - implement a workaround in clients/js/src/embeddings/WebAIEmbeddingFunction.ts to resolve #953 ## Test plan At present, we lack a testing setup specifically tailored for a browser environment. Implementing this would be a necessary step to implement tests for changes in this branch. I've tested it locally using this change: https://github.com/jeffchuber/nextjs-chroma/pull/1/files - http://localhost:3000/api/hello (node version) works fine - http://localhost:3000 (browser version) works fine --- clients/js/package.json | 29 +- .../TransformersEmbeddingFunction.ts | 9 +- .../src/embeddings/WebAIEmbeddingFunction.ts | 19 +- clients/js/src/utils.ts | 108 +- clients/js/yarn.lock | 3051 ++++++++++------- 5 files changed, 1891 insertions(+), 1325 deletions(-) diff --git a/clients/js/package.json b/clients/js/package.json index 8ed595623b18..abce9d7b6693 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -6,11 +6,11 @@ "author": "", "license": "Apache-2.0", "devDependencies": { + "@openapi-generator-plus/typescript-fetch-client-generator": "^1.5.0", "@types/jest": "^29.5.0", "jest": "^29.5.0", "npm-run-all": "^4.1.5", "openapi-generator-plus": "^2.6.0", - "@openapi-generator-plus/typescript-fetch-client-generator": "^1.5.0", "prettier": "2.8.7", "rimraf": "^5.0.0", "ts-jest": "^29.1.0", @@ -47,7 +47,34 @@ "prettier": "prettier --write .", "release": "run-s build test:run && npm publish" }, + "engines": { + "node": ">=14.17.0" + }, "dependencies": { "isomorphic-fetch": "^3.0.0" + }, + "peerDependencies": { + "@visheratin/web-ai": "^1.0.0", + "@visheratin/web-ai-node": "^1.0.0", + "@xenova/transformers": "^2.0.0", + "cohere-ai": "^6.0.0", + "openai": "^3.0.0 | ^4.0.0" + }, + "peerDependenciesMeta": { + "@visheratin/web-ai": { + "optional": true + }, + "@visheratin/web-ai-node": { + "optional": true + }, + "@xenova/transformers": { + "optional": true + }, + "cohere-ai": { + "optional": true + }, + "openai": { + "optional": true + } } } diff --git a/clients/js/src/embeddings/TransformersEmbeddingFunction.ts b/clients/js/src/embeddings/TransformersEmbeddingFunction.ts index 887910c159bc..960a69c03642 100644 --- a/clients/js/src/embeddings/TransformersEmbeddingFunction.ts +++ b/clients/js/src/embeddings/TransformersEmbeddingFunction.ts @@ -1,3 +1,4 @@ +import { importOptionalModule } from "../utils"; import { IEmbeddingFunction } from "./IEmbeddingFunction"; // Dynamically import module @@ -26,11 +27,9 @@ export class TransformersEmbeddingFunction implements IEmbeddingFunction { progress_callback?: Function | null; } = {}) { try { - // Since Transformers.js is an ESM package, we use the dynamic `import` syntax instead of `require`. - // Also, since we use `"module": "commonjs"` in tsconfig.json, we use the following workaround to ensure - // the dynamic import is not transpiled to a `require` statement. - // For more information, see https://github.com/microsoft/TypeScript/issues/43329#issuecomment-1008361973 - TransformersApi = Function('return import("@xenova/transformers")')(); + // Use dynamic import to support browser environments because we do not have a bundler that handles browser support. + // The util importOptionalModule is used to prevent issues when bundlers try to locate the dependency even when it's optional. + TransformersApi = importOptionalModule("@xenova/transformers"); } catch (e) { throw new Error( "Please install the @xenova/transformers package to use the TransformersEmbeddingFunction, `npm install -S @xenova/transformers`." diff --git a/clients/js/src/embeddings/WebAIEmbeddingFunction.ts b/clients/js/src/embeddings/WebAIEmbeddingFunction.ts index e76bc5c20f0c..983d4b39fce8 100755 --- a/clients/js/src/embeddings/WebAIEmbeddingFunction.ts +++ b/clients/js/src/embeddings/WebAIEmbeddingFunction.ts @@ -1,3 +1,4 @@ +import { importOptionalModule } from "../utils"; import { IEmbeddingFunction } from "./IEmbeddingFunction"; /** @@ -157,15 +158,15 @@ export class WebAIEmbeddingFunction implements IEmbeddingFunction { ) { this.proxy = proxy ? proxy : true; try { - // @ts-ignore - const webAI = await import("@visheratin/web-ai"); + const webAI = await importOptionalModule("@visheratin/web-ai"); if (wasmPath) { webAI.SessionParams.wasmRoot = wasmPath; } switch (modality) { case "text": { - // @ts-ignore - const webAIText = await import("@visheratin/web-ai/text"); + const webAIText = await importOptionalModule( + "@visheratin/web-ai/text" + ); let id = "mini-lm-v2-quant"; //default text model if (modelID) { id = modelID; @@ -182,8 +183,9 @@ export class WebAIEmbeddingFunction implements IEmbeddingFunction { ); } case "image": { - // @ts-ignore - const webAIImage = await import("@visheratin/web-ai/image"); + const webAIImage = await importOptionalModule( + "@visheratin/web-ai/image" + ); let id = "efficientformer-l1-feature-quant"; //default image model if (modelID) { id = modelID; @@ -200,8 +202,9 @@ export class WebAIEmbeddingFunction implements IEmbeddingFunction { ); } case "multimodal": { - // @ts-ignore - const webAIImage = await import("@visheratin/web-ai/multimodal"); + const webAIImage = await importOptionalModule( + "@visheratin/web-ai/multimodal" + ); let id = "clip-base-quant"; //default multimodal model if (modelID) { id = modelID; diff --git a/clients/js/src/utils.ts b/clients/js/src/utils.ts index 619152854830..62a7931e1366 100644 --- a/clients/js/src/utils.ts +++ b/clients/js/src/utils.ts @@ -1,66 +1,84 @@ -import { Api } from "./generated" +import { Api } from "./generated"; import Count200Response = Api.Count200Response; // a function to convert a non-Array object to an Array export function toArray(obj: T | Array): Array { - if (Array.isArray(obj)) { - return obj; - } else { - return [obj]; - } + if (Array.isArray(obj)) { + return obj; + } else { + return [obj]; + } } // a function to convert an array to array of arrays -export function toArrayOfArrays(obj: Array> | Array): Array> { - if (Array.isArray(obj[0])) { - return obj as Array>; - } else { - return [obj] as Array>; - } +export function toArrayOfArrays( + obj: Array> | Array +): Array> { + if (Array.isArray(obj[0])) { + return obj as Array>; + } else { + return [obj] as Array>; + } } // we need to override constructors to make it work with jest // https://stackoverflow.com/questions/76007003/jest-tobeinstanceof-expected-constructor-array-received-constructor-array export function repack(value: unknown): any { - if (Boolean(value) && typeof value === "object") { - if (Array.isArray(value)) { - return new Array(...value); - } else { - return { ...value }; - } + if (Boolean(value) && typeof value === "object") { + if (Array.isArray(value)) { + return new Array(...value); } else { - return value; + return { ...value }; } + } else { + return value; + } } export async function handleError(error: unknown) { - - if (error instanceof Response) { - try { - const res = await error.json(); - if ("error" in res) { - return { error: res.error }; - } - } catch (e: unknown) { - return { - //@ts-ignore - error: - e && typeof e === "object" && "message" in e - ? e.message - : "unknown error", - }; - } + if (error instanceof Response) { + try { + const res = await error.json(); + if ("error" in res) { + return { error: res.error }; + } + } catch (e: unknown) { + return { + //@ts-ignore + error: + e && typeof e === "object" && "message" in e + ? e.message + : "unknown error", + }; } - return { error }; + } + return { error }; } -export async function handleSuccess(response: Response | string | Count200Response) { - switch (true) { - case response instanceof Response: - return repack(await (response as Response).json()); - case typeof response === "string": - return repack((response as string)); // currently version is the only thing that return non-JSON - default: - return repack(response); - } +export async function handleSuccess( + response: Response | string | Count200Response +) { + switch (true) { + case response instanceof Response: + return repack(await (response as Response).json()); + case typeof response === "string": + return repack(response as string); // currently version is the only thing that return non-JSON + default: + return repack(response); + } +} + +/** + * Dynamically imports a specified module, providing a workaround for browser environments. + * This function is necessary because we dynamically import optional dependencies + * which can cause issues with bundlers that detect the import and throw an error + * on build time when the dependency is not installed. + * Using this workaround, the dynamic import is only evaluated on runtime + * where we work with try-catch when importing optional dependencies. + * + * @param {string} moduleName - Specifies the module to import. + * @returns {Promise} Returns a Promise that resolves to the imported module. + */ +export async function importOptionalModule(moduleName: string) { + return Function(`return import("${moduleName}")`)(); } diff --git a/clients/js/yarn.lock b/clients/js/yarn.lock index 746947e86b59..33df6f74dde8 100644 --- a/clients/js/yarn.lock +++ b/clients/js/yarn.lock @@ -3,322 +3,320 @@ "@ampproject/remapping@^2.2.0": - version "2.2.0" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + version "2.2.1" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== dependencies: - "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/gen-mapping" "^0.3.0" "@jridgewell/trace-mapping" "^0.3.9" "@apidevtools/openapi-schemas@^2.1.0": version "2.1.0" - resolved "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== "@apidevtools/swagger-methods@^3.0.2": version "3.0.2" - resolved "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" + integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.22.13" + chalk "^2.4.2" -"@babel/compat-data@^7.20.5": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz" - integrity sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g== +"@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz" - integrity sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA== +"@babel/core@^7.11.6", "@babel/core@^7.12.3": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.11.tgz#8033acaa2aa24c3f814edaaa057f3ce0ba559c24" + integrity sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.0" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.21.0" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.0" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/code-frame" "^7.22.10" + "@babel/generator" "^7.22.10" + "@babel/helper-compilation-targets" "^7.22.10" + "@babel/helper-module-transforms" "^7.22.9" + "@babel/helpers" "^7.22.11" + "@babel/parser" "^7.22.11" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.11" + "@babel/types" "^7.22.11" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" - json5 "^2.2.2" - semver "^6.3.0" + json5 "^2.2.3" + semver "^6.3.1" -"@babel/generator@^7.21.0", "@babel/generator@^7.7.2": - version "7.21.1" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz" - integrity sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA== +"@babel/generator@^7.22.10", "@babel/generator@^7.7.2": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722" + integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A== dependencies: - "@babel/types" "^7.21.0" + "@babel/types" "^7.22.10" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.20.7": - version "7.20.7" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz" - integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== +"@babel/helper-compilation-targets@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz#01d648bbc25dd88f513d862ee0df27b7d4e67024" + integrity sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q== dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.21.3" + "@babel/compat-data" "^7.22.9" + "@babel/helper-validator-option" "^7.22.5" + browserslist "^4.21.9" lru-cache "^5.1.1" - semver "^6.3.0" + semver "^6.3.1" -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== -"@babel/helper-function-name@^7.21.0": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz" - integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg== +"@babel/helper-function-name@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== dependencies: - "@babel/template" "^7.20.7" - "@babel/types" "^7.21.0" + "@babel/template" "^7.22.5" + "@babel/types" "^7.22.5" -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== +"@babel/helper-hoist-variables@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-module-imports@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.21.0": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.0.tgz" - integrity sha512-eD/JQ21IG2i1FraJnTMbUarAUkA7G988ofehG5MDCRXaUU91rEBJuCeSoou2Sk1y4RbLYXzqEg1QLwEmRU4qcQ== +"@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.19.1" - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.5" "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0": version "7.22.5" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== -"@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== +"@babel/helper-simple-access@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== dependencies: - "@babel/types" "^7.20.2" + "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== +"@babel/helper-split-export-declaration@^7.22.6": + version "7.22.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.22.5" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": - version "7.19.1" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== -"@babel/helper-validator-option@^7.18.6": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz" - integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== +"@babel/helper-validator-option@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== -"@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.11.tgz#b02f5d5f2d7abc21ab59eeed80de410ba70b056a" + integrity sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg== dependencies: - "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/template" "^7.22.5" + "@babel/traverse" "^7.22.11" + "@babel/types" "^7.22.11" -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.22.13": + version "7.22.13" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.13.tgz#9cda839e5d3be9ca9e8c26b6dd69e7548f0cbf16" + integrity sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.0": - version "7.21.1" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.21.1.tgz" - integrity sha512-JzhBFpkuhBNYUY7qs+wTzNmyCWUHEaAFpQQD2YfU1rPL38/L43Wvid0fFkiOCnHvsGncRZgEPyGnltABLcVDTg== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.11", "@babel/parser@^7.22.5": + version "7.22.14" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.14.tgz#c7de58e8de106e88efca42ce17f0033209dfd245" + integrity sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-bigint@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-class-properties@^7.8.3": version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== dependencies: "@babel/helper-plugin-utils" "^7.12.13" "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": version "7.22.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918" integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg== dependencies: "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-numeric-separator@^7.8.3": version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== dependencies: "@babel/helper-plugin-utils" "^7.10.4" "@babel/plugin-syntax-object-rest-spread@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-catch-binding@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-optional-chaining@^7.8.3": version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== dependencies: "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-top-level-await@^7.8.3": version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== dependencies: "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": version "7.22.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272" integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ== dependencies: "@babel/helper-plugin-utils" "^7.22.5" -"@babel/template@^7.20.7", "@babel/template@^7.3.3": - version "7.20.7" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz" - integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.20.7" - "@babel/types" "^7.20.7" - -"@babel/traverse@^7.21.0", "@babel/traverse@^7.7.2": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.0.tgz" - integrity sha512-Xdt2P1H4LKTO8ApPfnO1KmzYMFpp7D/EinoXzLYN/cHcBNrVCAkAtGUcXnHXrl/VGktureU6fkQrHSBE2URfoA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.21.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.0" - "@babel/types" "^7.21.0" +"@babel/template@^7.22.5", "@babel/template@^7.3.3": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== + dependencies: + "@babel/code-frame" "^7.22.5" + "@babel/parser" "^7.22.5" + "@babel/types" "^7.22.5" + +"@babel/traverse@^7.22.11": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.11.tgz#71ebb3af7a05ff97280b83f05f8865ac94b2027c" + integrity sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ== + dependencies: + "@babel/code-frame" "^7.22.10" + "@babel/generator" "^7.22.10" + "@babel/helper-environment-visitor" "^7.22.5" + "@babel/helper-function-name" "^7.22.5" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.22.11" + "@babel/types" "^7.22.11" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.6", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.21.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.21.0.tgz" - integrity sha512-uR7NWq2VNFnDi7EYqiRz2Jv/VQIu38tu64Zy8TX2nQFQ6etJ9V/Rr2msW8BS132mum2rL645qpDrLtAJtVpuow== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.10", "@babel/types@^7.22.11", "@babel/types@^7.22.5", "@babel/types@^7.3.3": + version "7.22.11" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.11.tgz#0e65a6a1d4d9cbaa892b2213f6159485fe632ea2" + integrity sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg== dependencies: - "@babel/helper-string-parser" "^7.19.4" - "@babel/helper-validator-identifier" "^7.19.1" + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": version "0.2.3" - resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" "@isaacs/cliui@^8.0.2": version "8.0.2" - resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== dependencies: string-width "^5.1.2" @@ -330,7 +328,7 @@ "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" - resolved "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== dependencies: camelcase "^5.3.1" @@ -341,113 +339,113 @@ "@istanbuljs/schema@^0.1.2": version "0.1.3" - resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== -"@jest/console@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz" - integrity sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ== +"@jest/console@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-29.6.4.tgz#a7e2d84516301f986bba0dd55af9d5fe37f46527" + integrity sha512-wNK6gC0Ha9QeEPSkeJedQuTQqxZYnDPuDcDhVuVatRvMkL4D0VTvFVZj+Yuh6caG2aOfzkUZ36KtCmLNtR02hw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.3" + jest-util "^29.6.3" slash "^3.0.0" -"@jest/core@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz" - integrity sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ== +"@jest/core@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-29.6.4.tgz#265ebee05ec1ff3567757e7a327155c8d6bdb126" + integrity sha512-U/vq5ccNTSVgYH7mHnodHmCffGWHJnz/E1BEWlLuK5pM4FZmGfBn/nrJGLjUsSmyx3otCeqc1T31F4y08AMDLg== dependencies: - "@jest/console" "^29.5.0" - "@jest/reporters" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.4" + "@jest/reporters" "^29.6.4" + "@jest/test-result" "^29.6.4" + "@jest/transform" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" ci-info "^3.2.0" exit "^0.1.2" graceful-fs "^4.2.9" - jest-changed-files "^29.5.0" - jest-config "^29.5.0" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-resolve-dependencies "^29.5.0" - jest-runner "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" - jest-watcher "^29.5.0" + jest-changed-files "^29.6.3" + jest-config "^29.6.4" + jest-haste-map "^29.6.4" + jest-message-util "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.6.4" + jest-resolve-dependencies "^29.6.4" + jest-runner "^29.6.4" + jest-runtime "^29.6.4" + jest-snapshot "^29.6.4" + jest-util "^29.6.3" + jest-validate "^29.6.3" + jest-watcher "^29.6.4" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.3" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz" - integrity sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ== +"@jest/environment@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.6.4.tgz#78ec2c9f8c8829a37616934ff4fea0c028c79f4f" + integrity sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ== dependencies: - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/fake-timers" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" + jest-mock "^29.6.3" -"@jest/expect-utils@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz" - integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg== +"@jest/expect-utils@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.6.4.tgz#17c7dfe6cec106441f218b0aff4b295f98346679" + integrity sha512-FEhkJhqtvBwgSpiTrocquJCdXPsyvNKcl/n7A3u7X4pVoF4bswm11c9d4AV+kfq2Gpv/mM8x7E7DsRvH+djkrg== dependencies: - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" -"@jest/expect@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz" - integrity sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g== +"@jest/expect@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-29.6.4.tgz#1d6ae17dc68d906776198389427ab7ce6179dba6" + integrity sha512-Warhsa7d23+3X5bLbrbYvaehcgX5TLYhI03JKoedTiI8uJU4IhqYBWF7OSSgUyz4IgLpUYPkK0AehA5/fRclAA== dependencies: - expect "^29.5.0" - jest-snapshot "^29.5.0" + expect "^29.6.4" + jest-snapshot "^29.6.4" -"@jest/fake-timers@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz" - integrity sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg== +"@jest/fake-timers@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.6.4.tgz#45a27f093c43d5d989362a3e7a8c70c83188b4f6" + integrity sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@sinonjs/fake-timers" "^10.0.2" "@types/node" "*" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-message-util "^29.6.3" + jest-mock "^29.6.3" + jest-util "^29.6.3" -"@jest/globals@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz" - integrity sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ== +"@jest/globals@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.6.4.tgz#4f04f58731b062b44ef23036b79bdb31f40c7f63" + integrity sha512-wVIn5bdtjlChhXAzVXavcY/3PEjf4VqM174BM3eGL5kMxLiZD5CLnbmkEyA1Dwh9q8XjP6E8RwjBsY/iCWrWsA== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/types" "^29.5.0" - jest-mock "^29.5.0" + "@jest/environment" "^29.6.4" + "@jest/expect" "^29.6.4" + "@jest/types" "^29.6.3" + jest-mock "^29.6.3" -"@jest/reporters@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz" - integrity sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA== +"@jest/reporters@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-29.6.4.tgz#9d6350c8a2761ece91f7946e97ab0dabc06deab7" + integrity sha512-sxUjWxm7QdchdrD3NfWKrL8FBsortZeibSJv4XLjESOOjSUOkjQcb0ZHJwfhEGIvBvTluTzfG2yZWZhkrXJu8g== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/console" "^29.6.4" + "@jest/test-result" "^29.6.4" + "@jest/transform" "^29.6.4" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" "@types/node" "*" chalk "^4.0.0" collect-v8-coverage "^1.0.0" @@ -455,156 +453,148 @@ glob "^7.1.3" graceful-fs "^4.2.9" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^5.1.0" + istanbul-lib-instrument "^6.0.0" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.1.3" - jest-message-util "^29.5.0" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-message-util "^29.6.3" + jest-util "^29.6.3" + jest-worker "^29.6.4" slash "^3.0.0" string-length "^4.0.1" strip-ansi "^6.0.0" v8-to-istanbul "^9.0.1" -"@jest/schemas@^29.4.3": - version "29.4.3" - resolved "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz" - integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: - "@sinclair/typebox" "^0.25.16" + "@sinclair/typebox" "^0.27.8" -"@jest/source-map@^29.4.3": - version "29.4.3" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz" - integrity sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w== +"@jest/source-map@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.6.3.tgz#d90ba772095cf37a34a5eb9413f1b562a08554c4" + integrity sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw== dependencies: - "@jridgewell/trace-mapping" "^0.3.15" + "@jridgewell/trace-mapping" "^0.3.18" callsites "^3.0.0" graceful-fs "^4.2.9" -"@jest/test-result@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz" - integrity sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ== +"@jest/test-result@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-29.6.4.tgz#adf5c79f6e1fb7405ad13d67d9e2b6ff54b54c6b" + integrity sha512-uQ1C0AUEN90/dsyEirgMLlouROgSY+Wc/JanVVk0OiUKa5UFh7sJpMEM3aoUBAz2BRNvUJ8j3d294WFuRxSyOQ== dependencies: - "@jest/console" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.4" + "@jest/types" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz" - integrity sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ== +"@jest/test-sequencer@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-29.6.4.tgz#86aef66aaa22b181307ed06c26c82802fb836d7b" + integrity sha512-E84M6LbpcRq3fT4ckfKs9ryVanwkaIB0Ws9bw3/yP4seRLg/VaCZ/LgW0MCq5wwk4/iP/qnilD41aj2fsw2RMg== dependencies: - "@jest/test-result" "^29.5.0" + "@jest/test-result" "^29.6.4" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.4" slash "^3.0.0" -"@jest/transform@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz" - integrity sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw== +"@jest/transform@^29.6.4": + version "29.6.4" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.6.4.tgz#a6bc799ef597c5d85b2e65a11fd96b6b239bab5a" + integrity sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA== dependencies: "@babel/core" "^7.11.6" - "@jest/types" "^29.5.0" - "@jridgewell/trace-mapping" "^0.3.15" + "@jest/types" "^29.6.3" + "@jridgewell/trace-mapping" "^0.3.18" babel-plugin-istanbul "^6.1.1" chalk "^4.0.0" convert-source-map "^2.0.0" fast-json-stable-stringify "^2.1.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" + jest-haste-map "^29.6.4" + jest-regex-util "^29.6.3" + jest-util "^29.6.3" micromatch "^4.0.4" pirates "^4.0.4" slash "^3.0.0" write-file-atomic "^4.0.2" -"@jest/types@^29.0.0", "@jest/types@^29.5.0": - version "29.5.0" - resolved "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz" - integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@3.1.0": - version "3.1.0" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" + integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": +"@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14": - version "1.4.14" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.17" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz" - integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g== - dependencies: - "@jridgewell/resolve-uri" "3.1.0" - "@jridgewell/sourcemap-codec" "1.4.14" +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.19" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811" + integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@jsdevtools/ono@^7.1.3": version "7.1.3" - resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3": version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" @@ -612,7 +602,7 @@ "@openapi-generator-plus/core@2.6.0": version "2.6.0" - resolved "https://registry.npmjs.org/@openapi-generator-plus/core/-/core-2.6.0.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/core/-/core-2.6.0.tgz#4004d92eb59c96d83ede224a71536027ce09fe86" integrity sha512-tEIIndmPMEzlCzEmKersKhOOSJ0XfIXbQOoEp85BH/J4vpnc1gncKwP1OqmAZs08uC5lbLSbqP4ZgJGtFr6JsQ== dependencies: "@openapi-generator-plus/indexed-type" "1.0.0" @@ -623,7 +613,7 @@ "@openapi-generator-plus/generator-common@1.3.3": version "1.3.3" - resolved "https://registry.npmjs.org/@openapi-generator-plus/generator-common/-/generator-common-1.3.3.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/generator-common/-/generator-common-1.3.3.tgz#331eabeee1ad757360af01e4b0881d55d8f72556" integrity sha512-B+q6e3yMaplqrjja8fhHPeyaqvRLKQyRxx0Ag0hrM+KjohXnauqfv0zZYkqs6+Jw8596JtQqAQ/lokRFYzdWVA== dependencies: "@openapi-generator-plus/types" "^2.5.0" @@ -633,7 +623,7 @@ "@openapi-generator-plus/handlebars-templates@1.2.4": version "1.2.4" - resolved "https://registry.npmjs.org/@openapi-generator-plus/handlebars-templates/-/handlebars-templates-1.2.4.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/handlebars-templates/-/handlebars-templates-1.2.4.tgz#eb418776a50a5390228abdb87a2df0ab2748f1a1" integrity sha512-+Q8VRayFih8xE9FD+Z7K5/tVU0Eqfn6tB8LUzmIRYmUihYMQorho/360srUcSMO6s1pneBLP337a9+DAgU9yzw== dependencies: "@openapi-generator-plus/generator-common" "1.3.3" @@ -644,14 +634,14 @@ marked "^4.0.15" pluralize "^8.0.0" -"@openapi-generator-plus/indexed-type@^1.0.0", "@openapi-generator-plus/indexed-type@1.0.0": +"@openapi-generator-plus/indexed-type@1.0.0", "@openapi-generator-plus/indexed-type@^1.0.0": version "1.0.0" - resolved "https://registry.npmjs.org/@openapi-generator-plus/indexed-type/-/indexed-type-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/indexed-type/-/indexed-type-1.0.0.tgz#0cde3bd7e3ad3ab9ee3ee5f41927aa3683b69978" integrity sha512-RGUrlulyLoH7+V6wDalDGD9bfwTyDgIMZnfPo5GmaQs3CGOZ2aSHYAsB78gVTz2KWTyc5Ov4doi2lPENeUarZQ== "@openapi-generator-plus/java-like-generator-helper@2.1.4": version "2.1.4" - resolved "https://registry.npmjs.org/@openapi-generator-plus/java-like-generator-helper/-/java-like-generator-helper-2.1.4.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/java-like-generator-helper/-/java-like-generator-helper-2.1.4.tgz#06436742969edce9e328aa2b250b889dcb7d74d8" integrity sha512-c7/eWPF7PEgusOXGXLRwiX56OLn6YUxMG88EJ7WnAGPnVUNxA3FfggDschH9hGpE62guLLiahJ/5qngyzACg5g== dependencies: "@openapi-generator-plus/generator-common" "1.3.3" @@ -660,7 +650,7 @@ "@openapi-generator-plus/json-schema-ref-parser@^9.0.11": version "9.0.11" - resolved "https://registry.npmjs.org/@openapi-generator-plus/json-schema-ref-parser/-/json-schema-ref-parser-9.0.11.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/json-schema-ref-parser/-/json-schema-ref-parser-9.0.11.tgz#076c6b085e2acfcd3097841bb75a9cff96702ae3" integrity sha512-SJbsXJgQozq86V2ImkLuthI9d7esDIPjG/MUw2BEVa3HLIi/lHMmAVpUvBGNIpK4+yvUGmZSpgLOLmW3R9XoTA== dependencies: "@jsdevtools/ono" "^7.1.3" @@ -670,7 +660,7 @@ "@openapi-generator-plus/swagger-parser@^10.1.0": version "10.1.0" - resolved "https://registry.npmjs.org/@openapi-generator-plus/swagger-parser/-/swagger-parser-10.1.0.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/swagger-parser/-/swagger-parser-10.1.0.tgz#b9643176358abdb9e7092f1ad2c3a49d6e077e02" integrity sha512-Nxa6cAcJR6f2qieIa/pXTg0B9LqwzwYj6/AHBS39jE/eizJrhHQm74kqzABPjrFhvp9EcZD9E8IBuRunFfQULg== dependencies: "@apidevtools/openapi-schemas" "^2.1.0" @@ -681,14 +671,14 @@ ajv-draft-04 "^1.0.0" call-me-maybe "^1.0.1" -"@openapi-generator-plus/types@^2.0.0", "@openapi-generator-plus/types@^2.5.0", "@openapi-generator-plus/types@2.5.0": +"@openapi-generator-plus/types@2.5.0", "@openapi-generator-plus/types@^2.0.0", "@openapi-generator-plus/types@^2.5.0": version "2.5.0" - resolved "https://registry.npmjs.org/@openapi-generator-plus/types/-/types-2.5.0.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/types/-/types-2.5.0.tgz#d36c1fb929bd5b5c640317b0033cfaf9a86f7817" integrity sha512-jELZ0fQx8FluA4EsekiGeRus0ZfrE+CbIswzUTcaUEKruv1Jm0q9aXEU2mAzVrzp+F92HOMqI5JyiUSBkv9hcw== "@openapi-generator-plus/typescript-fetch-client-generator@^1.5.0": version "1.5.0" - resolved "https://registry.npmjs.org/@openapi-generator-plus/typescript-fetch-client-generator/-/typescript-fetch-client-generator-1.5.0.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/typescript-fetch-client-generator/-/typescript-fetch-client-generator-1.5.0.tgz#d8e2687b6cb5578ce458d61999e154f296fb3800" integrity sha512-ZnMHRD38eMLEe26dWm5o0yz2lVSL+yb+ANNtqimMkR8r0aCwUIHBb4jZo4jz7iwN2rxqBn5iyca6V9lMZDpZkQ== dependencies: "@openapi-generator-plus/generator-common" "1.3.3" @@ -700,7 +690,7 @@ "@openapi-generator-plus/typescript-generator-common@1.5.4": version "1.5.4" - resolved "https://registry.npmjs.org/@openapi-generator-plus/typescript-generator-common/-/typescript-generator-common-1.5.4.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/typescript-generator-common/-/typescript-generator-common-1.5.4.tgz#85099df4d547d0273e7e394ca35a71b68648fed3" integrity sha512-sN7q6fCiG3d+MZoVfU1Fqz685YiBBxE2rK37uY5iwz+TkQVAVepSW4RD9011Q/q82d415Fqy8vT4C836WyrV8w== dependencies: "@openapi-generator-plus/generator-common" "1.3.3" @@ -710,9 +700,9 @@ handlebars "^4.7.7" pluralize "^8.0.0" -"@openapi-generator-plus/utils@^1.0.1", "@openapi-generator-plus/utils@1.0.1": +"@openapi-generator-plus/utils@1.0.1", "@openapi-generator-plus/utils@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@openapi-generator-plus/utils/-/utils-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@openapi-generator-plus/utils/-/utils-1.0.1.tgz#123d84a8a60ad905a0028f8ea64ee4bf08d04f67" integrity sha512-WceEoFbMmhdqnj2qzdsZTb7ZXH5boNp9LYJHNwD+7A0Y3UfHOh+KHMrKrO6+3K8O0g6dxjYWvG2/ZNLX8VbybA== dependencies: "@openapi-generator-plus/indexed-type" "^1.0.0" @@ -720,56 +710,109 @@ "@pkgjs/parseargs@^0.11.0": version "0.11.0" - resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@sinclair/typebox@^0.25.16": - version "0.25.23" - resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.23.tgz" - integrity sha512-VEB8ygeP42CFLWyAJhN5OklpxUliqdNEUcXb4xZ/CINqtYGTjL5ukluKdKzQ0iWdUxyQ7B0539PAUhHKrCNWSQ== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== "@sinonjs/commons@^3.0.0": version "3.0.0" - resolved "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" integrity sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA== dependencies: type-detect "4.0.8" "@sinonjs/fake-timers@^10.0.2": version "10.3.0" - resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz#55fdff1ecab9f354019129daf4df0dd4d923ea66" integrity sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA== dependencies: "@sinonjs/commons" "^3.0.0" "@tsconfig/node10@^1.0.7": version "1.0.9" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@tsd/typescript@~5.0.2": version "5.0.4" - resolved "https://registry.npmjs.org/@tsd/typescript/-/typescript-5.0.4.tgz" + resolved "https://registry.yarnpkg.com/@tsd/typescript/-/typescript-5.0.4.tgz#18aa4eb2c35c6bf9aab3199c289be319bedb7e9c" integrity sha512-YQi2lvZSI+xidKeUjlbv6b6Zw7qB3aXHw5oGJLs5OOGAEqKIOvz5UIAkWyg0bJbkSUWPBEtaOHpVxU4EYBO1Jg== "@types/babel__core@^7.1.14": version "7.20.1" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b" integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw== dependencies: "@babel/parser" "^7.20.7" @@ -780,42 +823,42 @@ "@types/babel__generator@*": version "7.6.4" - resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== dependencies: "@babel/types" "^7.0.0" "@types/babel__template@*": version "7.4.1" - resolved "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== dependencies: "@babel/parser" "^7.1.0" "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.18.3" - resolved "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.3.tgz" - integrity sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w== + version "7.20.1" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf" + integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg== dependencies: - "@babel/types" "^7.3.0" + "@babel/types" "^7.20.7" "@types/eslint@^7.2.13": version "7.29.0" - resolved "https://registry.npmjs.org/@types/eslint/-/eslint-7.29.0.tgz" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.29.0.tgz#e56ddc8e542815272720bb0b4ccc2aff9c3e1c78" integrity sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng== dependencies: "@types/estree" "*" "@types/json-schema" "*" "@types/estree@*": - version "1.0.0" - resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" + integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA== "@types/glob@^7.1.3": version "7.2.0" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== dependencies: "@types/minimatch" "*" @@ -823,51 +866,56 @@ "@types/graceful-fs@^4.1.3": version "4.1.6" - resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae" integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw== dependencies: "@types/node" "*" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" - resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== "@types/istanbul-lib-report@*": version "3.0.0" - resolved "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== dependencies: "@types/istanbul-lib-coverage" "*" "@types/istanbul-reports@^3.0.0": version "3.0.1" - resolved "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== dependencies: "@types/istanbul-lib-report" "*" "@types/jest@^29.5.0": - version "29.5.2" - resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.2.tgz" - integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg== + version "29.5.4" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.4.tgz#9d0a16edaa009a71e6a71a999acd582514dab566" + integrity sha512-PhglGmhWeD46FYOVLt3X7TiWjzwuVGW9wG/4qocPevXMjCmrIc5b6db9WjeGE4QYVpUAWMDv3v0IiBwObY289A== dependencies: expect "^29.0.0" pretty-format "^29.0.0" "@types/json-schema@*", "@types/json-schema@^7.0.6": - version "7.0.11" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== "@types/minimatch@*": version "5.1.2" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== "@types/minimist@^1.2.0": version "1.2.2" - resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== "@types/node@*": @@ -875,51 +923,81 @@ resolved "https://registry.npmjs.org/@types/node/-/node-18.14.0.tgz" integrity sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A== +"@types/node@>=13.7.0": + version "20.5.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.0.tgz#7fc8636d5f1aaa3b21e6245e97d56b7f56702313" + integrity sha512-Mgq7eCtoTjT89FqNoTzzXg2XvCi5VMhRV6+I2aYanc6kQCBImeNaAYRs/DyoVqk1YEUJK5gN9VO7HRIdz4Wo3Q== + "@types/normalize-package-data@^2.4.0": version "2.4.1" - resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== -"@types/prettier@^2.1.5": - version "2.7.3" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz" - integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== - "@types/stack-utils@^2.0.0": version "2.0.1" - resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== "@types/yargs-parser@*": version "21.0.0" - resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== "@types/yargs@^17.0.8": - version "17.0.22" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.22.tgz" - integrity sha512-pet5WJ9U8yPVRhkwuEIp5ktAeAqRZOq4UdAyWLWzxbtpyXnzbtLdKiXAjJzi/KLmPGS9wk86lUFWZFN6sISo4g== + version "17.0.24" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902" + integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw== dependencies: "@types/yargs-parser" "*" +"@visheratin/web-ai-node@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@visheratin/web-ai-node/-/web-ai-node-1.3.1.tgz#8d146183f5c5862bbd6e6d8582b2ac7463187a24" + integrity sha512-O2GLX/Jyzi/LtN8J8uPSuwXF0yNiDYB6Bswj9hkYuoVeoPT0BV+1T4VPu3OaHaEq2/vIyTUBQbQHR6udqdTw0w== + dependencies: + comlink "4.3.1" + localforage "1.10.0" + onnxruntime-common "1.15.1" + pako "2.1.0" + +"@visheratin/web-ai@^1.3.1": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@visheratin/web-ai/-/web-ai-1.3.1.tgz#b3723b62b55016c0d3dd3a8daf119cc8a08b194b" + integrity sha512-OWyYrTFAbHFijHmpfuJ6lzeLziYOt8jyHL6GeryRzUE671aRG3cDk6wRz/MJknJeWS0T1545jzkmDF/4blYPCQ== + dependencies: + comlink "4.3.1" + localforage "1.10.0" + onnxruntime-common "1.15.1" + pako "2.1.0" + +"@xenova/transformers@^2.5.2": + version "2.5.4" + resolved "https://registry.yarnpkg.com/@xenova/transformers/-/transformers-2.5.4.tgz#baaa5d59b63a25879718a5b82aa49d6af63dbe51" + integrity sha512-hXk/8j5E5Ql4PmP7/n45zSRVjTB1iLTUvRJum8ywU/aAXg4QW2SYQqb0oCBrY7yCG8Hb/8s3C+TevHjgWEbLkw== + dependencies: + onnxruntime-web "1.14.0" + sharp "^0.32.0" + optionalDependencies: + onnxruntime-node "1.14.0" + acorn-walk@^8.1.1: version "8.2.0" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^8.4.1: - version "8.8.2" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== + version "8.10.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== ajv-draft-04@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz#3b64761b268ba0b9e668f0b41ba53fce0ad77fc8" integrity sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw== -ajv@^8.5.0, ajv@^8.6.3: +ajv@^8.6.3: version "8.12.0" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== dependencies: fast-deep-equal "^3.1.1" @@ -929,60 +1007,53 @@ ajv@^8.5.0, ajv@^8.6.3: ansi-colors@^4.1.1: version "4.1.3" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-escapes@^4.2.1: version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: type-fest "^0.21.3" ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-regex@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== ansi-styles@^3.2.1: version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== ansi-styles@^6.1.0: version "6.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== anymatch@^3.0.3: version "3.1.3" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -990,52 +1061,89 @@ anymatch@^3.0.3: arg@^4.1.0: version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== argparse@^1.0.7: version "1.0.10" - resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-buffer-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" + integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== + dependencies: + call-bind "^1.0.2" + is-array-buffer "^3.0.1" + array-union@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +arraybuffer.prototype.slice@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" + integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + arrify@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + available-typed-arrays@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -babel-jest@^29.0.0, babel-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz" - integrity sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q== +axios@^0.26.0: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + +b4a@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.4.tgz#ef1c1422cae5ce6535ec191baeed7567443f36c9" + integrity sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw== + +babel-jest@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.6.4.tgz#98dbc45d1c93319c82a8ab4a478b670655dd2585" + integrity sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw== dependencies: - "@jest/transform" "^29.5.0" + "@jest/transform" "^29.6.4" "@types/babel__core" "^7.1.14" babel-plugin-istanbul "^6.1.1" - babel-preset-jest "^29.5.0" + babel-preset-jest "^29.6.3" chalk "^4.0.0" graceful-fs "^4.2.9" slash "^3.0.0" babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1044,10 +1152,10 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz" - integrity sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w== +babel-plugin-jest-hoist@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz#aadbe943464182a8922c3c927c3067ff40d24626" + integrity sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" @@ -1056,7 +1164,7 @@ babel-plugin-jest-hoist@^29.5.0: babel-preset-current-node-syntax@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -1072,22 +1180,36 @@ babel-preset-current-node-syntax@^1.0.0: "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-top-level-await" "^7.8.3" -babel-preset-jest@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz" - integrity sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg== +babel-preset-jest@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz#fa05fa510e7d493896d7b0dd2033601c840f171c" + integrity sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA== dependencies: - babel-plugin-jest-hoist "^29.5.0" + babel-plugin-jest-hoist "^29.6.3" babel-preset-current-node-syntax "^1.0.0" balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" @@ -1095,50 +1217,58 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" braces@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" -browserslist@^4.21.3, "browserslist@>= 4.21.0": - version "4.21.5" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz" - integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w== +browserslist@^4.21.9: + version "4.21.10" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" + integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== dependencies: - caniuse-lite "^1.0.30001449" - electron-to-chromium "^1.4.284" - node-releases "^2.0.8" - update-browserslist-db "^1.0.10" + caniuse-lite "^1.0.30001517" + electron-to-chromium "^1.4.477" + node-releases "^2.0.13" + update-browserslist-db "^1.0.11" bs-logger@0.x: version "0.2.6" - resolved "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz" + resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== dependencies: fast-json-stable-stringify "2.x" bser@2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== dependencies: node-int64 "^0.4.0" buffer-from@^1.0.0: version "1.1.2" - resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== dependencies: function-bind "^1.1.1" @@ -1146,17 +1276,17 @@ call-bind@^1.0.0, call-bind@^1.0.2: call-me-maybe@^1.0.1: version "1.0.2" - resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== callsites@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camel-case@^4.1.2: version "4.1.2" - resolved "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== dependencies: pascal-case "^3.1.2" @@ -1164,7 +1294,7 @@ camel-case@^4.1.2: camelcase-keys@^6.2.2: version "6.2.2" - resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== dependencies: camelcase "^5.3.1" @@ -1173,40 +1303,31 @@ camelcase-keys@^6.2.2: camelcase@^5.3.1: version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== camelcase@^6.2.0: version "6.3.0" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001449: - version "1.0.30001457" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001457.tgz" - integrity sha512-SDIV6bgE1aVbK6XyxdURbUE89zY7+k1BBBaOwYwkNCglXlel/E7mELiHC64HQ+W0xSKlqWhV9Wh7iHxUjMs4fA== +caniuse-lite@^1.0.30001517: + version "1.0.30001524" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz#1e14bce4f43c41a7deaeb5ebfe86664fe8dadb80" + integrity sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA== capital-case@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/capital-case/-/capital-case-1.0.4.tgz#9d130292353c9249f6b00fa5852bee38a717e669" integrity sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A== dependencies: no-case "^3.0.4" tslib "^2.0.3" upper-case-first "^2.0.2" -chalk@^2.0.0: +chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^2.4.1: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== dependencies: ansi-styles "^3.2.1" @@ -1215,7 +1336,7 @@ chalk@^2.4.1: chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -1223,7 +1344,7 @@ chalk@^4.0.0, chalk@^4.1.0: change-case@^4.1.2: version "4.1.2" - resolved "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/change-case/-/change-case-4.1.2.tgz#fedfc5f136045e2398c0410ee441f95704641e12" integrity sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A== dependencies: camel-case "^4.1.2" @@ -1241,22 +1362,27 @@ change-case@^4.1.2: char-regex@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^3.2.0: version "3.8.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== cjs-module-lexer@^1.0.0: version "1.2.3" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== cliui@^8.0.1: version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== dependencies: string-width "^4.2.0" @@ -1265,75 +1391,103 @@ cliui@^8.0.1: co@^4.6.0: version "4.6.0" - resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== +cohere-ai@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/cohere-ai/-/cohere-ai-6.2.2.tgz#ea3a01d5bad839ffb006e8ec793c034a951cca7e" + integrity sha512-+Tq+4e8N/YWKJqFpWaULsfbZR/GOvGh8WWYFKR1bpipu8bCok3VcbTPnBmIToQiIqOgFpGk3HsA4b0guVyL3vg== + collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + version "1.0.2" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9" + integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q== color-convert@^1.9.0: version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@^1.0.0, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== + dependencies: + color-convert "^2.0.1" + color-string "^1.9.0" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comlink@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.3.1.tgz#0c6b9d69bcd293715c907c33fe8fc45aecad13c5" + integrity sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA== + concat-map@0.0.1: version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== constant-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" integrity sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ== dependencies: no-case "^3.0.4" tslib "^2.0.3" upper-case "^2.0.2" -convert-source-map@^1.6.0: +convert-source-map@^1.6.0, convert-source-map@^1.7.0: version "1.9.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" - integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== - -convert-source-map@^1.7.0: - version "1.9.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== convert-source-map@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== create-require@^1.1.0: version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^6.0.5: version "6.0.5" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" @@ -1342,18 +1496,9 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.0: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -cross-spawn@^7.0.3: +cross-spawn@^7.0.0, cross-spawn@^7.0.3: version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -1362,14 +1507,14 @@ cross-spawn@^7.0.3: debug@^4.1.0, debug@^4.1.1: version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" decamelize-keys@^1.1.0: version "1.1.1" - resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" integrity sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg== dependencies: decamelize "^1.1.0" @@ -1377,52 +1522,74 @@ decamelize-keys@^1.1.0: decamelize@^1.1.0, decamelize@^1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -dedent@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz" - integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +dedent@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.1.tgz#4f3fc94c8b711e9bb2800d185cd6ad20f2a90aff" + integrity sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg== + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== deepmerge@^4.2.2: version "4.3.1" - resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== -define-properties@^1.1.3, define-properties@^1.1.4: +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5" integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA== dependencies: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +detect-libc@^2.0.0, detect-libc@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" + integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== + detect-newline@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== -diff-sequences@^29.4.3: - version "29.4.3" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz" - integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff-sequences@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" + integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q== diff@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" dot-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== dependencies: no-case "^3.0.4" @@ -1430,48 +1597,56 @@ dot-case@^3.0.4: eastasianwidth@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.284: - version "1.4.304" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.304.tgz" - integrity sha512-6c8M+ojPgDIXN2NyfGn8oHASXYnayj+gSEnGeLMKb9zjsySeVB/j7KkNAAG9yDcv8gNlhvFg5REa1N/kQU6pgA== +electron-to-chromium@^1.4.477: + version "1.4.506" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.506.tgz#59f64a211102db4c3ebae2f39cc0e8e1b12b3a07" + integrity sha512-xxGct4GPAKSRlrLBtJxJFYy74W11zX6PO9GyHgl/U+2s3Dp0ZEwAklDfNHXOWcvH7zWMpsmgbR0ggEuaYAVvHA== emittery@^0.13.1: version "0.13.1" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== emoji-regex@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + error-ex@^1.3.1: version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== dependencies: is-arrayish "^0.2.1" -es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.1" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz" - integrity sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg== +es-abstract@^1.20.4, es-abstract@^1.22.1: + version "1.22.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" + integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.1" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" - function-bind "^1.1.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" @@ -1479,8 +1654,8 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: has-property-descriptors "^1.0.0" has-proto "^1.0.1" has-symbols "^1.0.3" - internal-slot "^1.0.4" - is-array-buffer "^3.0.1" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" @@ -1488,20 +1663,25 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: is-string "^1.0.7" is-typed-array "^1.1.10" is-weakref "^1.0.2" - object-inspect "^1.12.2" + object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.0" + safe-array-concat "^1.0.0" safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.7" string.prototype.trimend "^1.0.6" string.prototype.trimstart "^1.0.6" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.10" es-set-tostringtag@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg== dependencies: get-intrinsic "^1.1.3" @@ -1510,7 +1690,7 @@ es-set-tostringtag@^2.0.1: es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" @@ -1519,22 +1699,22 @@ es-to-primitive@^1.2.1: escalade@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== escape-string-regexp@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== eslint-formatter-pretty@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/eslint-formatter-pretty/-/eslint-formatter-pretty-4.1.0.tgz#7a6877c14ffe2672066c853587d89603e97c7708" integrity sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ== dependencies: "@types/eslint" "^7.2.13" @@ -1548,17 +1728,17 @@ eslint-formatter-pretty@^4.1.0: eslint-rule-docs@^1.1.5: version "1.1.235" - resolved "https://registry.npmjs.org/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz" + resolved "https://registry.yarnpkg.com/eslint-rule-docs/-/eslint-rule-docs-1.1.235.tgz#be6ef1fc3525f17b3c859ae2997fedadc89bfb9b" integrity sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A== esprima@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== execa@^5.0.0: version "5.1.1" - resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" @@ -1573,29 +1753,39 @@ execa@^5.0.0: exit@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== -expect@^29.0.0, expect@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz" - integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg== +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +expect@^29.0.0, expect@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/expect/-/expect-29.6.4.tgz#a6e6f66d4613717859b2fe3da98a739437b6f4b8" + integrity sha512-F2W2UyQ8XYyftHT57dtfg8Ue3X5qLgm2sSug0ivvLRH/VKNRL/pDxg/TH7zVzbQB0tu80clNFy6LU7OS/VSEKA== dependencies: - "@jest/expect-utils" "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + "@jest/expect-utils" "^29.6.4" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.6.4" + jest-message-util "^29.6.3" + jest-util "^29.6.3" fast-deep-equal@^3.1.1: version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-fifo@^1.1.0, fast-fifo@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== + version "3.3.1" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1603,112 +1793,142 @@ fast-glob@^3.2.9: merge2 "^1.3.0" micromatch "^4.0.4" -fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x: +fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fastq@^1.6.0: version "1.15.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== dependencies: reusify "^1.0.4" fb-watchman@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c" integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA== dependencies: bser "2.1.1" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: locate-path "^5.0.0" path-exists "^4.0.0" +flatbuffers@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/flatbuffers/-/flatbuffers-1.12.0.tgz#72e87d1726cb1b216e839ef02658aa87dcef68aa" + integrity sha512-c7CZADjRcl6j0PlvFy0ZqXQ67qSEZfrVPynmnL+2zPc+NtMvrF8Y0QceMo7QqnSPc7+uWjUIAbvCQ5WIKlMVdQ== + +follow-redirects@^1.14.8: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + for-each@^0.3.3: version "0.3.3" - resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== dependencies: is-callable "^1.1.3" foreground-child@^3.1.0: version "3.1.1" - resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + function-bind@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + version "1.1.6" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" -functions-have-names@^1.2.2: +functions-have-names@^1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== gensync@^1.0.0-beta.2: version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz" - integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" + integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== dependencies: function-bind "^1.1.1" has "^1.0.3" + has-proto "^1.0.1" has-symbols "^1.0.3" get-package-type@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== get-stream@^6.0.0: version "6.0.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== get-symbol-description@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: call-bind "^1.0.2" @@ -1716,49 +1936,42 @@ get-symbol-description@^1.0.0: getopts@^2.3.0: version "2.3.0" - resolved "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/getopts/-/getopts-2.3.0.tgz#71e5593284807e03e2427449d4f6712a268666f4" integrity sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA== +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + glob-parent@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-promise@^4.2.2: version "4.2.2" - resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-4.2.2.tgz" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-4.2.2.tgz#15f44bcba0e14219cd93af36da6bb905ff007877" integrity sha512-xcUzJ8NWN5bktoTIX7eOclO1Npxd/dyVqUJxlLIDasT4C7KZyqlPIwkdJ0Ypiy3p2ZKahTjK4M9uC3sNSfNMzw== dependencies: "@types/glob" "^7.1.3" glob@^10.2.5: - version "10.3.1" - resolved "https://registry.npmjs.org/glob/-/glob-10.3.1.tgz" - integrity sha512-9BKYcEeIs7QwlCYs+Y3GBvqAMISufUS0i2ELd11zpZjxI5V9iyRj0HgzB5/cLf2NY4vcYBTYzJ7GIui7j/4DOw== + version "10.3.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.4.tgz#c85c9c7ab98669102b6defda76d35c5b1ef9766f" + integrity sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ== dependencies: foreground-child "^3.1.0" jackspeak "^2.0.3" minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2" - path-scurry "^1.10.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.2.0: +glob@^7.1.3, glob@^7.1.4, glob@^7.2.0: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -1770,19 +1983,19 @@ glob@^7.2.0: globals@^11.1.0: version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== globalthis@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== dependencies: define-properties "^1.1.3" globby@^11.0.1: version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -1794,23 +2007,28 @@ globby@^11.0.1: gopd@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== dependencies: get-intrinsic "^1.1.3" graceful-fs@^4.1.2, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +guid-typescript@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc" + integrity sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ== handlebars@^4.7.7: - version "4.7.7" - resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + version "4.7.8" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.8.tgz#41c42c18b1be2365439188c77c6afae71c0cd9e9" + integrity sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ== dependencies: minimist "^1.2.5" - neo-async "^2.6.0" + neo-async "^2.6.2" source-map "^0.6.1" wordwrap "^1.0.0" optionalDependencies: @@ -1818,58 +2036,58 @@ handlebars@^4.7.7: hard-rejection@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== dependencies: get-intrinsic "^1.1.1" has-proto@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: has-symbols "^1.0.2" has@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== dependencies: function-bind "^1.1.1" header-case@^2.0.4: version "2.0.4" - resolved "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/header-case/-/header-case-2.0.4.tgz#5a42e63b55177349cf405beb8d775acabb92c063" integrity sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q== dependencies: capital-case "^1.0.4" @@ -1877,34 +2095,44 @@ header-case@^2.0.4: hosted-git-info@^2.1.4: version "2.8.9" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^4.0.1: version "4.1.0" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224" integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== dependencies: lru-cache "^6.0.0" html-escaper@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== human-signals@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^5.2.0: version "5.2.4" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-local@^3.0.2: version "3.1.0" - resolved "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== dependencies: pkg-dir "^4.2.0" @@ -1912,30 +2140,35 @@ import-local@^3.0.2: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== indent-string@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -internal-slot@^1.0.4: +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +internal-slot@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986" integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ== dependencies: get-intrinsic "^1.2.0" @@ -1943,34 +2176,39 @@ internal-slot@^1.0.4: side-channel "^1.0.4" irregular-plurals@^3.2.0: - version "3.4.0" - resolved "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.4.0.tgz" - integrity sha512-YXxECO/W6N9aMBVKMKKZ8TXESgq7EFrp3emCGGUcrYY1cgJIeZjoB75MTu8qi+NAKntS9NwPU8VdcQ3r6E6aWQ== + version "3.5.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-3.5.0.tgz#0835e6639aa8425bdc8b0d33d0dc4e89d9c01d2b" + integrity sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ== -is-array-buffer@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz" - integrity sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ== +is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" + integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== dependencies: call-bind "^1.0.2" - get-intrinsic "^1.1.3" + get-intrinsic "^1.2.0" is-typed-array "^1.1.10" is-arrayish@^0.2.1: version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.4" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: has-bigints "^1.0.1" is-boolean-object@^1.1.0: version "1.1.2" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" @@ -1978,70 +2216,70 @@ is-boolean-object@^1.1.0: is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.5.0, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" is-date-object@^1.0.1: version "1.0.5" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-fn@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== is-glob@^4.0.1: version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-negative-zero@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: version "1.0.7" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-plain-obj@^1.1.0: version "1.1.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg== is-regex@^1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" @@ -2049,61 +2287,62 @@ is-regex@^1.1.4: is-shared-array-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: call-bind "^1.0.2" is-stream@^2.0.0: version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: has-tostringtag "^1.0.0" is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" is-typed-array@^1.1.10, is-typed-array@^1.1.9: - version "1.1.10" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz" - integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A== + version "1.1.12" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" + integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" + which-typed-array "^1.1.11" is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-weakref@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== dependencies: call-bind "^1.0.2" +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + isexe@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isomorphic-fetch@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== dependencies: node-fetch "^2.6.1" @@ -2111,12 +2350,12 @@ isomorphic-fetch@^3.0.0: istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: +istanbul-lib-instrument@^5.0.4: version "5.2.1" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d" integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg== dependencies: "@babel/core" "^7.12.3" @@ -2125,18 +2364,29 @@ istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +istanbul-lib-instrument@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz#7a8af094cbfff1d5bb280f62ce043695ae8dd5b8" + integrity sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^7.5.4" + istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== dependencies: istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" + make-dir "^4.0.0" supports-color "^7.1.0" istanbul-lib-source-maps@^4.0.0: version "4.0.1" - resolved "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== dependencies: debug "^4.1.1" @@ -2144,391 +2394,389 @@ istanbul-lib-source-maps@^4.0.0: source-map "^0.6.1" istanbul-reports@^3.1.3: - version "3.1.5" - resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz" - integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + version "3.1.6" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.6.tgz#2544bcab4768154281a2f0870471902704ccaa1a" + integrity sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg== dependencies: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" jackspeak@^2.0.3: - version "2.2.1" - resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-2.2.1.tgz" - integrity sha512-MXbxovZ/Pm42f6cDIDkl3xpwv1AGwObKwfmjs2nQePiy85tP3fatofl3FC1aBsOtP/6fq5SbtgHwWcMsLP+bDw== + version "2.3.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.1.tgz#ce2effa4c458e053640e61938865a5b5fae98456" + integrity sha512-4iSY3Bh1Htv+kLhiiZunUhQ+OYXIn0ze3ulq8JeWrFKmhPAJSySV2+kdtRh2pGcCeF0s6oR8Oc+pYZynJj4t8A== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jest-changed-files@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz" - integrity sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag== +jest-changed-files@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-29.6.3.tgz#97cfdc93f74fb8af2a1acb0b78f836f1fb40c449" + integrity sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg== dependencies: execa "^5.0.0" + jest-util "^29.6.3" p-limit "^3.1.0" -jest-circus@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz" - integrity sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA== +jest-circus@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-29.6.4.tgz#f074c8d795e0cc0f2ebf0705086b1be6a9a8722f" + integrity sha512-YXNrRyntVUgDfZbjXWBMPslX1mQ8MrSG0oM/Y06j9EYubODIyHWP8hMUbjbZ19M3M+zamqEur7O80HODwACoJw== dependencies: - "@jest/environment" "^29.5.0" - "@jest/expect" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/environment" "^29.6.4" + "@jest/expect" "^29.6.4" + "@jest/test-result" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - dedent "^0.7.0" + dedent "^1.0.0" is-generator-fn "^2.0.0" - jest-each "^29.5.0" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-runtime "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-each "^29.6.3" + jest-matcher-utils "^29.6.4" + jest-message-util "^29.6.3" + jest-runtime "^29.6.4" + jest-snapshot "^29.6.4" + jest-util "^29.6.3" p-limit "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.3" pure-rand "^6.0.0" slash "^3.0.0" stack-utils "^2.0.3" -jest-cli@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz" - integrity sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw== +jest-cli@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-29.6.4.tgz#ad52f2dfa1b0291de7ec7f8d7c81ac435521ede0" + integrity sha512-+uMCQ7oizMmh8ZwRfZzKIEszFY9ksjjEQnTEMTaL7fYiL3Kw4XhqT9bYh+A4DQKUb67hZn2KbtEnDuHvcgK4pQ== dependencies: - "@jest/core" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.4" + "@jest/test-result" "^29.6.4" + "@jest/types" "^29.6.3" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.9" import-local "^3.0.2" - jest-config "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-config "^29.6.4" + jest-util "^29.6.3" + jest-validate "^29.6.3" prompts "^2.0.1" yargs "^17.3.1" -jest-config@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz" - integrity sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA== +jest-config@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-29.6.4.tgz#eff958ee41d4e1ee7a6106d02b74ad9fc427d79e" + integrity sha512-JWohr3i9m2cVpBumQFv2akMEnFEPVOh+9L2xIBJhJ0zOaci2ZXuKJj0tgMKQCBZAKA09H049IR4HVS/43Qb19A== dependencies: "@babel/core" "^7.11.6" - "@jest/test-sequencer" "^29.5.0" - "@jest/types" "^29.5.0" - babel-jest "^29.5.0" + "@jest/test-sequencer" "^29.6.4" + "@jest/types" "^29.6.3" + babel-jest "^29.6.4" chalk "^4.0.0" ci-info "^3.2.0" deepmerge "^4.2.2" glob "^7.1.3" graceful-fs "^4.2.9" - jest-circus "^29.5.0" - jest-environment-node "^29.5.0" - jest-get-type "^29.4.3" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-runner "^29.5.0" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-circus "^29.6.4" + jest-environment-node "^29.6.4" + jest-get-type "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.6.4" + jest-runner "^29.6.4" + jest-util "^29.6.3" + jest-validate "^29.6.3" micromatch "^4.0.4" parse-json "^5.2.0" - pretty-format "^29.5.0" + pretty-format "^29.6.3" slash "^3.0.0" strip-json-comments "^3.1.1" -jest-diff@^29.0.3, jest-diff@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz" - integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw== +jest-diff@^29.0.3, jest-diff@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a" + integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw== dependencies: chalk "^4.0.0" - diff-sequences "^29.4.3" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + diff-sequences "^29.6.3" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-docblock@^29.4.3: - version "29.4.3" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz" - integrity sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg== +jest-docblock@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.6.3.tgz#293dca5188846c9f7c0c2b1bb33e5b11f21645f2" + integrity sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ== dependencies: detect-newline "^3.0.0" -jest-each@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz" - integrity sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA== +jest-each@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.6.3.tgz#1956f14f5f0cb8ae0b2e7cabc10bb03ec817c142" + integrity sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" chalk "^4.0.0" - jest-get-type "^29.4.3" - jest-util "^29.5.0" - pretty-format "^29.5.0" - -jest-environment-node@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz" - integrity sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/types" "^29.5.0" + jest-get-type "^29.6.3" + jest-util "^29.6.3" + pretty-format "^29.6.3" + +jest-environment-node@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-29.6.4.tgz#4ce311549afd815d3cafb49e60a1e4b25f06d29f" + integrity sha512-i7SbpH2dEIFGNmxGCpSc2w9cA4qVD+wfvg2ZnfQ7XVrKL0NA5uDVBIiGH8SR4F0dKEv/0qI5r+aDomDf04DpEQ== + dependencies: + "@jest/environment" "^29.6.4" + "@jest/fake-timers" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-mock "^29.5.0" - jest-util "^29.5.0" + jest-mock "^29.6.3" + jest-util "^29.6.3" -jest-get-type@^29.4.3: - version "29.4.3" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz" - integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg== +jest-get-type@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1" + integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw== -jest-haste-map@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz" - integrity sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA== +jest-haste-map@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.6.4.tgz#97143ce833829157ea7025204b08f9ace609b96a" + integrity sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/graceful-fs" "^4.1.3" "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.9" - jest-regex-util "^29.4.3" - jest-util "^29.5.0" - jest-worker "^29.5.0" + jest-regex-util "^29.6.3" + jest-util "^29.6.3" + jest-worker "^29.6.4" micromatch "^4.0.4" walker "^1.0.8" optionalDependencies: fsevents "^2.3.2" -jest-leak-detector@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz" - integrity sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow== +jest-leak-detector@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz#b9661bc3aec8874e59aff361fa0c6d7cd507ea01" + integrity sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q== dependencies: - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-matcher-utils@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz" - integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw== +jest-matcher-utils@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24" + integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ== dependencies: chalk "^4.0.0" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - pretty-format "^29.5.0" + jest-diff "^29.6.4" + jest-get-type "^29.6.3" + pretty-format "^29.6.3" -jest-message-util@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz" - integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA== +jest-message-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.6.3.tgz#bce16050d86801b165f20cfde34dc01d3cf85fbf" + integrity sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA== dependencies: "@babel/code-frame" "^7.12.13" - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/stack-utils" "^2.0.0" chalk "^4.0.0" graceful-fs "^4.2.9" micromatch "^4.0.4" - pretty-format "^29.5.0" + pretty-format "^29.6.3" slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz" - integrity sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw== +jest-mock@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.6.3.tgz#433f3fd528c8ec5a76860177484940628bdf5e0a" + integrity sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.3" jest-pnp-resolver@^1.2.2: version "1.2.3" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e" integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w== -jest-regex-util@^29.4.3: - version "29.4.3" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz" - integrity sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg== +jest-regex-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-29.6.3.tgz#4a556d9c776af68e1c5f48194f4d0327d24e8a52" + integrity sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg== -jest-resolve-dependencies@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz" - integrity sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg== +jest-resolve-dependencies@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.4.tgz#20156b33c7eacbb6bb77aeba4bed0eab4a3f8734" + integrity sha512-7+6eAmr1ZBF3vOAJVsfLj1QdqeXG+WYhidfLHBRZqGN24MFRIiKG20ItpLw2qRAsW/D2ZUUmCNf6irUr/v6KHA== dependencies: - jest-regex-util "^29.4.3" - jest-snapshot "^29.5.0" + jest-regex-util "^29.6.3" + jest-snapshot "^29.6.4" -jest-resolve@*, jest-resolve@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz" - integrity sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w== +jest-resolve@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-29.6.4.tgz#e34cb06f2178b429c38455d98d1a07572ac9faa3" + integrity sha512-fPRq+0vcxsuGlG0O3gyoqGTAxasagOxEuyoxHeyxaZbc9QNek0AmJWSkhjlMG+mTsj+8knc/mWb3fXlRNVih7Q== dependencies: chalk "^4.0.0" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" + jest-haste-map "^29.6.4" jest-pnp-resolver "^1.2.2" - jest-util "^29.5.0" - jest-validate "^29.5.0" + jest-util "^29.6.3" + jest-validate "^29.6.3" resolve "^1.20.0" resolve.exports "^2.0.0" slash "^3.0.0" -jest-runner@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz" - integrity sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ== +jest-runner@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-29.6.4.tgz#b3b8ccb85970fde0fae40c73ee11eb75adccfacf" + integrity sha512-SDaLrMmtVlQYDuG0iSPYLycG8P9jLI+fRm8AF/xPKhYDB2g6xDWjXBrR5M8gEWsK6KVFlebpZ4QsrxdyIX1Jaw== dependencies: - "@jest/console" "^29.5.0" - "@jest/environment" "^29.5.0" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/console" "^29.6.4" + "@jest/environment" "^29.6.4" + "@jest/test-result" "^29.6.4" + "@jest/transform" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" emittery "^0.13.1" graceful-fs "^4.2.9" - jest-docblock "^29.4.3" - jest-environment-node "^29.5.0" - jest-haste-map "^29.5.0" - jest-leak-detector "^29.5.0" - jest-message-util "^29.5.0" - jest-resolve "^29.5.0" - jest-runtime "^29.5.0" - jest-util "^29.5.0" - jest-watcher "^29.5.0" - jest-worker "^29.5.0" + jest-docblock "^29.6.3" + jest-environment-node "^29.6.4" + jest-haste-map "^29.6.4" + jest-leak-detector "^29.6.3" + jest-message-util "^29.6.3" + jest-resolve "^29.6.4" + jest-runtime "^29.6.4" + jest-util "^29.6.3" + jest-watcher "^29.6.4" + jest-worker "^29.6.4" p-limit "^3.1.0" source-map-support "0.5.13" -jest-runtime@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz" - integrity sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw== - dependencies: - "@jest/environment" "^29.5.0" - "@jest/fake-timers" "^29.5.0" - "@jest/globals" "^29.5.0" - "@jest/source-map" "^29.4.3" - "@jest/test-result" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" +jest-runtime@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-29.6.4.tgz#b0bc495c9b6b12a0a7042ac34ca9bb85f8cd0ded" + integrity sha512-s/QxMBLvmwLdchKEjcLfwzP7h+jsHvNEtxGP5P+Fl1FMaJX2jMiIqe4rJw4tFprzCwuSvVUo9bn0uj4gNRXsbA== + dependencies: + "@jest/environment" "^29.6.4" + "@jest/fake-timers" "^29.6.4" + "@jest/globals" "^29.6.4" + "@jest/source-map" "^29.6.3" + "@jest/test-result" "^29.6.4" + "@jest/transform" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" cjs-module-lexer "^1.0.0" collect-v8-coverage "^1.0.0" glob "^7.1.3" graceful-fs "^4.2.9" - jest-haste-map "^29.5.0" - jest-message-util "^29.5.0" - jest-mock "^29.5.0" - jest-regex-util "^29.4.3" - jest-resolve "^29.5.0" - jest-snapshot "^29.5.0" - jest-util "^29.5.0" + jest-haste-map "^29.6.4" + jest-message-util "^29.6.3" + jest-mock "^29.6.3" + jest-regex-util "^29.6.3" + jest-resolve "^29.6.4" + jest-snapshot "^29.6.4" + jest-util "^29.6.3" slash "^3.0.0" strip-bom "^4.0.0" -jest-snapshot@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz" - integrity sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g== +jest-snapshot@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-29.6.4.tgz#9833eb6b66ff1541c7fd8ceaa42d541f407b4876" + integrity sha512-VC1N8ED7+4uboUKGIDsbvNAZb6LakgIPgAF4RSpF13dN6YaMokfRqO+BaqK4zIh6X3JffgwbzuGqDEjHm/MrvA== dependencies: "@babel/core" "^7.11.6" "@babel/generator" "^7.7.2" "@babel/plugin-syntax-jsx" "^7.7.2" "@babel/plugin-syntax-typescript" "^7.7.2" - "@babel/traverse" "^7.7.2" "@babel/types" "^7.3.3" - "@jest/expect-utils" "^29.5.0" - "@jest/transform" "^29.5.0" - "@jest/types" "^29.5.0" - "@types/babel__traverse" "^7.0.6" - "@types/prettier" "^2.1.5" + "@jest/expect-utils" "^29.6.4" + "@jest/transform" "^29.6.4" + "@jest/types" "^29.6.3" babel-preset-current-node-syntax "^1.0.0" chalk "^4.0.0" - expect "^29.5.0" + expect "^29.6.4" graceful-fs "^4.2.9" - jest-diff "^29.5.0" - jest-get-type "^29.4.3" - jest-matcher-utils "^29.5.0" - jest-message-util "^29.5.0" - jest-util "^29.5.0" + jest-diff "^29.6.4" + jest-get-type "^29.6.3" + jest-matcher-utils "^29.6.4" + jest-message-util "^29.6.3" + jest-util "^29.6.3" natural-compare "^1.4.0" - pretty-format "^29.5.0" - semver "^7.3.5" + pretty-format "^29.6.3" + semver "^7.5.3" -jest-util@^29.0.0, jest-util@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz" - integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ== +jest-util@^29.0.0, jest-util@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.6.3.tgz#e15c3eac8716440d1ed076f09bc63ace1aebca63" + integrity sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" "@types/node" "*" chalk "^4.0.0" ci-info "^3.2.0" graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz" - integrity sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ== +jest-validate@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-29.6.3.tgz#a75fca774cfb1c5758c70d035d30a1f9c2784b4d" + integrity sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg== dependencies: - "@jest/types" "^29.5.0" + "@jest/types" "^29.6.3" camelcase "^6.2.0" chalk "^4.0.0" - jest-get-type "^29.4.3" + jest-get-type "^29.6.3" leven "^3.1.0" - pretty-format "^29.5.0" + pretty-format "^29.6.3" -jest-watcher@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz" - integrity sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA== +jest-watcher@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-29.6.4.tgz#633eb515ae284aa67fd6831f1c9d1b534cf0e0ba" + integrity sha512-oqUWvx6+On04ShsT00Ir9T4/FvBeEh2M9PTubgITPxDa739p4hoQweWPRGyYeaojgT0xTpZKF0Y/rSY1UgMxvQ== dependencies: - "@jest/test-result" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/test-result" "^29.6.4" + "@jest/types" "^29.6.3" "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" emittery "^0.13.1" - jest-util "^29.5.0" + jest-util "^29.6.3" string-length "^4.0.1" -jest-worker@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz" - integrity sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA== +jest-worker@^29.6.4: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.6.4.tgz#f34279f4afc33c872b470d4af21b281ac616abd3" + integrity sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q== dependencies: "@types/node" "*" - jest-util "^29.5.0" + jest-util "^29.6.3" merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^29.0.0, jest@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz" - integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== +jest@^29.5.0: + version "29.6.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-29.6.4.tgz#7c48e67a445ba264b778253b5d78d4ebc9d0a622" + integrity sha512-tEFhVQFF/bzoYV1YuGyzLPZ6vlPrdfvDmmAxudA1dLEuiztqg2Rkx20vkKY32xiDROcD2KXlgZ7Cu8RPeEHRKw== dependencies: - "@jest/core" "^29.5.0" - "@jest/types" "^29.5.0" + "@jest/core" "^29.6.4" + "@jest/types" "^29.6.3" import-local "^3.0.2" - jest-cli "^29.5.0" + jest-cli "^29.6.4" js-tokens@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^3.13.1: version "3.14.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== dependencies: argparse "^1.0.7" @@ -2536,59 +2784,66 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" jsesc@^2.5.1: version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== json-parse-better-errors@^1.0.1: version "1.0.2" - resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-parse-even-better-errors@^2.3.0: version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-schema-traverse@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== -json5@^2.2.2, json5@^2.2.3: +json5@^2.2.3: version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== kind-of@^6.0.3: version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== kleur@^3.0.3: version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== leven@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== +lie@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e" + integrity sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw== + dependencies: + immediate "~3.0.5" + lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== load-json-file@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw== dependencies: graceful-fs "^4.1.2" @@ -2596,99 +2851,111 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +localforage@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.10.0.tgz#5c465dc5f62b2807c3a84c0c6a1b1b3212781dd4" + integrity sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg== + dependencies: + lie "3.1.1" + locate-path@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: p-locate "^4.1.0" lodash.memoize@4.x: version "4.1.2" - resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== lodash@^4.17.21: version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== log-symbols@^4.0.0: version "4.1.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" is-unicode-supported "^0.1.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + lower-case@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== dependencies: tslib "^2.0.3" lru-cache@^5.1.1: version "5.1.1" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: yallist "^3.0.2" lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" "lru-cache@^9.1.1 || ^10.0.0": - version "10.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.0.0.tgz" - integrity sha512-svTf/fzsKHffP42sujkO/Rjs37BCIsQVRCeNYIm9WN8rgT7ffoUnRtZCqU+6BqcSBdv8gwJeTz8knJpgACeQMw== + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== +make-dir@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" + integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== dependencies: - semver "^6.0.0" + semver "^7.5.3" -make-error@^1.1.1, make-error@1.x: +make-error@1.x, make-error@^1.1.1: version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== makeerror@1.0.12: version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== dependencies: tmpl "1.0.5" map-obj@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== map-obj@^4.0.0: version "4.3.0" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== marked@^4.0.15: version "4.3.0" - resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== memorystream@^0.3.1: version "0.3.1" - resolved "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== meow@^9.0.0: version "9.0.0" - resolved "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz" + resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" integrity sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ== dependencies: "@types/minimist" "^1.2.0" @@ -2706,118 +2973,157 @@ meow@^9.0.0: merge-stream@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4: version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" picomatch "^2.3.1" +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mimic-fn@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== minimatch@^3.0.4, minimatch@^3.1.1: version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" minimatch@^9.0.1: - version "9.0.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.2.tgz" - integrity sha512-PZOT9g5v2ojiTL7r1xF6plNHLtOeTpSlDI007As2NlA2aYBMfVom17yqa6QzhmDP8QOhn7LjHTg7DFCVSSa6yg== + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== dependencies: brace-expansion "^2.0.1" minimist-options@4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== dependencies: arrify "^1.0.1" is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2": - version "6.0.2" - resolved "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz" - integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== ms@2.1.2: version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -neo-async@^2.6.0: +neo-async@^2.6.2: version "2.6.2" - resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== nice-try@^1.0.4: version "1.0.5" - resolved "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== no-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== dependencies: lower-case "^2.0.2" tslib "^2.0.3" +node-abi@^3.3.0: + version "3.47.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.47.0.tgz#6cbfa2916805ae25c2b7156ca640131632eb05e8" + integrity sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A== + dependencies: + semver "^7.3.5" + +node-addon-api@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-6.1.0.tgz#ac8470034e58e67d0c6f1204a18ae6995d9c0d76" + integrity sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA== + node-fetch@^2.6.1: - version "2.6.9" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz" - integrity sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg== + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" node-int64@^0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== -node-releases@^2.0.8: - version "2.0.10" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz" - integrity sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w== +node-releases@^2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== node-watch@^0.7.3: - version "0.7.3" - resolved "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz" - integrity sha512-3l4E8uMPY1HdMMryPRUAl+oIHtXtyiTlIiESNSVSNxcPfzAFzeTbXFQkZfAwBbo0B1qMSG8nUABx+Gd+YrbKrQ== + version "0.7.4" + resolved "https://registry.yarnpkg.com/node-watch/-/node-watch-0.7.4.tgz#34557106948cd4b8ddff9aa3d284774004548824" + integrity sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ== normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: version "2.5.0" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== dependencies: hosted-git-info "^2.1.4" @@ -2827,7 +3133,7 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.5.0: normalize-package-data@^3.0.0: version "3.0.3" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== dependencies: hosted-git-info "^4.0.1" @@ -2837,12 +3143,12 @@ normalize-package-data@^3.0.0: normalize-path@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== npm-run-all@^4.1.5: version "4.1.5" - resolved "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== dependencies: ansi-styles "^3.2.1" @@ -2857,24 +3163,24 @@ npm-run-all@^4.1.5: npm-run-path@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== dependencies: path-key "^3.0.0" -object-inspect@^1.12.2, object-inspect@^1.9.0: +object-inspect@^1.12.3, object-inspect@^1.9.0: version "1.12.3" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.4: version "4.1.4" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== dependencies: call-bind "^1.0.2" @@ -2882,23 +3188,67 @@ object.assign@^4.1.4: has-symbols "^1.0.3" object-keys "^1.1.1" -once@^1.3.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" onetime@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: mimic-fn "^2.1.0" +onnx-proto@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/onnx-proto/-/onnx-proto-4.0.4.tgz#2431a25bee25148e915906dda0687aafe3b9e044" + integrity sha512-aldMOB3HRoo6q/phyB6QRQxSt895HNNw82BNyZ2CMh4bjeKv7g/c+VpAFtJuEMVfYLMbRx61hbuqnKceLeDcDA== + dependencies: + protobufjs "^6.8.8" + +onnxruntime-common@1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.15.1.tgz#1b8af102409b5465811a7faef31cf438bded5425" + integrity sha512-Y89eJ8QmaRsPZPWLaX7mfqhj63ny47rSkQe80hIo+lvBQdrdXYR9VO362xvZulk9DFkCnXmGidprvgJ07bKsIQ== + +onnxruntime-common@~1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/onnxruntime-common/-/onnxruntime-common-1.14.0.tgz#2bb5dac5261269779aa5fb6536ca379657de8bf6" + integrity sha512-3LJpegM2iMNRX2wUmtYfeX/ytfOzNwAWKSq1HbRrKc9+uqG/FsEA0bbKZl1btQeZaXhC26l44NWpNUeXPII7Ew== + +onnxruntime-node@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/onnxruntime-node/-/onnxruntime-node-1.14.0.tgz#c4ae6c355cfae7d83abaf36dd39a905c4a010217" + integrity sha512-5ba7TWomIV/9b6NH/1x/8QEeowsb+jBEvFzU6z0T4mNsFwdPqXeFUM7uxC6QeSRkEbWu3qEB0VMjrvzN/0S9+w== + dependencies: + onnxruntime-common "~1.14.0" + +onnxruntime-web@1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/onnxruntime-web/-/onnxruntime-web-1.14.0.tgz#c8cee538781b1d4c1c6b043934f4a3e6ddf1466e" + integrity sha512-Kcqf43UMfW8mCydVGcX9OMXI2VN17c0p6XvR7IPSZzBf/6lteBzXHvcEVWDPmCKuGombl997HgLqj91F11DzXw== + dependencies: + flatbuffers "^1.12.0" + guid-typescript "^1.0.9" + long "^4.0.0" + onnx-proto "^4.0.4" + onnxruntime-common "~1.14.0" + platform "^1.3.6" + +openai@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/openai/-/openai-3.3.0.tgz#a6408016ad0945738e1febf43f2fccca83a3f532" + integrity sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ== + dependencies: + axios "^0.26.0" + form-data "^4.0.0" + openapi-generator-plus@^2.6.0: version "2.6.0" - resolved "https://registry.npmjs.org/openapi-generator-plus/-/openapi-generator-plus-2.6.0.tgz" + resolved "https://registry.yarnpkg.com/openapi-generator-plus/-/openapi-generator-plus-2.6.0.tgz#797d7b25f682b2f764ed2a0e3f49efd7517d62a1" integrity sha512-DRdlJn7goQDDFGw1/9RhU3ibNXm9XMkSTg5cNmoz4d1vvM/CHeI+FzbPcStPgcshs0i0jYUZffmBpNhUEkb27g== dependencies: "@openapi-generator-plus/core" "2.6.0" @@ -2910,40 +3260,40 @@ openapi-generator-plus@^2.6.0: node-watch "^0.7.3" yaml "^2.0.1" -openapi-types@>=7: - version "12.1.3" - resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz" - integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== - p-limit@^2.2.0: version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: p-try "^2.0.0" p-limit@^3.1.0: version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: p-limit "^2.2.0" p-try@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +pako@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + param-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== dependencies: dot-case "^3.0.4" @@ -2951,25 +3301,15 @@ param-case@^3.0.4: parse-json@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw== dependencies: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-json@^5.2.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== dependencies: "@babel/code-frame" "^7.0.0" @@ -2979,7 +3319,7 @@ parse-json@^5.2.0: pascal-case@^3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== dependencies: no-case "^3.0.4" @@ -2987,7 +3327,7 @@ pascal-case@^3.1.2: path-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" integrity sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg== dependencies: dot-case "^3.0.4" @@ -2995,153 +3335,213 @@ path-case@^3.0.4: path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== -path-key@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.0.tgz" - integrity sha512-tZFEaRQbMLjwrsmidsGJ6wDMv0iazJWk6SfIKnY4Xru8auXgmJkOBa5DUbYFcFD2Rzk2+KDlIiF0GVXNCbgC7g== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== dependencies: lru-cache "^9.1.1 || ^10.0.0" - minipass "^5.0.0 || ^6.0.2" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-type@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== dependencies: pify "^3.0.0" path-type@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pidtree@^0.3.0: version "0.3.1" - resolved "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== pify@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== pirates@^4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" - integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + version "4.0.6" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== pkg-dir@^4.2.0: version "4.2.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== dependencies: find-up "^4.0.0" +platform@^1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.6.tgz#48b4ce983164b209c2d45a107adb31f473a6e7a7" + integrity sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg== + plur@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/plur/-/plur-4.0.0.tgz#729aedb08f452645fe8c58ef115bf16b0a73ef84" integrity sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg== dependencies: irregular-plurals "^3.2.0" pluralize@^8.0.0: version "8.0.0" - resolved "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prettier@2.8.7: version "2.8.7" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.7.tgz#bb79fc8729308549d28fe3a98fce73d2c0656450" integrity sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw== -pretty-format@^29.0.0, pretty-format@^29.5.0: - version "29.5.0" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz" - integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw== +pretty-format@^29.0.0, pretty-format@^29.6.3: + version "29.6.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7" + integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw== dependencies: - "@jest/schemas" "^29.4.3" + "@jest/schemas" "^29.6.3" ansi-styles "^5.0.0" react-is "^18.0.0" prompts@^2.0.1: version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" sisteransi "^1.0.5" +protobufjs@^6.8.8: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^2.1.0: version "2.3.0" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== pure-rand@^6.0.0: version "6.0.2" - resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.2.tgz#a9c2ddcae9b68d736a8163036f088a2781c8b306" integrity sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ== querystringify@^2.1.1: version "2.2.0" - resolved "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + quick-lru@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-is@^18.0.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== dependencies: find-up "^4.1.0" @@ -3150,7 +3550,7 @@ read-pkg-up@^7.0.0, read-pkg-up@^7.0.1: read-pkg@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA== dependencies: load-json-file "^4.0.0" @@ -3159,7 +3559,7 @@ read-pkg@^3.0.0: read-pkg@^5.2.0: version "5.2.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== dependencies: "@types/normalize-package-data" "^2.4.0" @@ -3167,169 +3567,188 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" +readable-stream@^3.1.1, readable-stream@^3.4.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + redent@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== dependencies: indent-string "^4.0.0" strip-indent "^3.0.0" -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== +regexp.prototype.flags@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" + integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== dependencies: call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" + define-properties "^1.2.0" + functions-have-names "^1.2.3" require-directory@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== require-from-string@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== requires-port@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== resolve-cwd@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== dependencies: resolve-from "^5.0.0" resolve-from@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== resolve.exports@^2.0.0: version "2.0.2" - resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== resolve@^1.10.0, resolve@^1.20.0: - version "1.22.1" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" reusify@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@^5.0.0: version "5.0.1" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.1.tgz#0881323ab94ad45fec7c0221f27ea1a142f3f0d0" integrity sha512-OfFZdwtd3lZ+XZzYP/6gTACubwFcHdLRqS9UX3UwpU2dnGQYkPFISRwvM3w9IiB2w7bW5qGo/uAwE4SmXXSKvg== dependencies: glob "^10.2.5" run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" +safe-array-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" + integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + has-symbols "^1.0.3" + isarray "^2.0.5" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-regex-test@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== dependencies: call-bind "^1.0.2" get-intrinsic "^1.1.3" is-regex "^1.1.4" -semver@^5.5.0, "semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^6.0.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.4: - version "7.3.8" - resolved "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== - dependencies: - lru-cache "^6.0.0" +"semver@2 || 3 || 4 || 5", semver@^5.5.0: + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^7.3.5: - version "7.5.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== - dependencies: - lru-cache "^6.0.0" +semver@^6.3.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.5.3: - version "7.5.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz" - integrity sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ== +semver@^7.3.4, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" sentence-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" integrity sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg== dependencies: no-case "^3.0.4" tslib "^2.0.3" upper-case-first "^2.0.2" +sharp@^0.32.0: + version "0.32.5" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.32.5.tgz#9ddc78ead6446094f51e50355a2d4ec6e7220cd4" + integrity sha512-0dap3iysgDkNaPOaOL4X/0akdu0ma62GcdC2NBQ+93eqpePdDdr2/LM0sFdDSMmN7yS+odyZtPsb7tx/cYBKnQ== + dependencies: + color "^4.2.3" + detect-libc "^2.0.2" + node-addon-api "^6.1.0" + prebuild-install "^7.1.1" + semver "^7.5.4" + simple-get "^4.0.1" + tar-fs "^3.0.4" + tunnel-agent "^0.6.0" + shebang-command@^1.2.0: version "1.2.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== dependencies: shebang-regex "^1.0.0" shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== shell-quote@^1.6.1: - version "1.8.0" - resolved "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.0.tgz" - integrity sha512-QHsz8GgQIGKlRi24yFc6a6lN69Idnx634w49ay6+jA5yFh7a1UY+4Rp6HPx/L/1zcEDPEij8cIsiqR6bQsE5VQ== + version "1.8.1" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" + integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" @@ -3338,27 +3757,48 @@ side-channel@^1.0.4: signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== signal-exit@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz" - integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0, simple-get@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" sisteransi@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== slash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== snake-case@^3.0.4: version "3.0.4" - resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== dependencies: dot-case "^3.0.4" @@ -3366,7 +3806,7 @@ snake-case@^3.0.4: source-map-support@0.5.13: version "0.5.13" - resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== dependencies: buffer-from "^1.0.0" @@ -3374,67 +3814,66 @@ source-map-support@0.5.13: source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" spdx-exceptions@^2.1.0: version "2.3.0" - resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== spdx-expression-parse@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== dependencies: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.12" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz" - integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + version "3.0.13" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== stack-utils@^2.0.3: version "2.0.6" - resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ== dependencies: escape-string-regexp "^2.0.0" +streamx@^2.15.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.15.1.tgz#396ad286d8bc3eeef8f5cea3f029e81237c024c6" + integrity sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA== + dependencies: + fast-fifo "^1.1.0" + queue-tick "^1.0.1" + string-length@^4.0.1: version "4.0.2" - resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== dependencies: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: emoji-regex "^8.0.0" @@ -3443,7 +3882,7 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== dependencies: eastasianwidth "^0.2.0" @@ -3452,16 +3891,25 @@ string-width@^5.0.1, string-width@^5.1.2: string.prototype.padend@^3.0.0: version "3.1.4" - resolved "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6" integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" es-abstract "^1.20.4" +string.prototype.trim@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533" + integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.4" + string.prototype.trimend@^1.0.6: version "1.0.6" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ== dependencies: call-bind "^1.0.2" @@ -3470,85 +3918,90 @@ string.prototype.trimend@^1.0.6: string.prototype.trimstart@^1.0.6: version "1.0.6" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA== dependencies: call-bind "^1.0.2" define-properties "^1.1.4" es-abstract "^1.20.4" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - ansi-regex "^5.0.1" + safe-buffer "~5.2.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.1: version "7.1.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: ansi-regex "^6.0.1" strip-bom@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== strip-bom@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== strip-final-newline@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== strip-indent@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== dependencies: min-indent "^1.0.0" strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + supports-color@^5.3.0: version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-color@^8.0.0: version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" supports-hyperlinks@^2.0.0: version "2.3.0" - resolved "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624" integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA== dependencies: has-flag "^4.0.0" @@ -3556,12 +4009,51 @@ supports-hyperlinks@^2.0.0: supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +tar-fs@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-fs@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.4.tgz#a21dc60a2d5d9f55e0089ccd78124f1d3771dbbf" + integrity sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w== + dependencies: + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^3.1.5" + +tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar-stream@^3.1.5: + version "3.1.6" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.6.tgz#6520607b55a06f4a2e2e04db360ba7d338cc5bab" + integrity sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + test-exclude@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== dependencies: "@istanbuljs/schema" "^0.1.2" @@ -3570,34 +4062,34 @@ test-exclude@^6.0.0: tmpl@1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" tr46@~0.0.3: version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== trim-newlines@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== ts-jest@^29.1.0: version "29.1.1" - resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.1.tgz#f58fe62c63caf7bfcc5cc6472082f79180f0815b" integrity sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA== dependencies: bs-logger "0.x" @@ -3609,9 +4101,9 @@ ts-jest@^29.1.0: semver "^7.5.3" yargs-parser "^21.0.1" -ts-node@^10.9.1, ts-node@>=9.0.0: +ts-node@^10.9.1: version "10.9.1" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -3630,7 +4122,7 @@ ts-node@^10.9.1, ts-node@>=9.0.0: tsd@^0.28.1: version "0.28.1" - resolved "https://registry.npmjs.org/tsd/-/tsd-0.28.1.tgz" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.28.1.tgz#a470bd88a80ff138496c71606072893fe5820e62" integrity sha512-FeYrfJ05QgEMW/qOukNCr4fAJHww4SaKnivAXRv4g5kj4FeLpNV7zH4dorzB9zAfVX4wmA7zWu/wQf7kkcvfbw== dependencies: "@tsd/typescript" "~5.0.2" @@ -3642,57 +4134,94 @@ tsd@^0.28.1: read-pkg-up "^7.0.0" tslib@^2.0.3: - version "2.3.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" type-detect@4.0.8: version "4.0.8" - resolved "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== type-fest@^0.18.0: version "0.18.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== type-fest@^0.21.3: version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== type-fest@^0.6.0: version "0.6.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== type-fest@^0.8.1: version "0.8.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + typed-array-length@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== dependencies: call-bind "^1.0.2" for-each "^0.3.3" is-typed-array "^1.1.9" -typescript@^5.0.4, typescript@>=2.7, "typescript@>=4.3 <6": - version "5.1.6" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz" - integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== +typescript@^5.0.4: + version "5.2.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" + integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== uglify-js@^3.1.4: version "3.17.4" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== unbox-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: call-bind "^1.0.2" @@ -3700,51 +4229,56 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" -update-browserslist-db@^1.0.10: - version "1.0.10" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz" - integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ== +update-browserslist-db@^1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== dependencies: escalade "^3.1.1" picocolors "^1.0.0" upper-case-first@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" integrity sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg== dependencies: tslib "^2.0.3" upper-case@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-2.0.2.tgz#d89810823faab1df1549b7d97a76f8662bae6f7a" integrity sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg== dependencies: tslib "^2.0.3" uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" url-parse@^1.5.10: version "1.5.10" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^9.0.1: version "9.1.0" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz#1b83ed4e397f58c85c266a570fc2558b5feb9265" integrity sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" @@ -3753,7 +4287,7 @@ v8-to-istanbul@^9.0.1: validate-npm-package-license@^3.0.1: version "3.0.4" - resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== dependencies: spdx-correct "^3.0.0" @@ -3761,24 +4295,24 @@ validate-npm-package-license@^3.0.1: walker@^1.0.8: version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== dependencies: makeerror "1.0.12" webidl-conversions@^3.0.0: version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== whatwg-fetch@^3.4.1: - version "3.6.2" - resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== + version "3.6.18" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.18.tgz#2f640cdee315abced7daeaed2309abd1e44e62d4" + integrity sha512-ltN7j66EneWn5TFDO4L9inYC1D+Czsxlrw2SalgjMmEMkLfA5SIZxEFdE6QtHFiiM6Q7WL32c7AkI3w6yxM84Q== whatwg-url@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: tr46 "~0.0.3" @@ -3786,7 +4320,7 @@ whatwg-url@^5.0.0: which-boxed-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" @@ -3795,49 +4329,39 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" -which-typed-array@^1.1.9: - version "1.1.9" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz" - integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA== +which-typed-array@^1.1.10, which-typed-array@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a" + integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew== dependencies: available-typed-arrays "^1.0.5" call-bind "^1.0.2" for-each "^0.3.3" gopd "^1.0.1" has-tostringtag "^1.0.0" - is-typed-array "^1.1.10" which@^1.2.9: version "1.3.1" - resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" which@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" wordwrap@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -3846,7 +4370,7 @@ wrap-ansi@^7.0.0: wrap-ansi@^8.1.0: version "8.1.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== dependencies: ansi-styles "^6.1.0" @@ -3855,12 +4379,12 @@ wrap-ansi@^8.1.0: wrappy@1: version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^4.0.2: version "4.0.2" - resolved "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd" integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg== dependencies: imurmurhash "^0.1.4" @@ -3868,42 +4392,37 @@ write-file-atomic@^4.0.2: y18n@^5.0.5: version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yallist@^3.0.2: version "3.1.1" - resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== yaml@^2.0.1: - version "2.3.1" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz" - integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== + version "2.3.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.2.tgz#f522db4313c671a0ca963a75670f1c12ea909144" + integrity sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg== yargs-parser@^20.2.3: version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.0.1: - version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs-parser@^21.1.1: +yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.3.1: version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" @@ -3916,10 +4435,10 @@ yargs@^17.3.1: yn@3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 9394c76efb96c2c12f2ffd663f566c4524a0e2ec Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Mon, 4 Sep 2023 19:12:08 +0300 Subject: [PATCH 15/67] [BUG]: Fixed Static Auth Token range of allowed chars (#1076) Refs: #1066 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Small bugfix to extend the range of allowed characters to include printable ascii without whitespaces ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes Docs updated - https://github.com/chroma-core/docs/pull/128 --- chromadb/auth/token/__init__.py | 5 ++++- chromadb/test/auth/test_token_auth.py | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/chromadb/auth/token/__init__.py b/chromadb/auth/token/__init__.py index ec6dcdc45540..5132fa357987 100644 --- a/chromadb/auth/token/__init__.py +++ b/chromadb/auth/token/__init__.py @@ -68,7 +68,10 @@ def get_auth_info(self) -> Tuple[str, SecretStr]: def check_token(token: str) -> None: token_str = str(token) - if not all(c in string.ascii_letters + string.digits for c in token_str): + if not all( + c in string.digits + string.ascii_letters + string.punctuation + for c in token_str + ): raise ValueError("Invalid token. Must contain only ASCII letters and digits.") diff --git a/chromadb/test/auth/test_token_auth.py b/chromadb/test/auth/test_token_auth.py index cb372ec899c9..4e99baae3064 100644 --- a/chromadb/test/auth/test_token_auth.py +++ b/chromadb/test/auth/test_token_auth.py @@ -25,7 +25,11 @@ def token_config(draw: st.DrawFn) -> Dict[str, Any]: ) ) token = draw( - st.text(alphabet=string.ascii_letters + string.digits, min_size=1, max_size=50) + st.text( + alphabet=string.digits + string.ascii_letters + string.punctuation, + min_size=1, + max_size=50, + ) ) persistence = draw(st.booleans()) return { @@ -75,7 +79,7 @@ def random_token(draw: st.DrawFn) -> str: @st.composite def invalid_token(draw: st.DrawFn) -> str: opposite_alphabet = set(string.printable) - set( - string.ascii_letters + string.digits + string.digits + string.ascii_letters + string.punctuation ) token = draw(st.text(alphabet=list(opposite_alphabet), min_size=1, max_size=50)) return token From 8a4f8ef6443c458e53d17b0332b77345764f0ef2 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline Date: Mon, 4 Sep 2023 09:31:49 -0700 Subject: [PATCH 16/67] [CLN] replaced lambdas with def (#1070) ## Description of changes - Reduced unnecessary complexity. Replaced lambdas to def. ## Test plan tests run successfully. There should be no additional ut-s. Client/calling code for the changed names is not changed. ## Documentation Changes * added docstring to module. --- chromadb/utils/distance_functions.py | 32 ++++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/chromadb/utils/distance_functions.py b/chromadb/utils/distance_functions.py index 88fc77060f76..e7e77bf7f94c 100644 --- a/chromadb/utils/distance_functions.py +++ b/chromadb/utils/distance_functions.py @@ -1,18 +1,22 @@ -from typing import Dict, Callable +""" +These functions match what the spec of hnswlib is. +""" import numpy as np -import numpy.typing as npt +from numpy.typing import ArrayLike -# These match what the spec of hnswlib is -# This epsilon is used to prevent division by zero and the value is the same -# https://github.com/nmslib/hnswlib/blob/359b2ba87358224963986f709e593d799064ace6/python_bindings/bindings.cpp#L238 -NORM_EPS = 1e-30 -distance_functions: Dict[str, Callable[[npt.ArrayLike, npt.ArrayLike], float]] = { - "l2": lambda x, y: np.linalg.norm(x - y) ** 2, # type: ignore - "cosine": lambda x, y: 1 - np.dot(x, y) / ((np.linalg.norm(x) + NORM_EPS) * (np.linalg.norm(y) + NORM_EPS)), # type: ignore - "ip": lambda x, y: 1 - np.dot(x, y), # type: ignore -} +def l2(x: ArrayLike, y: ArrayLike) -> float: + return np.linalg.norm(x - y) ** 2 -l2 = distance_functions["l2"] -cosine = distance_functions["cosine"] -ip = distance_functions["ip"] + +def cosine(x: ArrayLike, y: ArrayLike) -> float: + # This epsilon is used to prevent division by zero, and the value is the same + # https://github.com/nmslib/hnswlib/blob/359b2ba87358224963986f709e593d799064ace6/python_bindings/bindings.cpp#L238 + NORM_EPS = 1e-30 + return 1 - np.dot(x, y) / ( + (np.linalg.norm(x) + NORM_EPS) * (np.linalg.norm(y) + NORM_EPS) + ) + + +def ip(x: ArrayLike, y: ArrayLike) -> float: + return 1 - np.dot(x, y) From c50408f676a77c5428e2528ef96eeb46495c93b9 Mon Sep 17 00:00:00 2001 From: Liquan Pei Date: Mon, 4 Sep 2023 10:34:27 -0700 Subject: [PATCH 17/67] Add CIP-1 Allow Filtering for Collections (#855) ## Description of changes This diff adds CIP-1 Allow Filtering for Collections *Summarize the changes made by this PR.* - Improvements & Bug fixes - ... - New functionality - ... ## Test plan *How are these changes tested?* ## Documentation Changes *Are all docstrings for user-facing APIs updated if required? Do we need to make documentation changes in the [docs repository](https://github.com/chroma-core/docs)?* Co-authored-by: Liquan Pei --- .../CIP-1_Allow_Filtering_for_Collections.md | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/cip/CIP-1_Allow_Filtering_for_Collections.md diff --git a/docs/cip/CIP-1_Allow_Filtering_for_Collections.md b/docs/cip/CIP-1_Allow_Filtering_for_Collections.md new file mode 100644 index 000000000000..6671390fc909 --- /dev/null +++ b/docs/cip/CIP-1_Allow_Filtering_for_Collections.md @@ -0,0 +1,54 @@ +# CIP-1 Allow Filtering for Collections + +## Status + +Current Status: Under Discussion + +## Motivation + +Currently operations on getting collections does not yet support filtering based on its +metadata, as a result, users have to perform filtering after getting the collection. +This is inconvenient to the users as they have to perform the filtering in the +application and inefficient as extra bandwidth are consumed when transferring data +between client and server. + +We should allow for getting a collection based on a filtering of its metadata. For +example, users could handle cases like wanting to get all collections belonging to a +specific id or a specific collection metadata field value. + +## Public Interfaces + +The public facing change is on the `list_collection` API. Specifically, we would like to +change the following API to add an optional `where` parameter in the API class. + +```python +def list_collections(self) -> Sequence[Collection]: # original +def list_collections(self, where: Optional[Where] = {}) # after the change +``` + +## Proposed Changes + +The proposed changes are mentioned in the public interfaces. + +## Compatibility, Deprecation, and Migration Plan + +This change is backward compatible. + +## Test Plan + +We plan to modify unit tests to accommodate the change and use system tests to verify +this API change is backward compatible. + +## Rejected Alternatives + +- An alternative solution would be adding new APIs similar to + +```python +def get_collection( +self, +name: str, +embedding_function: Optional[EmbeddingFunction] = ef.DefaultEmbeddingFunction(), +) -> Collection: +``` + +We decided to not go with it to reduce the user's burden to learn new APIs. From 750f2edbfa9a1529b821c4ba7e826a9a60b93603 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 5 Sep 2023 20:14:21 +0300 Subject: [PATCH 18/67] [BUG]: Local Segment Dir Removal (#1080) Note: Cherry-picked from original PR #1010 Refs: 1009 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Fixed an issue where the collection's persistent segment dir was not removed; thus, many dirs with segment data were left on the device. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes N/A --- chromadb/segment/__init__.py | 5 + chromadb/segment/impl/manager/local.py | 3 + chromadb/segment/impl/metadata/sqlite.py | 4 + chromadb/segment/impl/vector/local_hnsw.py | 4 + .../impl/vector/local_persistent_hnsw.py | 14 +- chromadb/test/segment/test_vector.py | 150 ++++++++++++++++++ 6 files changed, 178 insertions(+), 2 deletions(-) diff --git a/chromadb/segment/__init__.py b/chromadb/segment/__init__.py index 5c2f4316f06c..e92bccee6faf 100644 --- a/chromadb/segment/__init__.py +++ b/chromadb/segment/__init__.py @@ -39,6 +39,11 @@ def propagate_collection_metadata(metadata: Metadata) -> Optional[Metadata]: segment. Validation errors will be reported to the user.""" return None + @abstractmethod + def delete(self) -> None: + """Delete the segment and all its data""" + ... + S = TypeVar("S", bound=SegmentImplementation) diff --git a/chromadb/segment/impl/manager/local.py b/chromadb/segment/impl/manager/local.py index 1bf67aaf0491..e13452d41137 100644 --- a/chromadb/segment/impl/manager/local.py +++ b/chromadb/segment/impl/manager/local.py @@ -112,6 +112,9 @@ def delete_segments(self, collection_id: UUID) -> Sequence[UUID]: segments = self._sysdb.get_segments(collection=collection_id) for segment in segments: if segment["id"] in self._instances: + if segment["type"] == SegmentType.HNSW_LOCAL_PERSISTED.value: + instance = self.get_segment(collection_id, VectorReader) + instance.delete() del self._instances[segment["id"]] if collection_id in self._segment_cache: if segment["scope"] in self._segment_cache[collection_id]: diff --git a/chromadb/segment/impl/metadata/sqlite.py b/chromadb/segment/impl/metadata/sqlite.py index f4692fa73fa9..7f33866ffedb 100644 --- a/chromadb/segment/impl/metadata/sqlite.py +++ b/chromadb/segment/impl/metadata/sqlite.py @@ -466,6 +466,10 @@ def _where_doc_criterion( raise ValueError(f"Unknown where_doc operator {k}") raise ValueError("Empty where_doc") + @override + def delete(self) -> None: + raise NotImplementedError() + def _encode_seq_id(seq_id: SeqId) -> bytes: """Encode a SeqID into a byte array""" diff --git a/chromadb/segment/impl/vector/local_hnsw.py b/chromadb/segment/impl/vector/local_hnsw.py index 2b628bbcad9d..c45af628d2f8 100644 --- a/chromadb/segment/impl/vector/local_hnsw.py +++ b/chromadb/segment/impl/vector/local_hnsw.py @@ -305,3 +305,7 @@ def _write_records(self, records: Sequence[EmbeddingRecord]) -> None: batch.apply(record, label is not None) self._apply_batch(batch) + + @override + def delete(self) -> None: + raise NotImplementedError() diff --git a/chromadb/segment/impl/vector/local_persistent_hnsw.py b/chromadb/segment/impl/vector/local_persistent_hnsw.py index 0165e8358c2f..a0b52acd07a0 100644 --- a/chromadb/segment/impl/vector/local_persistent_hnsw.py +++ b/chromadb/segment/impl/vector/local_persistent_hnsw.py @@ -79,6 +79,7 @@ class PersistentLocalHnswSegment(LocalHnswSegment): _sync_threshold: int _persist_data: PersistentData _persist_directory: str + _allow_reset: bool def __init__(self, system: System, segment: Segment): super().__init__(system, segment) @@ -86,7 +87,7 @@ def __init__(self, system: System, segment: Segment): self._params = PersistentHnswParams(segment["metadata"] or {}) self._batch_size = self._params.batch_size self._sync_threshold = self._params.sync_threshold - + self._allow_reset = system.settings.allow_reset self._persist_directory = system.settings.require("persist_directory") self._curr_batch = Batch() self._brute_force_index = None @@ -395,9 +396,18 @@ def query_vectors( @override def reset_state(self) -> None: + if self._allow_reset: + data_path = self._get_storage_folder() + if os.path.exists(data_path): + self.close_persistent_index() + shutil.rmtree(data_path, ignore_errors=True) + + @override + def delete(self) -> None: data_path = self._get_storage_folder() if os.path.exists(data_path): - shutil.rmtree(data_path, ignore_errors=True) + self.close_persistent_index() + shutil.rmtree(data_path, ignore_errors=False) @staticmethod def get_file_handle_count() -> int: diff --git a/chromadb/test/segment/test_vector.py b/chromadb/test/segment/test_vector.py index cf55985d0f49..3a9145827e9f 100644 --- a/chromadb/test/segment/test_vector.py +++ b/chromadb/test/segment/test_vector.py @@ -516,3 +516,153 @@ def test_delete_without_add( producer.submit_embedding(topic, delete_record) except BaseException: pytest.fail("Unexpected error. Deleting on an empty segment should not raise.") + + +def test_delete_with_local_segment_storage( + system: System, + sample_embeddings: Iterator[SubmitEmbeddingRecord], + vector_reader: Type[VectorReader], + produce_fns: ProducerFn, +) -> None: + producer = system.instance(Producer) + system.reset_state() + segment_definition = create_random_segment_definition() + topic = str(segment_definition["topic"]) + + segment = vector_reader(system, segment_definition) + segment.start() + + embeddings, seq_ids = produce_fns( + producer=producer, topic=topic, embeddings=sample_embeddings, n=5 + ) + + sync(segment, seq_ids[-1]) + assert segment.count() == 5 + + delete_record = SubmitEmbeddingRecord( + id=embeddings[0]["id"], + embedding=None, + encoding=None, + metadata=None, + operation=Operation.DELETE, + ) + assert isinstance(seq_ids, List) + seq_ids.append( + produce_fns( + producer=producer, + topic=topic, + n=1, + embeddings=(delete_record for _ in range(1)), + )[1][0] + ) + + sync(segment, seq_ids[-1]) + + # Assert that the record is gone using `count` + assert segment.count() == 4 + + # Assert that the record is gone using `get` + assert segment.get_vectors(ids=[embeddings[0]["id"]]) == [] + results = segment.get_vectors() + assert len(results) == 4 + # get_vectors returns results in arbitrary order + results = sorted(results, key=lambda v: v["id"]) + for actual, expected in zip(results, embeddings[1:]): + assert actual["id"] == expected["id"] + assert approx_equal_vector( + actual["embedding"], cast(Vector, expected["embedding"]) + ) + + # Assert that the record is gone from KNN search + vector = cast(Vector, embeddings[0]["embedding"]) + query = VectorQuery( + vectors=[vector], k=10, allowed_ids=None, options=None, include_embeddings=False + ) + knn_results = segment.query_vectors(query) + assert len(results) == 4 + assert set(r["id"] for r in knn_results[0]) == set(e["id"] for e in embeddings[1:]) + + # Delete is idempotent + if isinstance(segment, PersistentLocalHnswSegment): + assert os.path.exists(segment._get_storage_folder()) + segment.delete() + assert not os.path.exists(segment._get_storage_folder()) + segment.delete() # should not raise + elif isinstance(segment, LocalHnswSegment): + with pytest.raises(NotImplementedError): + segment.delete() + + +def test_reset_state_ignored_for_allow_reset_false( + system: System, + sample_embeddings: Iterator[SubmitEmbeddingRecord], + vector_reader: Type[VectorReader], + produce_fns: ProducerFn, +) -> None: + producer = system.instance(Producer) + system.reset_state() + segment_definition = create_random_segment_definition() + topic = str(segment_definition["topic"]) + + segment = vector_reader(system, segment_definition) + segment.start() + + embeddings, seq_ids = produce_fns( + producer=producer, topic=topic, embeddings=sample_embeddings, n=5 + ) + + sync(segment, seq_ids[-1]) + assert segment.count() == 5 + + delete_record = SubmitEmbeddingRecord( + id=embeddings[0]["id"], + embedding=None, + encoding=None, + metadata=None, + operation=Operation.DELETE, + ) + assert isinstance(seq_ids, List) + seq_ids.append( + produce_fns( + producer=producer, + topic=topic, + n=1, + embeddings=(delete_record for _ in range(1)), + )[1][0] + ) + + sync(segment, seq_ids[-1]) + + # Assert that the record is gone using `count` + assert segment.count() == 4 + + # Assert that the record is gone using `get` + assert segment.get_vectors(ids=[embeddings[0]["id"]]) == [] + results = segment.get_vectors() + assert len(results) == 4 + # get_vectors returns results in arbitrary order + results = sorted(results, key=lambda v: v["id"]) + for actual, expected in zip(results, embeddings[1:]): + assert actual["id"] == expected["id"] + assert approx_equal_vector( + actual["embedding"], cast(Vector, expected["embedding"]) + ) + + # Assert that the record is gone from KNN search + vector = cast(Vector, embeddings[0]["embedding"]) + query = VectorQuery( + vectors=[vector], k=10, allowed_ids=None, options=None, include_embeddings=False + ) + knn_results = segment.query_vectors(query) + assert len(results) == 4 + assert set(r["id"] for r in knn_results[0]) == set(e["id"] for e in embeddings[1:]) + + if isinstance(segment, PersistentLocalHnswSegment): + if segment._allow_reset: + assert os.path.exists(segment._get_storage_folder()) + segment.reset_state() + assert not os.path.exists(segment._get_storage_folder()) + else: + assert os.path.exists(segment._get_storage_folder()) + segment.reset_state() + assert os.path.exists(segment._get_storage_folder()) From 6dd2d4af0bd34bdcce70c55f6ec52fee59492f3a Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 5 Sep 2023 20:42:01 +0300 Subject: [PATCH 19/67] [ENH]: CIP-4: In and Not In Metadata Filters (#1081) Cherry-picked from #1029 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Added support for `$in` and `$nin` metadata filters > Note: See CIP in `docs/` or example notebook for more info ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes TBD --------- Co-authored-by: Hammad Bashir --- chromadb/api/types.py | 34 +++- chromadb/segment/impl/metadata/sqlite.py | 78 +++++++-- chromadb/test/property/strategies.py | 31 +++- chromadb/test/property/test_filtering.py | 13 +- chromadb/types.py | 6 +- clients/js/test/client.test.ts | 2 +- docs/CIP_4_In_Nin_Metadata_Filters.md | 61 +++++++ .../in_not_in_filtering.ipynb | 149 ++++++++++++++++++ 8 files changed, 354 insertions(+), 20 deletions(-) create mode 100644 docs/CIP_4_In_Nin_Metadata_Filters.md create mode 100644 examples/basic_functionality/in_not_in_filtering.ipynb diff --git a/chromadb/api/types.py b/chromadb/api/types.py index 4b8e8863863d..7979dba624e8 100644 --- a/chromadb/api/types.py +++ b/chromadb/api/types.py @@ -207,6 +207,8 @@ def validate_where(where: Where) -> Where: if ( key != "$and" and key != "$or" + and key != "$in" + and key != "$nin" and not isinstance(value, (str, int, float, dict)) ): raise ValueError( @@ -238,15 +240,37 @@ def validate_where(where: Where) -> Where: raise ValueError( f"Expected operand value to be an int or a float for operator {operator}, got {operand}" ) - - if operator not in ["$gt", "$gte", "$lt", "$lte", "$ne", "$eq"]: + if operator in ["$in", "$nin"]: + if not isinstance(operand, list): + raise ValueError( + f"Expected operand value to be an list for operator {operator}, got {operand}" + ) + if operator not in [ + "$gt", + "$gte", + "$lt", + "$lte", + "$ne", + "$eq", + "$in", + "$nin", + ]: raise ValueError( - f"Expected where operator to be one of $gt, $gte, $lt, $lte, $ne, $eq, got {operator}" + f"Expected where operator to be one of $gt, $gte, $lt, $lte, $ne, $eq, $in, $nin, " + f"got {operator}" ) - if not isinstance(operand, (str, int, float)): + if not isinstance(operand, (str, int, float, list)): + raise ValueError( + f"Expected where operand value to be a str, int, float, or list of those type, got {operand}" + ) + if isinstance(operand, list) and ( + len(operand) == 0 + or not all(isinstance(x, type(operand[0])) for x in operand) + ): raise ValueError( - f"Expected where operand value to be a str, int, or float, got {operand}" + f"Expected where operand value to be a non-empty list, and all values to obe of the same type " + f"got {operand}" ) return where diff --git a/chromadb/segment/impl/metadata/sqlite.py b/chromadb/segment/impl/metadata/sqlite.py index 7f33866ffedb..781aed00ba4a 100644 --- a/chromadb/segment/impl/metadata/sqlite.py +++ b/chromadb/segment/impl/metadata/sqlite.py @@ -1,8 +1,8 @@ -from typing import Optional, Sequence, Any, Tuple, cast, Generator, Union, Dict +from typing import Optional, Sequence, Any, Tuple, cast, Generator, Union, Dict, List from chromadb.segment import MetadataReader from chromadb.ingest import Consumer from chromadb.config import System -from chromadb.types import Segment +from chromadb.types import Segment, InclusionExclusionOperator from chromadb.db.impl.sqlite import SqliteDB from overrides import override from chromadb.db.base import ( @@ -146,7 +146,6 @@ def get_metadata( limit = limit or 2**63 - 1 offset = offset or 0 - with self._db.tx() as cur: return list(islice(self._records(cur, q), offset, offset + limit)) @@ -405,7 +404,6 @@ def _where_map_criterion( self, q: QueryBuilder, where: Where, embeddings_t: Table, metadata_t: Table ) -> Criterion: clause: list[Criterion] = [] - for k, v in where.items(): if k == "$and": criteria = [ @@ -419,8 +417,32 @@ def _where_map_criterion( for w in cast(Sequence[Where], v) ] clause.append(reduce(lambda x, y: x | y, criteria)) + elif k == "$in": + expr = cast( + Dict[InclusionExclusionOperator, List[LiteralValue]], {k: v} + ) + sq = ( + self._db.querybuilder() + .from_(metadata_t) + .select(metadata_t.id) + .where(metadata_t.key.isin(ParameterValue(k))) + .where(_where_clause(expr, metadata_t)) + ) + clause.append(embeddings_t.id.isin(sq)) + elif k == "$nin": + expr = cast( + Dict[InclusionExclusionOperator, List[LiteralValue]], {k: v} + ) + sq = ( + self._db.querybuilder() + .from_(metadata_t) + .select(metadata_t.id) + .where(metadata_t.key.notin(ParameterValue(k))) + .where(_where_clause(expr, metadata_t)) + ) + clause.append(embeddings_t.id.notin(sq)) else: - expr = cast(Union[LiteralValue, Dict[WhereOperator, LiteralValue]], v) + expr = cast(Union[LiteralValue, Dict[WhereOperator, LiteralValue]], v) # type: ignore sq = ( self._db.querybuilder() .from_(metadata_t) @@ -492,24 +514,31 @@ def _decode_seq_id(seq_id_bytes: bytes) -> SeqId: def _where_clause( - expr: Union[LiteralValue, Dict[WhereOperator, LiteralValue]], + expr: Union[ + LiteralValue, + Dict[WhereOperator, LiteralValue], + Dict[InclusionExclusionOperator, List[LiteralValue]], + ], table: Table, ) -> Criterion: """Given a field name, an expression, and a table, construct a Pypika Criterion""" # Literal value case if isinstance(expr, (str, int, float, bool)): - return _where_clause({"$eq": expr}, table) + return _where_clause({cast(WhereOperator, "$eq"): expr}, table) # Operator dict case operator, value = next(iter(expr.items())) return _value_criterion(value, operator, table) -def _value_criterion(value: LiteralValue, op: WhereOperator, table: Table) -> Criterion: +def _value_criterion( + value: Union[LiteralValue, List[LiteralValue]], + op: Union[WhereOperator, InclusionExclusionOperator], + table: Table, +) -> Criterion: """Return a criterion to compare a value with the appropriate columns given its type and the operation type.""" - if isinstance(value, str): cols = [table.string_value] # isinstance(True, int) evaluates to True, so we need to check for bools separately @@ -519,6 +548,37 @@ def _value_criterion(value: LiteralValue, op: WhereOperator, table: Table) -> Cr cols = [table.int_value] elif isinstance(value, float) and op in ("$eq", "$ne"): cols = [table.float_value] + elif isinstance(value, list) and op in ("$in", "$nin"): + _v = value + if len(_v) == 0: + raise ValueError(f"Empty list for {op} operator") + if isinstance(value[0], str): + col_exprs = [ + table.string_value.isin(_v) + if op == "$in" + else table.str_value.notin(_v) + ] + elif isinstance(value[0], bool): + col_exprs = [ + table.bool_value.isin(_v) if op == "$in" else table.bool_value.notin(_v) + ] + elif isinstance(value[0], int): + col_exprs = [ + table.int_value.isin(_v) if op == "$in" else table.int_value.notin(_v) + ] + elif isinstance(value[0], float): + col_exprs = [ + table.float_value.isin(_v) + if op == "$in" + else table.float_value.notin(_v) + ] + elif isinstance(value, list) and op in ("$in", "$nin"): + col_exprs = [ + table.int_value.isin(value), + table.float_value.isin(value) + if op == "$in" + else table.float_value.notin(value), + ] else: cols = [table.int_value, table.float_value] diff --git a/chromadb/test/property/strategies.py b/chromadb/test/property/strategies.py index 6f855d99e966..e8540ef37aa9 100644 --- a/chromadb/test/property/strategies.py +++ b/chromadb/test/property/strategies.py @@ -14,6 +14,7 @@ from dataclasses import dataclass from chromadb.api.types import Documents, Embeddings, Metadata +from chromadb.types import LiteralValue # Set the random seed for reproducibility np.random.seed(0) # unnecessary, hypothesis does this for us @@ -448,6 +449,26 @@ def is_valid(self, rule) -> bool: # type: ignore return True +def opposite_value(value: LiteralValue) -> SearchStrategy[Any]: + """ + Returns a strategy that will generate all valid values except the input value - testing of $nin + """ + if isinstance(value, float): + return st.floats(allow_nan=False, allow_infinity=False).filter( + lambda x: x != value + ) + elif isinstance(value, str): + return safe_text.filter(lambda x: x != value) + elif isinstance(value, bool): + return st.booleans().filter(lambda x: x != value) + elif isinstance(value, int): + return st.integers(min_value=-(2**31), max_value=2**31 - 1).filter( + lambda x: x != value + ) + else: + return st.from_type(type(value)).filter(lambda x: x != value) + + @st.composite def where_clause(draw: st.DrawFn, collection: Collection) -> types.Where: """Generate a filter that could be used in a query against the given collection""" @@ -457,7 +478,7 @@ def where_clause(draw: st.DrawFn, collection: Collection) -> types.Where: key = draw(st.sampled_from(known_keys)) value = collection.known_metadata_keys[key] - legal_ops: List[Optional[str]] = [None, "$eq", "$ne"] + legal_ops: List[Optional[str]] = [None, "$eq", "$ne", "$in", "$nin"] if not isinstance(value, str) and not isinstance(value, bool): legal_ops.extend(["$gt", "$lt", "$lte", "$gte"]) if isinstance(value, float): @@ -468,6 +489,14 @@ def where_clause(draw: st.DrawFn, collection: Collection) -> types.Where: if op is None: return {key: value} + elif op == "$in": + if isinstance(value, str) and not value: + return {} + return {key: {op: [value, *[draw(opposite_value(value)) for _ in range(3)]]}} + elif op == "$nin": + if isinstance(value, str) and not value: + return {} + return {key: {op: [draw(opposite_value(value)) for _ in range(3)]}} else: return {key: {op: value}} diff --git a/chromadb/test/property/test_filtering.py b/chromadb/test/property/test_filtering.py index d9ca874bf452..ddcdefb0ed3f 100644 --- a/chromadb/test/property/test_filtering.py +++ b/chromadb/test/property/test_filtering.py @@ -42,11 +42,16 @@ def _filter_where_clause(clause: Where, metadata: Metadata) -> bool: if key == "$or": assert isinstance(expr, list) return any(_filter_where_clause(clause, metadata) for clause in expr) + if key == "$in": + assert isinstance(expr, list) + return metadata[key] in expr if key in metadata else False + if key == "$nin": + assert isinstance(expr, list) + return metadata[key] not in expr # expr is an operator expression assert isinstance(expr, dict) op, val = list(expr.items())[0] - assert isinstance(metadata, dict) if key not in metadata: return False @@ -55,6 +60,10 @@ def _filter_where_clause(clause: Where, metadata: Metadata) -> bool: return key in metadata and metadata_key == val elif op == "$ne": return key in metadata and metadata_key != val + elif op == "$in": + return key in metadata and metadata_key in val + elif op == "$nin": + return key in metadata and metadata_key not in val # The following conditions only make sense for numeric values assert isinstance(metadata_key, int) or isinstance(metadata_key, float) @@ -132,7 +141,6 @@ def _filter_embedding_set( ) if not _filter_where_doc_clause(filter["where_document"], documents[i]): ids.discard(normalized_record_set["ids"][i]) - return list(ids) @@ -174,7 +182,6 @@ def test_filterable_metadata_get( return coll.add(**record_set) - for filter in filters: result_ids = coll.get(**filter)["ids"] expected_ids = _filter_embedding_set(record_set, filter) diff --git a/chromadb/types.py b/chromadb/types.py index fd5c3709045a..713cab7757c1 100644 --- a/chromadb/types.py +++ b/chromadb/types.py @@ -122,7 +122,11 @@ class VectorQueryResult(TypedDict): Literal["$ne"], Literal["$eq"], ] -OperatorExpression = Dict[Union[WhereOperator, LogicalOperator], LiteralValue] +InclusionExclusionOperator = Union[Literal["$in"], Literal["$nin"]] +OperatorExpression = Union[ + Dict[Union[WhereOperator, LogicalOperator], LiteralValue], + Dict[InclusionExclusionOperator, List[LiteralValue]], +] Where = Dict[ Union[str, LogicalOperator], Union[LiteralValue, OperatorExpression, List["Where"]] diff --git a/clients/js/test/client.test.ts b/clients/js/test/client.test.ts index 5fbf2b09b8eb..512237a24570 100644 --- a/clients/js/test/client.test.ts +++ b/clients/js/test/client.test.ts @@ -191,5 +191,5 @@ test('wrong code returns an error', async () => { // @ts-ignore - supposed to fail const results = await collection.get({ where: { "test": { "$contains": "hello" } } }); expect(results.error).toBeDefined() - expect(results.error).toBe("ValueError('Expected where operator to be one of $gt, $gte, $lt, $lte, $ne, $eq, got $contains')") + expect(results.error).toContain("ValueError('Expected where operator") }) diff --git a/docs/CIP_4_In_Nin_Metadata_Filters.md b/docs/CIP_4_In_Nin_Metadata_Filters.md new file mode 100644 index 000000000000..e9a0911e69e6 --- /dev/null +++ b/docs/CIP_4_In_Nin_Metadata_Filters.md @@ -0,0 +1,61 @@ +# CIP-4: In and Not In Metadata Filters Proposal + +## Status + +Current Status: `Under Discussion` + +## **Motivation** + +Currently, Chroma does not provide a way to filter metadata through `in` and `not in`. This appears to be a frequent ask +from community members. + +## **Public Interfaces** + +The changes will affect the following public interfaces: + +- `Where` and `OperatorExpression` + classes - https://github.com/chroma-core/chroma/blob/48700dd07f14bcfd8b206dc3b2e2795d5531094d/chromadb/types.py#L125-L129 +- `collection.get()` +- `collection.query()` + +## **Proposed Changes** + +We suggest the introduction of two new operators `$in` and `$nin` that will be used to filter metadata. We call these +operators `InclusionExclusionOperator`. + +We suggest the following new operator definition: + +```python +InclusionExclusionOperator = Union[Literal["$in"], Literal["$nin"]] +``` + +Additionally, we suggest that those operators are added to `OperatorExpression` for seamless integration with +existing `Where` semantics: + +```python +OperatorExpression = Union[ + Dict[Union[WhereOperator, LogicalOperator], LiteralValue], + Dict[InclusionExclusionOperator, List[LiteralValue]], +] +``` + +An example of a query using the new operators would be: + +```python +collection.query(query_texts=query, + where={"$and": [{"author": {'$in': ['john', 'jill']}}, {"article_type": {"$eq": "blog"}}]}, + n_results=3) +``` + +## **Compatibility, Deprecation, and Migration Plan** + +The change is compatible with existing release 0.4.x. + +## **Test Plan** + +Property tests will be updated to ensure boundary conditions are covered as well as interoperability with existing `Where` +operators. + +## **Rejected Alternatives** + +N/A diff --git a/examples/basic_functionality/in_not_in_filtering.ipynb b/examples/basic_functionality/in_not_in_filtering.ipynb new file mode 100644 index 000000000000..3076d4a3585c --- /dev/null +++ b/examples/basic_functionality/in_not_in_filtering.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "initial_id", + "metadata": { + "collapsed": true, + "ExecuteTime": { + "end_time": "2023-08-30T12:48:38.227653Z", + "start_time": "2023-08-30T12:48:27.744069Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Number of requested results 10 is greater than number of elements in index 3, updating n_results = 3\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'ids': [['1', '3']], 'distances': [[0.28824201226234436, 1.017508625984192]], 'metadatas': [[{'author': 'john'}, {'author': 'jill'}]], 'embeddings': None, 'documents': [['Article by john', 'Article by Jill']]}\n", + "{'ids': ['1', '3'], 'embeddings': None, 'metadatas': [{'author': 'john'}, {'author': 'jill'}], 'documents': ['Article by john', 'Article by Jill']}\n" + ] + } + ], + "source": [ + "import chromadb\n", + "\n", + "from chromadb.utils import embedding_functions\n", + "\n", + "sentence_transformer_ef = embedding_functions.SentenceTransformerEmbeddingFunction(model_name=\"all-MiniLM-L6-v2\")\n", + "\n", + "\n", + "client = chromadb.Client()\n", + "# client.heartbeat()\n", + "# client.reset()\n", + "collection = client.get_or_create_collection(\"test-where-list\", embedding_function=sentence_transformer_ef)\n", + "collection.add(documents=[\"Article by john\", \"Article by Jack\", \"Article by Jill\"],\n", + " metadatas=[{\"author\": \"john\"}, {\"author\": \"jack\"}, {\"author\": \"jill\"}], ids=[\"1\", \"2\", \"3\"])\n", + "\n", + "query = [\"Give me articles by john\"]\n", + "res = collection.query(query_texts=query,where={'author': {'$in': ['john', 'jill']}}, n_results=10)\n", + "print(res)\n", + "\n", + "res_get = collection.get(where={'author': {'$in': ['john', 'jill']}})\n", + "print(res_get)\n" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Interactions with existing Where operators" + ], + "metadata": { + "collapsed": false + }, + "id": "752cef843ba2f900" + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "data": { + "text/plain": "{'ids': [['1']],\n 'distances': [[0.28824201226234436]],\n 'metadatas': [[{'article_type': 'blog', 'author': 'john'}]],\n 'embeddings': None,\n 'documents': [['Article by john']]}" + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "collection.upsert(documents=[\"Article by john\", \"Article by Jack\", \"Article by Jill\"],\n", + " metadatas=[{\"author\": \"john\",\"article_type\":\"blog\"}, {\"author\": \"jack\",\"article_type\":\"social\"}, {\"author\": \"jill\",\"article_type\":\"paper\"}], ids=[\"1\", \"2\", \"3\"])\n", + "\n", + "collection.query(query_texts=query,where={\"$and\":[{\"author\": {'$in': ['john', 'jill']}},{\"article_type\":{\"$eq\":\"blog\"}}]}, n_results=3)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-30T12:48:49.974353Z", + "start_time": "2023-08-30T12:48:49.938985Z" + } + }, + "id": "ca56cda318f9e94d" + }, + { + "cell_type": "code", + "execution_count": 3, + "outputs": [ + { + "data": { + "text/plain": "{'ids': [['1', '3']],\n 'distances': [[0.28824201226234436, 1.017508625984192]],\n 'metadatas': [[{'article_type': 'blog', 'author': 'john'},\n {'article_type': 'paper', 'author': 'jill'}]],\n 'embeddings': None,\n 'documents': [['Article by john', 'Article by Jill']]}" + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "collection.query(query_texts=query,where={\"$or\":[{\"author\": {'$in': ['john']}},{\"article_type\":{\"$in\":[\"paper\"]}}]}, n_results=3)" + ], + "metadata": { + "collapsed": false, + "ExecuteTime": { + "end_time": "2023-08-30T12:48:53.501431Z", + "start_time": "2023-08-30T12:48:53.481571Z" + } + }, + "id": "f10e79ec90c797c1" + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [], + "metadata": { + "collapsed": false + }, + "id": "d97b8b6dd96261d0" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 240401be2718164ccd9ca6277f541e1a232cf2ce Mon Sep 17 00:00:00 2001 From: Benjamin BERNARD Date: Tue, 5 Sep 2023 19:43:04 +0200 Subject: [PATCH 20/67] [BUG] docker-compose default persistent directory path updated (#1046) Fixes #1035 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Default persistent directory isn't `/index_data` it's located at `/chroma/chroma/` [config.py#L85](https://github.com/chroma-core/chroma/blob/main/chromadb/config.py#L85) fixing it in serveur core docker-compose - Removing misleading volume `/index_data` in main docker-compose.yaml file, that clearly induce in error users - Adding comment in main docker-compose.yaml file so that Chroma users won't waste time seeking for this directory path - New functionality Does it introduce a breaking change ? - With this change data will now be persistent for users using `docker-compose.server.example.yml` as it's a exemple file this shouldn't be considered as a breaking change. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python, `yarn test` for js - [x] Start Chroma server with `docker-compose up -d` create a collection, stop Chroma server with `docker-compose down` and start it again `docker-compose up -d`, check the created collection still exists. ## Documentation Changes *Are all docstrings for user-facing APIs updated if required? Do we need to make documentation changes in the [docs repository](https://github.com/chroma-core/docs)?* --- .gitignore | 4 ++++ docker-compose.server.example.yml | 8 +++++--- docker-compose.yml | 7 +++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index de36093c7f62..fd4f8aa8a977 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ index_data +# Default configuration for persist_directory in chromadb/config.py +# Currently it's located in "./chroma/" +chroma/ + venv .env .chroma diff --git a/docker-compose.server.example.yml b/docker-compose.server.example.yml index 5c575fe0eadd..2c8228fad06f 100644 --- a/docker-compose.server.example.yml +++ b/docker-compose.server.example.yml @@ -7,14 +7,16 @@ services: server: image: ghcr.io/chroma-core/chroma:latest volumes: - - index_data:/chroma/.chroma/index + # Default configuration for persist_directory in chromadb/config.py + # Currently it's located in "/chroma/chroma/" + - chroma_persistent_folder:/chroma/chroma/ ports: - 8000:8000 networks: - net volumes: - index_data: - driver: local backups: driver: local + chroma_persistent_folder: + driver: local diff --git a/docker-compose.yml b/docker-compose.yml index 1d78773052bc..9316be5d554a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,8 @@ services: dockerfile: Dockerfile volumes: - ./:/chroma - - index_data:/index_data + # Be aware that indexed data are located in "/chroma/chroma/" + # Default configuration for persist_directory in chromadb/config.py command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml environment: - IS_PERSISTENT=TRUE @@ -26,7 +27,5 @@ services: - net volumes: - index_data: - driver: local backups: - driver: local + driver: local \ No newline at end of file From 2ae7b70b9a71f7f962d68cbffa865dc7be8958ba Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 5 Sep 2023 22:24:28 +0300 Subject: [PATCH 21/67] [ENH]: AWS Deployment (#1086) Refactored and Rebased version of #1059 ## Description of changes This template includes the following: - Create a security group with required ports open (22 and 8000) - Create EC2 instance with Ubuntu 22 and deploy Chroma using docker compose - Create a data volume (ESB) for Chroma data - Mount the data volume to the EC2 instance - Format the data volume with ext4 - Start Chroma - Enable (by default) Token Auth with randomly generated token ## Test plan *How are these changes tested?* - Terraform tests performed ## Documentation Changes The template contains README with a tutorial on how to use it. --- examples/deployments/aws-terraform/README.md | 155 +++++++++++++++++ examples/deployments/aws-terraform/chroma.tf | 161 ++++++++++++++++++ examples/deployments/aws-terraform/startup.sh | 53 ++++++ .../deployments/aws-terraform/variables.tf | 94 ++++++++++ 4 files changed, 463 insertions(+) create mode 100644 examples/deployments/aws-terraform/README.md create mode 100644 examples/deployments/aws-terraform/chroma.tf create mode 100644 examples/deployments/aws-terraform/startup.sh create mode 100644 examples/deployments/aws-terraform/variables.tf diff --git a/examples/deployments/aws-terraform/README.md b/examples/deployments/aws-terraform/README.md new file mode 100644 index 000000000000..dc47929821a1 --- /dev/null +++ b/examples/deployments/aws-terraform/README.md @@ -0,0 +1,155 @@ +# AWS EC2 Basic Deployment + +This is an example deployment to AWS EC2 Compute using [terraform](https://www.terraform.io/). + +This deployment will do the following: + +- Create a security group with required ports open (22 and 8000) +- Create EC2 instance with Ubuntu 22 and deploy Chroma using docker compose +- Create a data volume for Chroma data +- Mount the data volume to the EC2 instance +- Format the data volume with ext4 +- Start Chroma + +## Requirements + +- [Terraform CLI v1.3.4+](https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/install-cli) + +## Deployment with terraform + +This deployment uses Ubuntu 22 as foundation, but you'd like to use a different AMI (non-Debian based image) you may have to adjust the startup script. + +To find AWS EC2 AMIs you can use: + +```bash +# 099720109477 is Canonical +aws ec2 describe-images \ + --owners 099720109477 \ + --filters 'Name=name,Values=ubuntu/images/*/ubuntu-jammy*' \ + --query 'sort_by(Images,&CreationDate)[-1].ImageId' +``` + +### 2. Init your terraform state +```bash +terraform init +``` + +### 3. Deploy your application + +Generate SSH key to use with your chroma instance (so you can login to the EC2): + +> Note: This is optional. You can use your own existing SSH key if you prefer. + +```bash +ssh-keygen -t RSA -b 4096 -C "Chroma AWS Key" -N "" -f ./chroma-aws && chmod 400 ./chroma-aws +``` + +Set up your Terraform variables and deploy your instance: + +```bash +export TF_VAR_AWS_ACCESS_KEY= #take note of this as it must be present in all of the subsequent steps +export TF_VAR_AWS_SECRET_ACCESS_KEY= #take note of this as it must be present in all of the subsequent steps +export TF_ssh_public_key="./chroma-aws.pub" #path to the public key you generated above (or can be different if you want to use your own key) +export TF_ssh_private_key="./chroma-aws" #path to the private key you generated above (or can be different if you want to use your own key) - used for formatting the Chroma data volume +export TF_VAR_chroma_release=0.4.8 #set the chroma release to deploy +export TF_VAR_region="us-west-1" # AWS region to deploy the chroma instance to +export TF_VAR_public_access="true" #enable public access to the chroma instance on port 8000 +export TF_VAR_enable_auth="true" #enable basic auth for the chroma instance +export TF_VAR_auth_type="token" #The auth type to use for the chroma instance (token or basic) +terraform apply -auto-approve +``` +> Note: Basic Auth is supported by Chroma v0.4.7+ + +### 4. Check your public IP and that Chroma is running + +Get the public IP of your instance + +```bash +terraform output instance_public_ip +``` + +Check that chroma is running (It should take up several minutes for the instance to be ready) + +```bash +export instance_public_ip=$(terraform output instance_public_ip | sed 's/"//g') +curl -v http://$instance_public_ip:8000/api/v1/heartbeat +``` + +#### 4.1 Checking Auth + +##### Basic +When basic auth is enabled you can check the get the credentials from Terraform state by running: + +```bash +terraform output chroma_auth_basic +``` + +You should see something of the form: + +```bash +chroma:VuA8I}QyNrm0@QLq +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_basic | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -u "${CHROMA_AUTH}" +``` + +> Note: Without `-u` you should be getting 401 Unauthorized response + + +##### Token +When token auth is enabled you can check the get the credentials from Terraform state by running: + +```bash +terraform output chroma_auth_token +``` + +You should see something of the form: + +```bash +PVcQ4qUUnmahXwUgAf3UuYZoMlos6MnF +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_token | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -H "Authorization: Bearer ${CHROMA_AUTH}" +``` + +#### 4.2 SSH to your instance + + +To SSH to your instance: + +```bash +ssh -i ./chroma-aws ubuntu@$instance_public_ip +``` + +### 5. Destroy your Chroma instance +```bash +terraform destroy -auto-approve +``` + +## Extras + +You can visualize your infrastructure with: + +```bash +terraform graph | dot -Tsvg > graph.svg +``` + +>Note: You will need graphviz installed for this to work diff --git a/examples/deployments/aws-terraform/chroma.tf b/examples/deployments/aws-terraform/chroma.tf new file mode 100644 index 000000000000..5f0db03ed51f --- /dev/null +++ b/examples/deployments/aws-terraform/chroma.tf @@ -0,0 +1,161 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +# Define provider +variable "AWS_ACCESS_KEY" {} +variable "AWS_SECRET_ACCESS_KEY" {} + +provider "aws" { + access_key = var.AWS_ACCESS_KEY + secret_key = var.AWS_SECRET_ACCESS_KEY + region = var.region +} + +# Create security group +resource "aws_security_group" "chroma_sg" { + name = "chroma-cluster-sg" + description = "Security group for the cluster nodes" + + ingress { + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + dynamic "ingress" { + for_each = var.public_access ? [1] : [] + content { + from_port = 8000 + to_port = 8000 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Name = "chroma" + } +} + +resource "aws_key_pair" "chroma-keypair" { + key_name = "chroma-keypair" # Replace with your desired key pair name + public_key = file(var.ssh_public_key) # Replace with the path to your public key file +} + +data "aws_ami" "ubuntu" { + most_recent = true + + filter { + name = "name" + values = ["ubuntu/images/hvm-ssd/ubuntu-jammy*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + filter { + name = "architecture" + values = ["x86_64"] + } + + owners = ["099720109477"] # Canonical +} +# Create EC2 instances +resource "aws_instance" "chroma_instance" { + ami = data.aws_ami.ubuntu.id + instance_type = var.instance_type + key_name = "chroma-keypair" + security_groups = [aws_security_group.chroma_sg.name] + + user_data = templatefile("${path.module}/startup.sh", { + chroma_release = var.chroma_release, + enable_auth = var.enable_auth, + auth_type = var.auth_type, + basic_auth_credentials = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}", + token_auth_credentials = random_password.chroma_token.result, + }) + + tags = { + Name = "chroma" + } + + ebs_block_device { + device_name = "/dev/sda1" + volume_size = var.chroma_instance_volume_size # size in GBs + } +} + + +resource "aws_ebs_volume" "chroma-volume" { + availability_zone = aws_instance.chroma_instance.availability_zone + size = var.chroma_data_volume_size + + tags = { + Name = "chroma" + } + + lifecycle { + prevent_destroy = var.prevent_chroma_data_volume_delete # size in GBs + } +} + +locals { + cleaned_volume_id = replace(aws_ebs_volume.chroma-volume.id, "-", "") +} + +resource "aws_volume_attachment" "chroma_volume_attachment" { + device_name = "/dev/sdh" + volume_id = aws_ebs_volume.chroma-volume.id + instance_id = aws_instance.chroma_instance.id + provisioner "remote-exec" { + inline = [ + "export VOLUME_ID=${local.cleaned_volume_id} && sudo mkfs -t ext4 /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}')", + "sudo mkdir /chroma-data", + "export VOLUME_ID=${local.cleaned_volume_id} && sudo mount /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}') /chroma-data" + ] + + connection { + host = aws_instance.chroma_instance.public_ip + type = "ssh" + user = "ubuntu" + private_key = file(var.ssh_private_key) + } + } + depends_on = [aws_instance.chroma_instance, aws_ebs_volume.chroma-volume] +} + + +output "instance_public_ip" { + value = aws_instance.chroma_instance.public_ip +} + +output "instance_private_ip" { + value = aws_instance.chroma_instance.private_ip +} + +output "chroma_auth_token" { + value = random_password.chroma_token.result + sensitive = true +} + + +output "chroma_auth_basic" { + value = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}" + sensitive = true +} diff --git a/examples/deployments/aws-terraform/startup.sh b/examples/deployments/aws-terraform/startup.sh new file mode 100644 index 000000000000..a6e5b3134f30 --- /dev/null +++ b/examples/deployments/aws-terraform/startup.sh @@ -0,0 +1,53 @@ +#! /bin/bash + +# Note: This is run as root + +cd ~ +export enable_auth="${enable_auth}" +export basic_auth_credentials="${basic_auth_credentials}" +export auth_type="${auth_type}" +export token_auth_credentials="${token_auth_credentials}" +apt-get update -y +apt-get install -y ca-certificates curl gnupg lsb-release +mkdir -m 0755 -p /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null +apt-get update -y +chmod a+r /etc/apt/keyrings/docker.gpg +apt-get update -y +apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git +usermod -aG docker ubuntu +git clone https://github.com/chroma-core/chroma.git && cd chroma +git fetch --tags +git checkout tags/${chroma_release} + +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "basic" ] && [ ! -z "$${basic_auth_credentials}" ]; then + username=$(echo $basic_auth_credentials | cut -d: -f1) + password=$(echo $basic_auth_credentials | cut -d: -f2) + docker run --rm --entrypoint htpasswd httpd:2 -Bbn $username $password > server.htpasswd + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS_FILE="/chroma/server.htpasswd" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' +EOF +fi + +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "token" ] && [ ! -z "$${token_auth_credentials}" ]; then + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS="$${token_auth_credentials}" \ +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.token.TokenConfigServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.token.TokenAuthServerProvider' +EOF +fi + +cat < docker-compose.override.yaml +version: '3.8' +services: + server: + volumes: + - /chroma-data:/chroma/chroma +EOF + +COMPOSE_PROJECT_NAME=chroma docker compose up -d --build diff --git a/examples/deployments/aws-terraform/variables.tf b/examples/deployments/aws-terraform/variables.tf new file mode 100644 index 000000000000..84e086116b5a --- /dev/null +++ b/examples/deployments/aws-terraform/variables.tf @@ -0,0 +1,94 @@ +variable "chroma_release" { + description = "The chroma release to deploy" + type = string + default = "0.4.8" +} + +variable "region" { + description = "AWS Region" + type = string + default = "us-west-1" +} + +variable "instance_type" { + description = "AWS EC2 Instance Type" + type = string + default = "t3.medium" +} + + +variable "public_access" { + description = "Enable public ingress on port 8000" + type = bool + default = true // or true depending on your needs +} + +variable "enable_auth" { + description = "Enable authentication" + type = bool + default = true // or false depending on your needs +} + +variable "auth_type" { + description = "Authentication type" + type = string + default = "token" // or token depending on your needs + validation { + condition = contains(["basic", "token"], var.auth_type) + error_message = "The auth type must be either basic or token" + } +} + +resource "random_password" "chroma_password" { + length = 16 + special = true + lower = true + upper = true +} + +resource "random_password" "chroma_token" { + length = 32 + special = false + lower = true + upper = true +} + + +locals { + basic_auth_credentials = { + username = "chroma" + password = random_password.chroma_password.result + } + token_auth_credentials = { + token = random_password.chroma_token.result + } +} + +variable "ssh_public_key" { + description = "SSH Public Key" + type = string + default = "./chroma-aws.pub" +} +variable "ssh_private_key" { + description = "SSH Private Key" + type = string + default = "./chroma-aws" +} + +variable "chroma_instance_volume_size" { + description = "The size of the instance volume - the root volume" + type = number + default = 30 +} + +variable "chroma_data_volume_size" { + description = "EBS Volume Size of the attached data volume where your chroma data is stored" + type = number + default = 20 +} + +variable "prevent_chroma_data_volume_delete" { + description = "Prevent the chroma data volume from being deleted when the instance is terminated" + type = bool + default = false +} From 69ff979c96866e7a33015460dfe7bd560636935f Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 5 Sep 2023 22:48:38 +0300 Subject: [PATCH 22/67] [ENH]: Improved HttpClient URL support (#1067) Refs: #1019 ## Description of changes The HTTP Client now supports a variety of URIs: - URLs: `http://example.com/` - Hostnames: `localhost` or chroma (existing) - Domains `example.com` - Paths in URLs/Domains - `http://example.com/my_path` ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes TBD - perhaps some clarifications on the HttpClient --- chromadb/api/fastapi.py | 36 +++++++-- chromadb/config.py | 1 + chromadb/test/property/test_client_url.py | 94 +++++++++++++++++++++++ 3 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 chromadb/test/property/test_client_url.py diff --git a/chromadb/api/fastapi.py b/chromadb/api/fastapi.py index 5551b051370d..8498f9ec110e 100644 --- a/chromadb/api/fastapi.py +++ b/chromadb/api/fastapi.py @@ -30,27 +30,47 @@ from chromadb.auth.registry import resolve_provider from chromadb.config import Settings, System from chromadb.telemetry import Telemetry +from urllib.parse import urlparse, urlunparse, quote class FastAPI(API): _settings: Settings + @staticmethod + def resolve_url( + chroma_server_host: str, + chroma_server_ssl_enabled: Optional[bool] = False, + default_api_path: Optional[str] = "", + chroma_server_http_port: int = 8000, + ) -> str: + parsed = urlparse(chroma_server_host) + + scheme = "https" if chroma_server_ssl_enabled else parsed.scheme or "http" + net_loc = parsed.netloc or parsed.hostname or chroma_server_host + port = parsed.port or chroma_server_http_port + path = parsed.path or default_api_path + + if not path or path == net_loc or not path.endswith(default_api_path or ""): + path = default_api_path if default_api_path else "" + full_url = urlunparse( + (scheme, f"{net_loc}:{port}", quote(path.replace("//", "/")), "", "", "") + ) + + return full_url + def __init__(self, system: System): super().__init__(system) - url_prefix = "https" if system.settings.chroma_server_ssl_enabled else "http" system.settings.require("chroma_server_host") system.settings.require("chroma_server_http_port") self._telemetry_client = self.require(Telemetry) self._settings = system.settings - port_suffix = ( - f":{system.settings.chroma_server_http_port}" - if system.settings.chroma_server_http_port - else "" - ) - self._api_url = ( - f"{url_prefix}://{system.settings.chroma_server_host}{port_suffix}/api/v1" + self._api_url = FastAPI.resolve_url( + chroma_server_host=str(system.settings.chroma_server_host), + chroma_server_http_port=int(str(system.settings.chroma_server_http_port)), + chroma_server_ssl_enabled=system.settings.chroma_server_ssl_enabled, + default_api_path=system.settings.chroma_server_api_default_path, ) self._header = system.settings.chroma_server_headers diff --git a/chromadb/config.py b/chromadb/config.py index bc46114eb93e..4cb7eac1aae4 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -88,6 +88,7 @@ class Settings(BaseSettings): # type: ignore chroma_server_headers: Optional[Dict[str, str]] = None chroma_server_http_port: Optional[str] = None chroma_server_ssl_enabled: Optional[bool] = False + chroma_server_api_default_path: Optional[str] = "/api/v1" chroma_server_grpc_port: Optional[str] = None chroma_server_cors_allow_origins: List[str] = [] # eg ["http://localhost:3000"] diff --git a/chromadb/test/property/test_client_url.py b/chromadb/test/property/test_client_url.py new file mode 100644 index 000000000000..992af9813995 --- /dev/null +++ b/chromadb/test/property/test_client_url.py @@ -0,0 +1,94 @@ +from typing import Optional +from urllib.parse import urlparse + +from hypothesis import given, strategies as st + +from chromadb.api.fastapi import FastAPI + + +def hostname_strategy() -> st.SearchStrategy[str]: + label = st.text( + alphabet=st.characters(min_codepoint=97, max_codepoint=122), + min_size=1, + max_size=63, + ) + return st.lists(label, min_size=1, max_size=3).map("-".join) + + +tld_list = ["com", "org", "net", "edu"] + + +def domain_strategy() -> st.SearchStrategy[str]: + label = st.text( + alphabet=st.characters(min_codepoint=97, max_codepoint=122), + min_size=1, + max_size=63, + ) + tld = st.sampled_from(tld_list) + return st.tuples(label, tld).map(".".join) + + +port_strategy = st.integers(min_value=1, max_value=65535) + +ssl_enabled_strategy = st.booleans() + + +def url_path_strategy() -> st.SearchStrategy[str]: + path_segment = st.text( + alphabet=st.sampled_from("abcdefghijklmnopqrstuvwxyz/-_"), + min_size=1, + max_size=10, + ) + return ( + st.lists(path_segment, min_size=1, max_size=5) + .map("/".join) + .map(lambda x: "/" + x) + ) + + +def is_valid_url(url: str) -> bool: + try: + parsed = urlparse(url) + return all([parsed.scheme, parsed.netloc]) + except Exception: + return False + + +def generate_valid_domain_url() -> st.SearchStrategy[str]: + return st.builds( + lambda url_scheme, hostname, url_path: f"{url_scheme}://{hostname}{url_path}", + url_scheme=st.sampled_from(["http", "https"]), + hostname=domain_strategy(), + url_path=url_path_strategy(), + ) + + +host_or_domain_strategy = st.one_of( + generate_valid_domain_url(), domain_strategy(), st.sampled_from(["localhost"]) +) + + +@given( + hostname=host_or_domain_strategy, + port=port_strategy, + ssl_enabled=ssl_enabled_strategy, + default_api_path=st.sampled_from(["/api/v1", "/api/v2", None]), +) +def test_url_resolve( + hostname: str, + port: int, + ssl_enabled: bool, + default_api_path: Optional[str], +) -> None: + _url = FastAPI.resolve_url( + chroma_server_host=hostname, + chroma_server_http_port=port, + chroma_server_ssl_enabled=ssl_enabled, + default_api_path=default_api_path, + ) + assert is_valid_url(_url), f"Invalid URL: {_url}" + assert ( + _url.startswith("https") if ssl_enabled else _url.startswith("http") + ), f"Invalid URL: {_url} - SSL Enabled: {ssl_enabled}" + if default_api_path: + assert _url.endswith(default_api_path), f"Invalid URL: {_url}" From 6c700ebdfc09dc8580d59cc20bd0254ed5744cfc Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 12:48:57 -0700 Subject: [PATCH 23/67] Add explicit env to docker compose so that it can hint at usage (#1088) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Add explicit env for persist directory defaults to docker compose so that it can hint at usage - New functionality - ... ## Test plan Existing tests - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None required --- docker-compose.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9316be5d554a..2119eba7e001 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,6 +21,7 @@ services: - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=${CHROMA_SERVER_AUTH_CREDENTIALS_FILE} - CHROMA_SERVER_AUTH_CREDENTIALS=${CHROMA_SERVER_AUTH_CREDENTIALS} - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=${CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER} + - PERSIST_DIRECTORY=${PERSIST_DIRECTORY:-/chroma/chroma} ports: - 8000:8000 networks: @@ -28,4 +29,4 @@ services: volumes: backups: - driver: local \ No newline at end of file + driver: local From 986fc38c983d8d29b8d2c16985ae0c1318cadcb8 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 15:04:31 -0700 Subject: [PATCH 24/67] [BLD] Add release automation for JS client (#1091) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Adds release automation for JS client. ## Test plan They will have to be tested after merging this PR ## Documentation Changes Update the js client Develop guide for how to use this automation. --- .github/workflows/chroma-js-release.yml | 38 +++++++++++++++++++++++++ clients/js/DEVELOP.md | 33 ++++++++++++++++++++- clients/js/package.json | 3 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/chroma-js-release.yml diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml new file mode 100644 index 000000000000..37f8885f6b7a --- /dev/null +++ b/.github/workflows/chroma-js-release.yml @@ -0,0 +1,38 @@ +name: Chroma Release JS Client + +on: + push: + tags: + - 'js_release_*.*.*' # Match tags in the form js_release_X.Y.Z + - 'js_release_alpha_*.*.*' # Match tags in the form js_release_alpha_X.Y.Z + +jobs: + build-and-release: + runs-on: ubuntu-latest + permissions: write-all + steps: + - name: Check if tag matches the pattern + run: | + if [[ "${{ github.ref }}" =~ ^refs/tags/js_release_alpha_[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Tag matches the pattern js_release_alpha_X.Y.Z" + NPM_SCRIPT="release_alpha" + elif [[ "${{ github.ref }}" =~ ^refs/tags/js_release_[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Tag matches the pattern js_release_X.Y.Z" + NPM_SCRIPT="release" + else + echo "Tag does not match the release tag pattern, exiting workflow" + exit 1 + fi + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set up JS + uses: actions/setup-node@v3 + with: + node-version: '16.x' + registry-url: 'https://registry.npmjs.org' + - name: npm Test & Publish + run: npm run $NPM_SCRIPT + env: + GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/clients/js/DEVELOP.md b/clients/js/DEVELOP.md index d106513054b6..8236cc36a92f 100644 --- a/clients/js/DEVELOP.md +++ b/clients/js/DEVELOP.md @@ -20,10 +20,41 @@ This readme is helpful for local dev. ### Pushing to npm -The goal of the design is that this will be added to our github action releases so that the JS API is always up to date and pinned against the python backend API. +#### Automatically +##### Increase the version number +1. Create a new PR for the release that upgrades the version in code. Name it `js_release/A.B.C` for production releases and `js_release_alpha/A.B.C` for alpha releases. In the package.json update the version number to the new version +2. Add the "release" label to this PR +3. Once the PR is merged, tag your commit SHA with the release version + +```bash +git tag js_release_A.B.C + +# or for alpha releases: + +git tag js_release_alpha_A.B.C +``` + +4. You need to then wait for the github action for main for `chroma js release` to complete on main. + +##### Perform the release +1. Push your tag to origin to create the release + +```bash + +git push origin js_release_A.B.C + +# or for alpha releases: + +git push origin js_release_alpha_A.B.C +``` +2. This will trigger a Github action which performs the release + +#### Manually `npm run release` pushes the `package.json` defined packaged to the package manager for authenticated users. It will build, test, and then publish the new version. + + ### Useful links https://gaganpreet.in/posts/hyperproductive-apis-fastapi/ diff --git a/clients/js/package.json b/clients/js/package.json index abce9d7b6693..f8b26a57acb1 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -45,7 +45,8 @@ "build:module": "tsc -p tsconfig.module.json", "genapi": "./genapi.sh", "prettier": "prettier --write .", - "release": "run-s build test:run && npm publish" + "release": "run-s build test:run && npm publish", + "release_alpha": "run-s build test:run && npm publish --tag alpha" }, "engines": { "node": ">=14.17.0" From 2c8a11f7b2a97cf562e6eccaa6981f4471906f37 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 15:07:45 -0700 Subject: [PATCH 25/67] [BLD] Add working directory to npm run js --- .github/workflows/chroma-js-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 37f8885f6b7a..3104100530de 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -34,5 +34,6 @@ jobs: registry-url: 'https://registry.npmjs.org' - name: npm Test & Publish run: npm run $NPM_SCRIPT + working-directory: ./clients/js/ env: GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} From 50f31656c08c77a9eccd299718e501f7fe4e25ef Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 15:09:30 -0700 Subject: [PATCH 26/67] [BLD] Add working directory to npm run js (#1092) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Add working directory to npm run js - New functionality - ... ## Test plan These must be tested after merge, this is an iteration on #1091 - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None required. From 8b41722b344f0fb9a354f6fec74a2fc00704dcc4 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 15:16:25 -0700 Subject: [PATCH 27/67] [BLD] Prevent python release from running on js tag pushes (#1093) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Prevent python release from running on js tag pushes ## Test plan Manually on main via triggering ## Documentation Changes None --- .github/workflows/chroma-release-python-client.yml | 2 +- .github/workflows/chroma-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chroma-release-python-client.yml b/.github/workflows/chroma-release-python-client.yml index eeec25856423..2cab15848f9e 100644 --- a/.github/workflows/chroma-release-python-client.yml +++ b/.github/workflows/chroma-release-python-client.yml @@ -3,7 +3,7 @@ name: Chroma Release Python Client on: push: tags: - - '*' + - '*.*.*' # Match tags in the form X.Y.Z branches: - main - hammad/thin_client diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index 7533cf4b48fc..4f59d2a7cb09 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -3,7 +3,7 @@ name: Chroma Release on: push: tags: - - '*' + - '*.*.*' # Match tags in the form X.Y.Z branches: - main From d7e3d82d6f118d234ca54190d3cb82faeb90b292 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 15:42:46 -0700 Subject: [PATCH 28/67] [BLD] Release env vars save to GITHUB_ENV (#1094) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Properly use github_env to pass env vars across steps - https://docs.github.com/en/github-ae@latest/actions/using-workflows/workflow-commands-for-github-actions#setting-an-environment-variable ## Test plan Will be manually tested, this is an iteration on #1093 - [ ] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None --- .github/workflows/chroma-js-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 3104100530de..8d9ebfff8345 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -15,10 +15,10 @@ jobs: run: | if [[ "${{ github.ref }}" =~ ^refs/tags/js_release_alpha_[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Tag matches the pattern js_release_alpha_X.Y.Z" - NPM_SCRIPT="release_alpha" + echo "NPM_SCRIPT=release_alpha" >> "$GITHUB_ENV" elif [[ "${{ github.ref }}" =~ ^refs/tags/js_release_[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "Tag matches the pattern js_release_X.Y.Z" - NPM_SCRIPT="release" + echo "NPM_SCRIPT=release" >> "$GITHUB_ENV" else echo "Tag does not match the release tag pattern, exiting workflow" exit 1 From 687375ae860f81456c659a9fc05c466e96de5b7a Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 15:51:19 -0700 Subject: [PATCH 29/67] [BLD] Add install deps to JS release workflow (#1095) Add install deps to JS release workflow --- .github/workflows/chroma-js-release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 8d9ebfff8345..ef04b3b66b18 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -32,6 +32,9 @@ jobs: with: node-version: '16.x' registry-url: 'https://registry.npmjs.org' + - name: Install Client Dev Dependencies + run: npm install + working-directory: ./clients/js/ - name: npm Test & Publish run: npm run $NPM_SCRIPT working-directory: ./clients/js/ From 347b8be4a9722847f7748801d4235a901ec18a08 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 16:02:50 -0700 Subject: [PATCH 30/67] [BLD] Add npm run db:run to run docker-compose up (#1097) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Add npm run db:run to run docker-compose up - New functionality - ... ## Test plan Will be tested manually. This is an iteration on #1095 . - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None --- .github/workflows/chroma-js-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index ef04b3b66b18..f0df6e388746 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -36,7 +36,7 @@ jobs: run: npm install working-directory: ./clients/js/ - name: npm Test & Publish - run: npm run $NPM_SCRIPT + run: npm run db:run & npm run $NPM_SCRIPT working-directory: ./clients/js/ env: GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} From 07aa62e098ef672a051c71afab11d1904684c582 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:11:33 -0700 Subject: [PATCH 31/67] [BLD] fix run typo --- .github/workflows/chroma-js-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index f0df6e388746..a6bbe0bd33fc 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -36,7 +36,7 @@ jobs: run: npm install working-directory: ./clients/js/ - name: npm Test & Publish - run: npm run db:run & npm run $NPM_SCRIPT + run: npm run db:run && npm run $NPM_SCRIPT working-directory: ./clients/js/ env: GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} From 096e52280edee85847de6015a0e7898c1f944828 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:23:40 -0700 Subject: [PATCH 32/67] [BLD] js_release workflow fix chroma port --- .github/workflows/chroma-js-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index a6bbe0bd33fc..1eeb64651cf7 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -36,7 +36,7 @@ jobs: run: npm install working-directory: ./clients/js/ - name: npm Test & Publish - run: npm run db:run && npm run $NPM_SCRIPT + run: npm run db:run && CHROMA_SERVER_HTTP_PORT=8001 npm run $NPM_SCRIPT working-directory: ./clients/js/ env: GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} From b0e75e677d0709b6350c2c9cd1252ee1439accfc Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:29:56 -0700 Subject: [PATCH 33/67] [BLD] js_release workflow fix chroma port --- .github/workflows/chroma-js-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 1eeb64651cf7..98503cac2356 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -36,7 +36,7 @@ jobs: run: npm install working-directory: ./clients/js/ - name: npm Test & Publish - run: npm run db:run && CHROMA_SERVER_HTTP_PORT=8001 npm run $NPM_SCRIPT + run: npm run db:run && PORT=8001 npm run $NPM_SCRIPT working-directory: ./clients/js/ env: GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} From 27ece76d3638268be017b17884439576c11a32a8 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:41:13 -0700 Subject: [PATCH 34/67] [BLD] JS release token fix. Python tag push fix --- .github/workflows/chroma-js-release.yml | 2 +- .github/workflows/chroma-release-python-client.yml | 2 +- .github/workflows/chroma-release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 98503cac2356..918f9d75b59c 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -39,4 +39,4 @@ jobs: run: npm run db:run && PORT=8001 npm run $NPM_SCRIPT working-directory: ./clients/js/ env: - GITHUB_TOKEN: ${{ secrets.NPM_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/chroma-release-python-client.yml b/.github/workflows/chroma-release-python-client.yml index 2cab15848f9e..735fb2f848fa 100644 --- a/.github/workflows/chroma-release-python-client.yml +++ b/.github/workflows/chroma-release-python-client.yml @@ -3,7 +3,7 @@ name: Chroma Release Python Client on: push: tags: - - '*.*.*' # Match tags in the form X.Y.Z + - '[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z branches: - main - hammad/thin_client diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index 4f59d2a7cb09..0bfe34bf1a0e 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -3,7 +3,7 @@ name: Chroma Release on: push: tags: - - '*.*.*' # Match tags in the form X.Y.Z + - '[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z branches: - main From 37298853769f007e2d5c42fb8daab2f993fe7857 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:43:38 -0700 Subject: [PATCH 35/67] [BLD] JS release token fix. --- .github/workflows/chroma-js-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/chroma-js-release.yml b/.github/workflows/chroma-js-release.yml index 918f9d75b59c..62af392c29b6 100644 --- a/.github/workflows/chroma-js-release.yml +++ b/.github/workflows/chroma-js-release.yml @@ -39,4 +39,4 @@ jobs: run: npm run db:run && PORT=8001 npm run $NPM_SCRIPT working-directory: ./clients/js/ env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 2acc057d856edf3ae45fb4d14aa83315838976cb Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 16:49:47 -0700 Subject: [PATCH 36/67] [BLD] Switch to version prefixed tags to be able to use github filter patterns on actions. v0.4.9 instead of 0.4.9 --- .github/workflows/chroma-release-python-client.yml | 2 +- .github/workflows/chroma-release.yml | 2 +- RELEASE_PROCESS.md | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/chroma-release-python-client.yml b/.github/workflows/chroma-release-python-client.yml index 735fb2f848fa..fbcd7d320b36 100644 --- a/.github/workflows/chroma-release-python-client.yml +++ b/.github/workflows/chroma-release-python-client.yml @@ -3,7 +3,7 @@ name: Chroma Release Python Client on: push: tags: - - '[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z + - 'v[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z branches: - main - hammad/thin_client diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index 0bfe34bf1a0e..65626d270aa6 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -3,7 +3,7 @@ name: Chroma Release on: push: tags: - - '[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z + - 'v[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z branches: - main diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 577345c8faed..ae67156782f1 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -3,20 +3,20 @@ This guide covers how to release chroma to PyPi #### Increase the version number -1. Create a new PR for the release that upgrades the version in code. Name it `release/A.B.C` In [this file](https://github.com/chroma-core/chroma/blob/main/chromadb/__init__.py) update the __ version __. +1. Create a new PR for the release that upgrades the version in code. Name it `release/vA.B.C` In [this file](https://github.com/chroma-core/chroma/blob/main/chromadb/__init__.py) update the __ version __. ``` __version__ = "A.B.C" ``` 2. Add the "release" label to this PR 3. Once the PR is merged, tag your commit SHA with the release version ``` -git tag A.B.C +git tag vA.B.C ``` 4. You need to then wait for the github action for main for `chroma release` and `chroma client release` to go green. Not doing this will result in a race condition. #### Perform the release 1. Push your tag to origin to create the release ``` -git push origin A.B.C +git push origin vA.B.C ``` 2. This will trigger a Github action which performs the release From c3075ef31a00962f41613ba9fba504f77f325c92 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 16:50:16 -0700 Subject: [PATCH 37/67] [RELEASE] Release JS 1.5.7 (#1090) Release JS 1.5.7 --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index f8b26a57acb1..3e49e65b6511 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "chromadb", - "version": "1.5.6", + "version": "1.5.7", "description": "A JavaScript interface for chroma", "keywords": [], "author": "", From 242d165554600eb8de1d8dff4284c2928c69d40c Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 17:21:09 -0700 Subject: [PATCH 38/67] [RELEASE] Release js 1.5.8 --- clients/js/DEVELOP.md | 3 ++- clients/js/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/js/DEVELOP.md b/clients/js/DEVELOP.md index 8236cc36a92f..030f3ef455c6 100644 --- a/clients/js/DEVELOP.md +++ b/clients/js/DEVELOP.md @@ -23,7 +23,8 @@ This readme is helpful for local dev. #### Automatically ##### Increase the version number -1. Create a new PR for the release that upgrades the version in code. Name it `js_release/A.B.C` for production releases and `js_release_alpha/A.B.C` for alpha releases. In the package.json update the version number to the new version +1. Create a new PR for the release that upgrades the version in code. Name it `js_release/A.B.C` for production releases and `js_release_alpha/A.B.C` for alpha releases. In the package.json update the version number to the new version. For production releases this is just the version number, for alpha +releases this is the version number with '-alphaX' appended to it. For example, if the current version is 1.0.0, the alpha release would be 1.0.0-alpha1 for the first alpha release, 1.0.0-alpha2 for the second alpha release, etc. 2. Add the "release" label to this PR 3. Once the PR is merged, tag your commit SHA with the release version diff --git a/clients/js/package.json b/clients/js/package.json index 3e49e65b6511..53dbaf0e9c6d 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "chromadb", - "version": "1.5.7", + "version": "1.5.8", "description": "A JavaScript interface for chroma", "keywords": [], "author": "", From 3e83d174eb9330fff047648d35266b91a8106b71 Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 19:04:06 -0700 Subject: [PATCH 39/67] [BLD] Make python release processes add a check tag step --- .../workflows/chroma-release-python-client.yml | 15 ++++++++++++++- .github/workflows/chroma-release.yml | 15 ++++++++++++++- RELEASE_PROCESS.md | 6 +++--- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/.github/workflows/chroma-release-python-client.yml b/.github/workflows/chroma-release-python-client.yml index fbcd7d320b36..f9507ca7d6b3 100644 --- a/.github/workflows/chroma-release-python-client.yml +++ b/.github/workflows/chroma-release-python-client.yml @@ -3,14 +3,27 @@ name: Chroma Release Python Client on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z + - '[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z branches: - main - hammad/thin_client jobs: + check_tag: + runs-on: ubuntu-latest + outputs: + tag_matches: ${{ steps.check-tag.outputs.match }} + steps: + - name: Check Tag + id: check-tag + run: | + if [[ ${{ github.event.ref }} =~ ^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "tag_matches=true" >> $GITHUB_OUTPUT + fi build-and-release: runs-on: ubuntu-latest + needs: check_tag + if: needs.check_tag.outputs.tag_matches == 'true' permissions: write-all steps: - name: Checkout diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index 65626d270aa6..a397cfc59b2c 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -3,7 +3,7 @@ name: Chroma Release on: push: tags: - - 'v[0-9]+.[0-9]+.[0-9]+' # Match tags in the form X.Y.Z + - '*' branches: - main @@ -13,8 +13,21 @@ env: PLATFORMS: linux/amd64,linux/arm64 #linux/riscv64, linux/arm/v7 jobs: + check_tag: + runs-on: ubuntu-latest + outputs: + tag_matches: ${{ steps.check-tag.outputs.match }} + steps: + - name: Check Tag + id: check-tag + run: | + if [[ ${{ github.event.ref }} =~ ^refs/tags/[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "tag_matches=true" >> $GITHUB_OUTPUT + fi build-and-release: runs-on: ubuntu-latest + needs: check_tag + if: needs.check_tag.outputs.tag_matches == 'true' permissions: write-all # id-token: write # contents: read diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index ae67156782f1..577345c8faed 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -3,20 +3,20 @@ This guide covers how to release chroma to PyPi #### Increase the version number -1. Create a new PR for the release that upgrades the version in code. Name it `release/vA.B.C` In [this file](https://github.com/chroma-core/chroma/blob/main/chromadb/__init__.py) update the __ version __. +1. Create a new PR for the release that upgrades the version in code. Name it `release/A.B.C` In [this file](https://github.com/chroma-core/chroma/blob/main/chromadb/__init__.py) update the __ version __. ``` __version__ = "A.B.C" ``` 2. Add the "release" label to this PR 3. Once the PR is merged, tag your commit SHA with the release version ``` -git tag vA.B.C +git tag A.B.C ``` 4. You need to then wait for the github action for main for `chroma release` and `chroma client release` to go green. Not doing this will result in a race condition. #### Perform the release 1. Push your tag to origin to create the release ``` -git push origin vA.B.C +git push origin A.B.C ``` 2. This will trigger a Github action which performs the release From e862ab207fef6876d65801bed234f0b7d5dbf70f Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Tue, 5 Sep 2023 21:57:19 -0700 Subject: [PATCH 40/67] remove unused bin/build (#1099) closes https://github.com/chroma-core/chroma/issues/1003 --- bin/build | 3 --- 1 file changed, 3 deletions(-) delete mode 100755 bin/build diff --git a/bin/build b/bin/build deleted file mode 100755 index 5e324198f858..000000000000 --- a/bin/build +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -docker build . --target chroma_server -t ghcr.io/chroma-core/chroma-server:`bin/version` From 327accb2de923c413d69dc5878c94f903c24e269 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 5 Sep 2023 22:19:02 -0700 Subject: [PATCH 41/67] [RELEASE] Release 0.4.9 (#1100) ## Description of changes Release 0.4.9 --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 0f5c86300264..62cb174f7b08 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.8" +__version__ = "0.4.9" # Workaround to deal with Colab's old sqlite3 version try: From 747f7c6457fdea29394cdedcfd7cd0bc0541b3eb Mon Sep 17 00:00:00 2001 From: hammadb Date: Tue, 5 Sep 2023 22:26:52 -0700 Subject: [PATCH 42/67] [BLD] Update release workflow tag check --- .github/workflows/chroma-release-python-client.yml | 2 +- .github/workflows/chroma-release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/chroma-release-python-client.yml b/.github/workflows/chroma-release-python-client.yml index f9507ca7d6b3..c09c8c90f966 100644 --- a/.github/workflows/chroma-release-python-client.yml +++ b/.github/workflows/chroma-release-python-client.yml @@ -12,7 +12,7 @@ jobs: check_tag: runs-on: ubuntu-latest outputs: - tag_matches: ${{ steps.check-tag.outputs.match }} + tag_matches: ${{ steps.check-tag.outputs.tag_matches }} steps: - name: Check Tag id: check-tag diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index a397cfc59b2c..ca394a245942 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -16,7 +16,7 @@ jobs: check_tag: runs-on: ubuntu-latest outputs: - tag_matches: ${{ steps.check-tag.outputs.match }} + tag_matches: ${{ steps.check-tag.outputs.tag_matches }} steps: - name: Check Tag id: check-tag From 3241de7a6fc20fe178dbe93090ee89f57a5625b6 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Wed, 6 Sep 2023 22:19:41 -0700 Subject: [PATCH 43/67] update JS instructions (#960) Improve develop instructions for the JS client --------- Co-authored-by: Pascal M <11357019+perzeuss@users.noreply.github.com> Co-authored-by: Hammad Bashir --- clients/js/DEVELOP.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/clients/js/DEVELOP.md b/clients/js/DEVELOP.md index 030f3ef455c6..c82fd15327ed 100644 --- a/clients/js/DEVELOP.md +++ b/clients/js/DEVELOP.md @@ -5,18 +5,24 @@ This readme is helpful for local dev. ### Prereqs: - Make sure you have Java installed (for the generator). You can download it from [java.com](https://java.com) +- Make sure you set ALLOW_RESET=True for your Docker Container. If you don't do this, tests won't pass. +``` +environment: + - IS_PERSISTENT=TRUE + - ALLOW_RESET=True +``` - Make sure you are running the docker backend at localhost:8000 (\*there is probably a way to stand up the fastapi server by itself and programmatically in the loop of generating this, but not prioritizing it for now. It may be important for the release) ### Generating 1. `yarn` to install deps -2. `yarn genapi-zsh` if you have zsh +2. `yarn genapi` 3. Examples are in the `examples` folder. There is one for the browser and one for node. Run them with `yarn dev`, eg `cd examples/browser && yarn dev` ### Running test -`yarn test` will launch a test docker backend. -`yarn test:run` will run against the docker backend you have running. But CAUTION, it will delete data. +`yarn test` will launch a test docker backend, run a db cleanup and run tests. +`yarn test:run` will run against the docker backend you have running. But CAUTION, it will delete data. This is the easiest and fastest way to run tests. ### Pushing to npm From 237b3e3c96c0e25d87dec4c5b2b21085c040dfa3 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Wed, 6 Sep 2023 23:13:29 -0700 Subject: [PATCH 44/67] [BLD] Add dockerhub support (#1112) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Pushes images to dockerhub ## Test plan *How are these changes tested?* Will have to be tested on main as part of CI/CD - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes None required. --- .github/workflows/chroma-release.yml | 53 +++++++++++++--------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/.github/workflows/chroma-release.yml b/.github/workflows/chroma-release.yml index ca394a245942..a2f0a988a433 100644 --- a/.github/workflows/chroma-release.yml +++ b/.github/workflows/chroma-release.yml @@ -8,8 +8,8 @@ on: - main env: - REGISTRY: ghcr.io - IMAGE_NAME: "ghcr.io/chroma-core/chroma" + GHCR_IMAGE_NAME: "ghcr.io/chroma-core/chroma" + DOCKERHUB_IMAGE_NAME: "chromadb/chroma" PLATFORMS: linux/amd64,linux/arm64 #linux/riscv64, linux/arm/v7 jobs: @@ -27,14 +27,7 @@ jobs: build-and-release: runs-on: ubuntu-latest needs: check_tag - if: needs.check_tag.outputs.tag_matches == 'true' permissions: write-all -# id-token: write -# contents: read -# deployments: write -# packages: write -# pull-requests: read -# statuses: write steps: - name: Checkout uses: actions/checkout@v3 @@ -57,36 +50,38 @@ jobs: run: python -m build - name: Test Client Package run: bin/test-package.sh dist/*.tar.gz - - name: Log in to the Container registry + - name: Log in to the Github Container registry uses: docker/login-action@v2.1.0 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Login to DockerHub + uses: docker/login-action@v2.1.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Install setuptools_scm run: python -m pip install setuptools_scm - - name: Get Docker Tag - id: tag - run: echo "tag_name=$IMAGE_NAME:$(bin/version)" >> $GITHUB_OUTPUT + - name: Get Release Version + id: version + run: echo "version=$(python -m setuptools_scm)" >> $GITHUB_OUTPUT - name: Build and push prerelease Docker image - if: "!startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches != 'true'" uses: docker/build-push-action@v3.2.0 with: context: . platforms: ${{ env.PLATFORMS }} push: true - tags: ${{ steps.tag.outputs.tag_name}} + tags: "${{ env.GHCR_IMAGE_NAME }}:${{ steps.version.outputs.version }},${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.version.outputs.version }}" - name: Build and push release Docker image - if: "startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches == 'true'" uses: docker/build-push-action@v3.2.0 with: context: . platforms: ${{ env.PLATFORMS }} push: true - tags: "${{ steps.tag.outputs.tag_name }},${{ env.IMAGE_NAME }}:latest" - - name: Get Release Version - id: version - run: echo "version=$(python -m setuptools_scm)" >> $GITHUB_OUTPUT + tags: "${{ env.GHCR_IMAGE_NAME }}:${{ steps.version.outputs.version }},${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.version.outputs.version }},${{ env.GHCR_IMAGE_NAME }}:latest,${{ env.DOCKERHUB_IMAGE_NAME }}:latest" - name: Get current date id: builddate run: echo "builddate=$(date +'%Y-%m-%dT%H:%M')" >> $GITHUB_OUTPUT @@ -96,7 +91,7 @@ jobs: password: ${{ secrets.TEST_PYPI_API_TOKEN }} repository_url: https://test.pypi.org/legacy/ - name: Publish to PyPI - if: startsWith(github.ref, 'refs/tags') + if: "needs.check_tag.outputs.tag_matches == 'true'" uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} @@ -107,31 +102,32 @@ jobs: aws-region: us-east-1 - name: Generate CloudFormation template id: generate-cf - if: "startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches == 'true'" run: "pip install boto3 && python bin/generate_cloudformation.py" - name: Release Tagged Version uses: ncipollo/release-action@v1.11.1 - if: "startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches == 'true'" with: body: | Version: `${{steps.version.outputs.version}}` Git ref: `${{github.ref}}` Build Date: `${{steps.builddate.outputs.builddate}}` PIP Package: `chroma-${{steps.version.outputs.version}}.tar.gz` - Docker Image: `${{steps.tag.outputs.tag_name}}` + Github Container Registry Image: `${{ env.GHCR_IMAGE_NAME }}:${{ steps.version.outputs.version }}` + DockerHub Image: `${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.version.outputs.version }}` artifacts: "dist/chroma-${{steps.version.outputs.version}}.tar.gz" prerelease: true generateReleaseNotes: true - name: Update Tag uses: richardsimko/update-tag@v1.0.5 - if: "!startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches != 'true'" with: tag_name: latest env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Release Latest uses: ncipollo/release-action@v1.11.1 - if: "!startsWith(github.ref, 'refs/tags/')" + if: "needs.check_tag.outputs.tag_matches != 'true'" with: tag: "latest" name: "Latest" @@ -140,7 +136,8 @@ jobs: Git ref: `${{github.ref}}` Build Date: `${{steps.builddate.outputs.builddate}}` PIP Package: `chroma-${{steps.version.outputs.version}}.tar.gz` - Docker Image: `${{steps.tag.outputs.tag_name}}` + Github Container Registry Image: `${{ env.GHCR_IMAGE_NAME }}:${{ steps.version.outputs.version }}` + DockerHub Image: `${{ env.DOCKERHUB_IMAGE_NAME }}:${{ steps.version.outputs.version }}` artifacts: "dist/chroma-${{steps.version.outputs.version}}.tar.gz" allowUpdates: true prerelease: true From ea73f05bdf91ff771388d6c573af9ddd8ca37cbe Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Thu, 7 Sep 2023 23:24:41 +0300 Subject: [PATCH 45/67] [BUG]: Issue where In/Nin list values (#1111) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Fixed an issue where list values for In/Nin that are not wrapped with pypika ParameterValue will result in floating point comparisons failure after a certain precision threshold. ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes N/A --- chromadb/segment/impl/metadata/sqlite.py | 52 ++++++++---------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/chromadb/segment/impl/metadata/sqlite.py b/chromadb/segment/impl/metadata/sqlite.py index 781aed00ba4a..8e9649a627d0 100644 --- a/chromadb/segment/impl/metadata/sqlite.py +++ b/chromadb/segment/impl/metadata/sqlite.py @@ -105,7 +105,6 @@ def get_metadata( offset: Optional[int] = None, ) -> Sequence[MetadataEmbeddingRecord]: """Query for embedding metadata.""" - embeddings_t, metadata_t, fulltext_t = Tables( "embeddings", "embedding_metadata", "embedding_fulltext_search" ) @@ -135,7 +134,6 @@ def get_metadata( if where: q = q.where(self._where_map_criterion(q, where, embeddings_t, metadata_t)) - if where_document: q = q.where( self._where_doc_criterion(q, where_document, embeddings_t, fulltext_t) @@ -417,32 +415,8 @@ def _where_map_criterion( for w in cast(Sequence[Where], v) ] clause.append(reduce(lambda x, y: x | y, criteria)) - elif k == "$in": - expr = cast( - Dict[InclusionExclusionOperator, List[LiteralValue]], {k: v} - ) - sq = ( - self._db.querybuilder() - .from_(metadata_t) - .select(metadata_t.id) - .where(metadata_t.key.isin(ParameterValue(k))) - .where(_where_clause(expr, metadata_t)) - ) - clause.append(embeddings_t.id.isin(sq)) - elif k == "$nin": - expr = cast( - Dict[InclusionExclusionOperator, List[LiteralValue]], {k: v} - ) - sq = ( - self._db.querybuilder() - .from_(metadata_t) - .select(metadata_t.id) - .where(metadata_t.key.notin(ParameterValue(k))) - .where(_where_clause(expr, metadata_t)) - ) - clause.append(embeddings_t.id.notin(sq)) else: - expr = cast(Union[LiteralValue, Dict[WhereOperator, LiteralValue]], v) # type: ignore + expr = cast(Union[LiteralValue, Dict[WhereOperator, LiteralValue]], v) sq = ( self._db.querybuilder() .from_(metadata_t) @@ -554,30 +528,36 @@ def _value_criterion( raise ValueError(f"Empty list for {op} operator") if isinstance(value[0], str): col_exprs = [ - table.string_value.isin(_v) + table.string_value.isin(ParameterValue(_v)) if op == "$in" - else table.str_value.notin(_v) + else table.str_value.notin(ParameterValue(_v)) ] elif isinstance(value[0], bool): col_exprs = [ - table.bool_value.isin(_v) if op == "$in" else table.bool_value.notin(_v) + table.bool_value.isin(ParameterValue(_v)) + if op == "$in" + else table.bool_value.notin(ParameterValue(_v)) ] elif isinstance(value[0], int): col_exprs = [ - table.int_value.isin(_v) if op == "$in" else table.int_value.notin(_v) + table.int_value.isin(ParameterValue(_v)) + if op == "$in" + else table.int_value.notin(ParameterValue(_v)) ] elif isinstance(value[0], float): col_exprs = [ - table.float_value.isin(_v) + table.float_value.isin(ParameterValue(_v)) if op == "$in" - else table.float_value.notin(_v) + else table.float_value.notin(ParameterValue(_v)) ] elif isinstance(value, list) and op in ("$in", "$nin"): col_exprs = [ - table.int_value.isin(value), - table.float_value.isin(value) + table.int_value.isin(ParameterValue(value)) + if op == "$in" + else table.int_value.notin(ParameterValue(value)), + table.float_value.isin(ParameterValue(value)) if op == "$in" - else table.float_value.notin(value), + else table.float_value.notin(ParameterValue(value)), ] else: cols = [table.int_value, table.float_value] From 9c1979c9311acb8662fc22a7deac17907a9b128b Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Mon, 11 Sep 2023 17:58:20 +0300 Subject: [PATCH 46/67] [BUG]: URL Parsing And Validation (#1118) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Added additional validations to URLs - URLs like api-gw.aws.com/dev will now trigger an error asking the user to correctly specify the URL with http or https - When the full URL (http(s)://example.com) is provided by the user, the port parameter is ignored (debug message is logged). An assumption is made that the URL is entirely defined, thus not requiring additional alterations such as injecting the port. - Added negative test cases for invalid URLs ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes TBD --- chromadb/api/fastapi.py | 37 ++++++++++++++--- chromadb/test/property/test_client_url.py | 48 +++++++++++++++++++++-- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/chromadb/api/fastapi.py b/chromadb/api/fastapi.py index 8498f9ec110e..c08458a2fcb9 100644 --- a/chromadb/api/fastapi.py +++ b/chromadb/api/fastapi.py @@ -1,4 +1,5 @@ import json +import logging from typing import Optional, cast from typing import Sequence from uuid import UUID @@ -32,28 +33,54 @@ from chromadb.telemetry import Telemetry from urllib.parse import urlparse, urlunparse, quote +logger = logging.getLogger(__name__) + class FastAPI(API): _settings: Settings + @staticmethod + def _validate_host(host: str) -> None: + parsed = urlparse(host) + if "/" in host and parsed.scheme not in {"http", "https"}: + raise ValueError( + "Invalid URL. " f"Unrecognized protocol - {parsed.scheme}." + ) + if "/" in host and (not host.startswith("http")): + raise ValueError( + "Invalid URL. " + "Seems that you are trying to pass URL as a host but without specifying the protocol. " + "Please add http:// or https:// to the host." + ) + @staticmethod def resolve_url( chroma_server_host: str, chroma_server_ssl_enabled: Optional[bool] = False, default_api_path: Optional[str] = "", - chroma_server_http_port: int = 8000, + chroma_server_http_port: Optional[int] = 8000, ) -> str: - parsed = urlparse(chroma_server_host) + _skip_port = False + _chroma_server_host = chroma_server_host + FastAPI._validate_host(_chroma_server_host) + if _chroma_server_host.startswith("http"): + logger.debug("Skipping port as the user is passing a full URL") + _skip_port = True + parsed = urlparse(_chroma_server_host) scheme = "https" if chroma_server_ssl_enabled else parsed.scheme or "http" net_loc = parsed.netloc or parsed.hostname or chroma_server_host - port = parsed.port or chroma_server_http_port + port = ( + ":" + str(parsed.port or chroma_server_http_port) if not _skip_port else "" + ) path = parsed.path or default_api_path - if not path or path == net_loc or not path.endswith(default_api_path or ""): + if not path or path == net_loc: path = default_api_path if default_api_path else "" + if not path.endswith(default_api_path or ""): + path = path + default_api_path if default_api_path else "" full_url = urlunparse( - (scheme, f"{net_loc}:{port}", quote(path.replace("//", "/")), "", "", "") + (scheme, f"{net_loc}{port}", quote(path.replace("//", "/")), "", "", "") ) return full_url diff --git a/chromadb/test/property/test_client_url.py b/chromadb/test/property/test_client_url.py index 992af9813995..cc5df1e05141 100644 --- a/chromadb/test/property/test_client_url.py +++ b/chromadb/test/property/test_client_url.py @@ -1,6 +1,7 @@ from typing import Optional from urllib.parse import urlparse +import pytest from hypothesis import given, strategies as st from chromadb.api.fastapi import FastAPI @@ -28,7 +29,7 @@ def domain_strategy() -> st.SearchStrategy[str]: return st.tuples(label, tld).map(".".join) -port_strategy = st.integers(min_value=1, max_value=65535) +port_strategy = st.one_of(st.integers(min_value=1, max_value=65535), st.none()) ssl_enabled_strategy = st.booleans() @@ -56,8 +57,21 @@ def is_valid_url(url: str) -> bool: def generate_valid_domain_url() -> st.SearchStrategy[str]: return st.builds( - lambda url_scheme, hostname, url_path: f"{url_scheme}://{hostname}{url_path}", - url_scheme=st.sampled_from(["http", "https"]), + lambda url_scheme, hostname, url_path: f"{url_scheme}{hostname}{url_path}", + url_scheme=st.sampled_from(["http://", "https://"]), + hostname=domain_strategy(), + url_path=url_path_strategy(), + ) + + +def generate_invalid_domain_url() -> st.SearchStrategy[str]: + return st.builds( + lambda url_scheme, hostname, url_path: f"{url_scheme}{hostname}{url_path}", + url_scheme=st.builds( + lambda scheme, suffix: f"{scheme}{suffix}", + scheme=st.text(max_size=10), + suffix=st.sampled_from(["://", ":///", ":////", ""]), + ), hostname=domain_strategy(), url_path=url_path_strategy(), ) @@ -76,7 +90,7 @@ def generate_valid_domain_url() -> st.SearchStrategy[str]: ) def test_url_resolve( hostname: str, - port: int, + port: Optional[int], ssl_enabled: bool, default_api_path: Optional[str], ) -> None: @@ -90,5 +104,31 @@ def test_url_resolve( assert ( _url.startswith("https") if ssl_enabled else _url.startswith("http") ), f"Invalid URL: {_url} - SSL Enabled: {ssl_enabled}" + if hostname.startswith("http"): + assert ":" + str(port) not in _url, f"Port in URL not expected: {_url}" + else: + assert ":" + str(port) in _url, f"Port in URL expected: {_url}" if default_api_path: assert _url.endswith(default_api_path), f"Invalid URL: {_url}" + + +@given( + hostname=generate_invalid_domain_url(), + port=port_strategy, + ssl_enabled=ssl_enabled_strategy, + default_api_path=st.sampled_from(["/api/v1", "/api/v2", None]), +) +def test_resolve_invalid( + hostname: str, + port: Optional[int], + ssl_enabled: bool, + default_api_path: Optional[str], +) -> None: + with pytest.raises(ValueError) as e: + FastAPI.resolve_url( + chroma_server_host=hostname, + chroma_server_http_port=port, + chroma_server_ssl_enabled=ssl_enabled, + default_api_path=default_api_path, + ) + assert "Invalid URL" in str(e.value) From 2dd5a1552687043889a2dafe27de56577ad278a7 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Mon, 11 Sep 2023 18:19:57 +0300 Subject: [PATCH 47/67] [ENH] Added auth and external volume support for GCP (#1107) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Added external volume for Chroma data - Bumped to the latest version (0.4.9) - Added auth by default - Made the template more configurable via variables with sensible defaults ## Test plan *How are these changes tested?* - Tested with terraform ## Documentation Changes The update contains README with docs. --- .../google-cloud-compute/README.md | 98 +++++++++++++- .../google-cloud-compute/chroma.tf | 105 ++++++++++++-- .../deployments/google-cloud-compute/main.tf | 8 -- .../google-cloud-compute/startup.sh | 39 +++++- .../google-cloud-compute/variables.tf | 128 +++++++++++++++++- 5 files changed, 348 insertions(+), 30 deletions(-) diff --git a/examples/deployments/google-cloud-compute/README.md b/examples/deployments/google-cloud-compute/README.md index 8da1af830de9..ea25613baf46 100644 --- a/examples/deployments/google-cloud-compute/README.md +++ b/examples/deployments/google-cloud-compute/README.md @@ -3,43 +3,135 @@ This is an example deployment to Google Cloud Compute using [terraform](https://www.terraform.io/) ## Requirements + - [gcloud CLI](https://cloud.google.com/sdk/gcloud) - [Terraform CLI v1.3.4+](https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/install-cli) +- [Terraform GCP provider](https://registry.terraform.io/providers/hashicorp/google/latest/docs) ## Deployment with terraform ### 1. Auth to your Google Cloud project + ```bash gcloud auth application-default login ``` ### 2. Init your terraform state + ```bash terraform init ``` ### 3. Deploy your application + +> **WARNING**: GCP Terraform provider does not allow use of variables in the lifecycle of the volume. By default, the +> template does not prevent deletion of the volume however if you plan to use this template for production deployment you +> may consider change the value of `prevent_destroy` to `true` in `chroma.tf` file. + +Generate SSH key to use with your chroma instance (so you can SSH to the GCP VM): + +> Note: This is optional. You can use your own existing SSH key if you prefer. + +```bash +ssh-keygen -t RSA -b 4096 -C "Chroma AWS Key" -N "" -f ./chroma-aws && chmod 400 ./chroma-aws +``` + ```bash export TF_VAR_project_id= #take note of this as it must be present in all of the subsequent steps -export TF_VAR_chroma_release=0.4.5 #set the chroma release to deploy +export TF_ssh_public_key="./chroma-aws.pub" #path to the public key you generated above (or can be different if you want to use your own key) +export TF_ssh_private_key="./chroma-aws" #path to the private key you generated above (or can be different if you want to use your own key) - used for formatting the Chroma data volume +export TF_VAR_chroma_release="0.4.9" #set the chroma release to deploy +export TF_VAR_zone="us-central1-a" # AWS region to deploy the chroma instance to +export TF_VAR_public_access="true" #enable public access to the chroma instance on port 8000 +export TF_VAR_enable_auth="true" #enable basic auth for the chroma instance +export TF_VAR_auth_type="token" #The auth type to use for the chroma instance (token or basic) terraform apply -auto-approve ``` ### 4. Check your public IP and that Chroma is running -Get the public IP of your instance +> Note: Depending on your instance type it might take a few minutes for the instance to be ready + +Get the public IP of your instance (it should also be printed out after successful `terraform apply`): ```bash terraform output instance_public_ip ``` -Check that chroma is running +Check that chroma is running: + ```bash export instance_public_ip=$(terraform output instance_public_ip | sed 's/"//g') curl -v http://$instance_public_ip:8000/api/v1/heartbeat ``` +#### 4.1 Checking Auth + +##### Token + +When token auth is enabled (this is the default option) you can check the get the credentials from Terraform state by +running: + +```bash +terraform output chroma_auth_token +``` + +You should see something of the form: + +```bash +PVcQ4qUUnmahXwUgAf3UuYZoMlos6MnF +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_token | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -H "Authorization: Bearer ${CHROMA_AUTH}" +``` + +##### Basic + +When basic auth is enabled you can check the get the credentials from Terraform state by running: + +```bash +terraform output chroma_auth_basic +``` + +You should see something of the form: + +```bash +chroma:VuA8I}QyNrm0@QLq +``` + +You can then export these credentials: + +```bash +export CHROMA_AUTH=$(terraform output chroma_auth_basic | sed 's/"//g') +``` + +Using the credentials: + +```bash +curl -v http://$instance_public_ip:8000/api/v1/collections -u "${CHROMA_AUTH}" +``` + +> Note: Without `-u` you should be getting 401 Unauthorized response + +#### 4.2 SSH to your instance + +To SSH to your instance: + +```bash +ssh -i ./chroma-aws debian@$instance_public_ip +``` + ### 5. Destroy your application + ```bash terraform destroy -auto-approve ``` diff --git a/examples/deployments/google-cloud-compute/chroma.tf b/examples/deployments/google-cloud-compute/chroma.tf index 7ed41b417a28..f49fc59cfe37 100644 --- a/examples/deployments/google-cloud-compute/chroma.tf +++ b/examples/deployments/google-cloud-compute/chroma.tf @@ -1,18 +1,36 @@ -resource "google_compute_instance" "chroma1" { +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.80.0" + } + } +} + +resource "google_compute_instance" "chroma" { project = var.project_id name = "chroma-1" machine_type = var.machine_type zone = var.zone - tags = ["chroma"] + tags = local.tags + + labels = var.labels + boot_disk { initialize_params { - image = "debian-cloud/debian-11" - size = 20 + image = var.image + size = var.chroma_instance_volume_size #size in GB } } + attached_disk { + source = google_compute_disk.chroma.id + device_name = var.chroma_data_volume_device_name + mode = "READ_WRITE" + } + network_interface { network = "default" @@ -21,30 +39,91 @@ resource "google_compute_instance" "chroma1" { } } - metadata_startup_script = templatefile("${path.module}/startup.sh", { chroma_release = var.chroma_release }) + metadata = { + ssh-keys = "${var.vm_user}:${file(var.ssh_public_key)}" + } + + metadata_startup_script = templatefile("${path.module}/startup.sh", { + chroma_release = var.chroma_release, + enable_auth = var.enable_auth, + auth_type = var.auth_type, + basic_auth_credentials = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}", + token_auth_credentials = random_password.chroma_token.result, + }) + + provisioner "remote-exec" { + inline = [ + "export VOLUME_ID=${var.chroma_data_volume_device_name} && sudo mkfs -t ext4 /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}')", + "sudo mkdir /chroma-data", + "export VOLUME_ID=${var.chroma_data_volume_device_name} && sudo mount /dev/$(lsblk -o +SERIAL | grep $VOLUME_ID | awk '{print $1}') /chroma-data" + ] + + connection { + host = google_compute_instance.chroma.network_interface[0].access_config[0].nat_ip + type = "ssh" + user = var.vm_user + private_key = file(var.ssh_private_key) + } + } } + +resource "google_compute_disk" "chroma" { + project = var.project_id + name = "chroma-data" + type = var.disk_type + zone = var.zone + labels = var.labels + size = var.chroma_data_volume_size #size in GB + + lifecycle { + prevent_destroy = false #WARNING: You need to configure this manually as the provider does not support it yet + } +} + +#resource "google_compute_attached_disk" "vm_attached_disk" { +# disk = google_compute_disk.chroma.id +# instance = google_compute_instance.chroma.self_link +# +#} + + + resource "google_compute_firewall" "default" { project = var.project_id name = "chroma-firewall" network = "default" allow { - protocol = "icmp" + protocol = "icmp" #allow ping } - allow { - protocol = "tcp" - ports = ["8000"] + dynamic "allow" { + for_each = var.public_access ? [1] : [] + content { + protocol = "tcp" + ports = [var.chroma_port] + } } - source_ranges = ["0.0.0.0/0"] + source_ranges = var.source_ranges - target_tags = ["chroma"] + target_tags = local.tags } output "instance_public_ip" { description = "The public IP address of the instance." - value = google_compute_instance.chroma1.network_interface[0].access_config[0].nat_ip -} \ No newline at end of file + value = google_compute_instance.chroma.network_interface[0].access_config[0].nat_ip +} + +output "chroma_auth_token" { + value = random_password.chroma_token.result + sensitive = true +} + + +output "chroma_auth_basic" { + value = "${local.basic_auth_credentials.username}:${local.basic_auth_credentials.password}" + sensitive = true +} diff --git a/examples/deployments/google-cloud-compute/main.tf b/examples/deployments/google-cloud-compute/main.tf index a73c37765429..e69de29bb2d1 100644 --- a/examples/deployments/google-cloud-compute/main.tf +++ b/examples/deployments/google-cloud-compute/main.tf @@ -1,8 +0,0 @@ -terraform { - required_providers { - google = { - source = "hashicorp/google" - version = "~> 4.47.0" - } - } -} diff --git a/examples/deployments/google-cloud-compute/startup.sh b/examples/deployments/google-cloud-compute/startup.sh index d0140670cb43..1d93e46c3e23 100644 --- a/examples/deployments/google-cloud-compute/startup.sh +++ b/examples/deployments/google-cloud-compute/startup.sh @@ -1,7 +1,12 @@ #! /bin/bash -cd ~ +# Note: This is run as root +cd ~ +export enable_auth="${enable_auth}" +export basic_auth_credentials="${basic_auth_credentials}" +export auth_type="${auth_type}" +export token_auth_credentials="${token_auth_credentials}" apt-get update -y apt-get install -y ca-certificates curl gnupg lsb-release mkdir -m 0755 -p /etc/apt/keyrings @@ -13,10 +18,36 @@ apt-get update -y chmod a+r /etc/apt/keyrings/docker.gpg apt-get update -y apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin git - -git clone https://github.com/chroma-core/chroma.git -cd chroma +usermod -aG docker debian +git clone https://github.com/chroma-core/chroma.git && cd chroma git fetch --tags git checkout tags/${chroma_release} +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "basic" ] && [ ! -z "$${basic_auth_credentials}" ]; then + username=$(echo $basic_auth_credentials | cut -d: -f1) + password=$(echo $basic_auth_credentials | cut -d: -f2) + docker run --rm --entrypoint htpasswd httpd:2 -Bbn $username $password > server.htpasswd + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS_FILE="/chroma/server.htpasswd" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' +EOF +fi + +if [ "$${enable_auth}" = "true" ] && [ "$${auth_type}" = "token" ] && [ ! -z "$${token_auth_credentials}" ]; then + cat < .env +CHROMA_SERVER_AUTH_CREDENTIALS="$${token_auth_credentials}" \ +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.token.TokenConfigServerAuthCredentialsProvider' +CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.token.TokenAuthServerProvider' +EOF +fi + +cat < docker-compose.override.yaml +version: '3.8' +services: + server: + volumes: + - /chroma-data:/chroma/chroma +EOF + COMPOSE_PROJECT_NAME=chroma docker compose up -d --build diff --git a/examples/deployments/google-cloud-compute/variables.tf b/examples/deployments/google-cloud-compute/variables.tf index a3bca24654bf..0147ce49aa42 100644 --- a/examples/deployments/google-cloud-compute/variables.tf +++ b/examples/deployments/google-cloud-compute/variables.tf @@ -1,10 +1,11 @@ variable "project_id" { - type = string + type = string + description = "The project id to deploy to" } variable "chroma_release" { description = "The chroma release to deploy" type = string - default = "0.4.5" + default = "0.4.9" } variable "zone" { @@ -12,7 +13,130 @@ variable "zone" { default = "us-central1-a" } +variable "image" { + default = "debian-cloud/debian-11" + description = "The image to use for the instance" + type = string +} + +variable "vm_user" { + default = "debian" + description = "The user to use for connecting to the instance. This is usually the default image user" + type = string +} + variable "machine_type" { type = string default = "e2-small" } + +variable "public_access" { + description = "Enable public ingress on port 8000" + type = bool + default = true // or true depending on your needs +} + +variable "enable_auth" { + description = "Enable authentication" + type = bool + default = true // or false depending on your needs +} + +variable "auth_type" { + description = "Authentication type" + type = string + default = "token" // or token depending on your needs + validation { + condition = contains(["basic", "token"], var.auth_type) + error_message = "The auth type must be either basic or token" + } +} + +resource "random_password" "chroma_password" { + length = 16 + special = true + lower = true + upper = true +} + +resource "random_password" "chroma_token" { + length = 32 + special = false + lower = true + upper = true +} + + +locals { + basic_auth_credentials = { + username = "chroma" + password = random_password.chroma_password.result + } + token_auth_credentials = { + token = random_password.chroma_token.result + } + tags = [ + "chroma", + "release-${replace(var.chroma_release, ".", "")}", + ] +} + +variable "ssh_public_key" { + description = "SSH Public Key" + type = string + default = "./chroma-aws.pub" +} +variable "ssh_private_key" { + description = "SSH Private Key" + type = string + default = "./chroma-aws" +} + +variable "chroma_instance_volume_size" { + description = "The size of the instance volume - the root volume" + type = number + default = 30 +} + +variable "chroma_data_volume_size" { + description = "Volume Size of the attached data volume where your chroma data is stored" + type = number + default = 20 +} + +variable "chroma_data_volume_device_name" { + default = "chroma-disk-0" + description = "The device name of the chroma data volume" + type = string +} + +variable "prevent_chroma_data_volume_delete" { + description = "Prevent the chroma data volume from being deleted when the instance is terminated" + type = bool + default = false +} + +variable "disk_type" { + default = "pd-ssd" + description = "The type of disk to use for the instance. Can be either pd-standard or pd-ssd" +} + +variable "labels" { + default = { + environment = "dev" + } + description = "Labels to apply to all resources in this example" + type = map(string) +} + +variable "chroma_port" { + default = "8000" + description = "The port that chroma listens on" + type = string +} + +variable "source_ranges" { + default = ["0.0.0.0/0"] + type = list(string) + description = "List of CIDR ranges to allow through the firewall" +} From 9db68045e6b57fdaa418ad723f72a3121be297ac Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Mon, 11 Sep 2023 09:49:36 -0700 Subject: [PATCH 48/67] [CHORE] Bump HNSWlib to latest version that has precompiled binaries (#1109) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Bump HNSWlib to latest version that has precompiled binaries. Use alpha release for CI tests before releasing ## Test plan Existing tests should over functionality. Build compatibility of the binaries was manually verified. - [x] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes We should add how to force recompiling with AVX to the docs. --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3d62663ca86b..926048d38ff7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ dependencies = [ 'requests >= 2.28', 'pydantic>=1.9,<2.0', - 'chroma-hnswlib==0.7.2', + 'chroma-hnswlib==0.7.3', 'fastapi>=0.95.2, <0.100.0', 'uvicorn[standard] >= 0.18.3', 'numpy == 1.21.6; python_version < "3.8"', diff --git a/requirements.txt b/requirements.txt index abf333a95c04..e94f7caf91f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ bcrypt==4.0.1 -chroma-hnswlib==0.7.2 +chroma-hnswlib==0.7.3 fastapi>=0.95.2, <0.100.0 graphlib_backport==1.0.3; python_version < '3.9' importlib-resources From 8e967304d6c1a18d88b63c1fdb3e7a638a678f01 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Mon, 11 Sep 2023 11:09:02 -0700 Subject: [PATCH 49/67] [RELEASE] 0.4.10 (#1132) Release 0.4.10 --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 62cb174f7b08..804ff93404c2 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.9" +__version__ = "0.4.10" # Workaround to deal with Colab's old sqlite3 version try: From 7d412aef8c8b8e7623e8d394763ced4373e24898 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 11 Sep 2023 20:49:25 -0700 Subject: [PATCH 50/67] [ENH] initial CLI (#1032) This proposes an initial CLI The CLI is installed when you installed `pip install chromadb`. You then call the CLI with `chroma run --path --port ` where path and port are optional. This also adds `chroma help` and `chroma docs` as convenience links - but I'm open to removing those. To make this easy - I added `typer` (by the author of FastAPI). I'm not sure this is the tool that we want to commit to for a fuller featured CLI, but given the extremely minimal footprint of this - I don't think it's a one way door. Screenshot 2023-08-23 at 4 59 54 PM *** #### TODO - [x] test in fresh env - i think i need to add `typer` as a req - [ ] consider expanding the test to make sure the service is actually running - [x] hide the test option from the typer UI - [x] linking to a getting started guide could be interesting at the top of the logs --- .github/workflows/chroma-integration-test.yml | 3 +- DEVELOP.md | 2 +- README.md | 2 +- chromadb/cli/__init__.py | 0 chromadb/cli/cli.py | 87 +++++++++++++++++++ chromadb/log_config.yml | 37 ++++++++ chromadb/test/test_cli.py | 21 +++++ clients/js/package.json | 5 +- docker-compose.test-auth.yml | 2 +- docker-compose.test.yml | 2 +- docker-compose.yml | 2 +- examples/use_with/cohere/cohere_js.js | 20 ++--- log_config.yml | 23 ----- pyproject.toml | 9 +- requirements.txt | 1 + 15 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 chromadb/cli/__init__.py create mode 100644 chromadb/cli/cli.py create mode 100644 chromadb/log_config.yml create mode 100644 chromadb/test/test_cli.py delete mode 100644 log_config.yml diff --git a/.github/workflows/chroma-integration-test.yml b/.github/workflows/chroma-integration-test.yml index e4a2b7517f5f..91628d6f5453 100644 --- a/.github/workflows/chroma-integration-test.yml +++ b/.github/workflows/chroma-integration-test.yml @@ -16,8 +16,9 @@ jobs: matrix: python: ['3.7'] platform: [ubuntu-latest, windows-latest] - testfile: ["--ignore-glob 'chromadb/test/property/*'", + testfile: ["--ignore-glob 'chromadb/test/property/*' --ignore='chromadb/test/test_cli.py'", "chromadb/test/property/test_add.py", + "chromadb/test/test_cli.py", "chromadb/test/property/test_collections.py", "chromadb/test/property/test_cross_version_persist.py", "chromadb/test/property/test_embeddings.py", diff --git a/DEVELOP.md b/DEVELOP.md index 29f36abb1ff9..f034e07bed38 100644 --- a/DEVELOP.md +++ b/DEVELOP.md @@ -43,7 +43,7 @@ print(api.heartbeat()) 3. With a persistent backend and a small frontend client -Run `docker-compose up -d --build` +Run `chroma run --path /chroma_db_path` ```python import chromadb api = chromadb.HttpClient(host="localhost", port="8000") diff --git a/README.md b/README.md index 7354f7ed9912..25db53b73d8c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ ```bash pip install chromadb # python client # for javascript, npm install chromadb! -# for client-server mode, docker-compose up -d --build +# for client-server mode, chroma run --path /chroma_db_path ``` The core API is only 4 functions (run our [💡 Google Colab](https://colab.research.google.com/drive/1QEzFyqnoFxq7LUGyP1vzR4iLt9PpCDXv?usp=sharing) or [Replit template](https://replit.com/@swyx/BasicChromaStarter?v=1)): diff --git a/chromadb/cli/__init__.py b/chromadb/cli/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/chromadb/cli/cli.py b/chromadb/cli/cli.py new file mode 100644 index 000000000000..d60a80b8a48e --- /dev/null +++ b/chromadb/cli/cli.py @@ -0,0 +1,87 @@ +import typer +import uvicorn +import os +import webbrowser + +app = typer.Typer() + +_logo = """ + \033[38;5;069m((((((((( \033[38;5;203m(((((\033[38;5;220m#### + \033[38;5;069m(((((((((((((\033[38;5;203m(((((((((\033[38;5;220m######### + \033[38;5;069m(((((((((((((\033[38;5;203m(((((((((((\033[38;5;220m########### + \033[38;5;069m((((((((((((((\033[38;5;203m((((((((((((\033[38;5;220m############ + \033[38;5;069m(((((((((((((\033[38;5;203m((((((((((((((\033[38;5;220m############# + \033[38;5;069m(((((((((((((\033[38;5;203m((((((((((((((\033[38;5;220m############# + \033[38;5;069m((((((((((((\033[38;5;203m(((((((((((((\033[38;5;220m############## + \033[38;5;069m((((((((((((\033[38;5;203m((((((((((((\033[38;5;220m############## + \033[38;5;069m((((((((((\033[38;5;203m(((((((((((\033[38;5;220m############# + \033[38;5;069m((((((((\033[38;5;203m((((((((\033[38;5;220m############## + \033[38;5;069m(((((\033[38;5;203m(((( \033[38;5;220m#########\033[0m + + """ + + +@app.command() # type: ignore +def run( + path: str = typer.Option( + "./chroma_data", help="The path to the file or directory." + ), + port: int = typer.Option(8000, help="The port to run the server on."), + test: bool = typer.Option(False, help="Test mode.", show_envvar=False, hidden=True), +) -> None: + """Run a chroma server""" + + print("\033[1m") # Bold logo + print(_logo) + print("\033[1m") # Bold + print("Running Chroma") + print("\033[0m") # Reset + + typer.echo(f"\033[1mSaving data to\033[0m: \033[32m{path}\033[0m") + typer.echo( + f"\033[1mConnect to chroma at\033[0m: \033[32mhttp://localhost:{port}\033[0m" + ) + typer.echo( + "\033[1mGetting started guide\033[0m: https://docs.trychroma.com/getting-started\n\n" + ) + + # set ENV variable for PERSIST_DIRECTORY to path + os.environ["IS_PERSISTENT"] = "True" + os.environ["PERSIST_DIRECTORY"] = path + + # get the path where chromadb is installed + chromadb_path = os.path.dirname(os.path.realpath(__file__)) + + # this is the path of the CLI, we want to move up one directory + chromadb_path = os.path.dirname(chromadb_path) + + config = { + "app": "chromadb.app:app", + "host": "0.0.0.0", + "port": port, + "workers": 1, + "log_config": f"{chromadb_path}/log_config.yml", + } + + if test: + return + + uvicorn.run(**config) + + +@app.command() # type: ignore +def help() -> None: + """Opens help url in your browser""" + + webbrowser.open("https://discord.gg/MMeYNTmh3x") + + +@app.command() # type: ignore +def docs() -> None: + """Opens docs url in your browser""" + + webbrowser.open("https://docs.trychroma.com") + + +if __name__ == "__main__": + app() diff --git a/chromadb/log_config.yml b/chromadb/log_config.yml new file mode 100644 index 000000000000..80e62479917c --- /dev/null +++ b/chromadb/log_config.yml @@ -0,0 +1,37 @@ +version: 1 +disable_existing_loggers: False +formatters: + default: + "()": uvicorn.logging.DefaultFormatter + format: '%(levelprefix)s [%(asctime)s] %(message)s' + use_colors: null + datefmt: '%d-%m-%Y %H:%M:%S' + access: + "()": uvicorn.logging.AccessFormatter + format: '%(levelprefix)s [%(asctime)s] %(client_addr)s - "%(request_line)s" %(status_code)s' + datefmt: '%d-%m-%Y %H:%M:%S' +handlers: + default: + formatter: default + class: logging.StreamHandler + stream: ext://sys.stderr + access: + formatter: access + class: logging.StreamHandler + stream: ext://sys.stdout + console: + class: logging.StreamHandler + stream: ext://sys.stdout + formatter: default + file: + class : logging.handlers.RotatingFileHandler + filename: chroma.log + formatter: default +loggers: + root: + level: WARN + handlers: [console, file] + chromadb: + level: DEBUG + uvicorn: + level: INFO diff --git a/chromadb/test/test_cli.py b/chromadb/test/test_cli.py new file mode 100644 index 000000000000..231877341f56 --- /dev/null +++ b/chromadb/test/test_cli.py @@ -0,0 +1,21 @@ +from typer.testing import CliRunner + +from chromadb.cli.cli import app + +runner = CliRunner() + + +def test_app() -> None: + result = runner.invoke( + app, + [ + "run", + "--path", + "chroma_test_data", + "--port", + "8001", + "--test", + ], + ) + assert "chroma_test_data" in result.stdout + assert "8001" in result.stdout diff --git a/clients/js/package.json b/clients/js/package.json index 53dbaf0e9c6d..13014226bed3 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -29,7 +29,9 @@ "dist" ], "scripts": { - "test": "run-s db:clean db:run test:runfull db:clean db:run-auth test:runfull-authonly db:clean", + "test": "run-s db:clean db:cleanauth db:run test:runfull db:clean db:run-auth test:runfull-authonly db:cleanauth", + "testnoauth": "run-s db:clean db:run test:runfull db:clean", + "testauth": "run-s db:cleanauth db:run-auth test:runfull-authonly db:cleanauth", "test:set-port": "cross-env URL=localhost:8001", "test:run": "jest --runInBand --testPathIgnorePatterns=test/auth.basic.test.ts", "test:run-auth": "jest --runInBand --testPathPattern=test/auth.basic.test.ts", @@ -37,6 +39,7 @@ "test:runfull-authonly": "PORT=8001 jest --runInBand --testPathPattern=test/auth.basic.test.ts", "test:update": "run-s db:clean db:run && jest --runInBand --updateSnapshot && run-s db:clean", "db:clean": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml down --volumes", + "db:cleanauth": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test-auth.yml down --volumes", "db:run": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml up --detach && sleep 5", "db:run-auth": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test-auth.yml up --detach && sleep 5", "clean": "rimraf dist", diff --git a/docker-compose.test-auth.yml b/docker-compose.test-auth.yml index 945739782a1a..921f0749f06d 100644 --- a/docker-compose.test-auth.yml +++ b/docker-compose.test-auth.yml @@ -12,7 +12,7 @@ services: volumes: - ./:/chroma - test_index_data:/index_data - command: uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml + command: uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --log-config chromadb/log_config.yml environment: - ANONYMIZED_TELEMETRY=False - ALLOW_RESET=True diff --git a/docker-compose.test.yml b/docker-compose.test.yml index eb65303ebd6a..514047257303 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -12,7 +12,7 @@ services: volumes: - ./:/chroma - test_index_data:/index_data - command: uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml + command: uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --log-config chromadb/log_config.yml environment: - ANONYMIZED_TELEMETRY=False - ALLOW_RESET=True diff --git a/docker-compose.yml b/docker-compose.yml index 2119eba7e001..93581dd23c7b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: - ./:/chroma # Be aware that indexed data are located in "/chroma/chroma/" # Default configuration for persist_directory in chromadb/config.py - command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml + command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config chromadb/log_config.yml environment: - IS_PERSISTENT=TRUE - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} diff --git a/examples/use_with/cohere/cohere_js.js b/examples/use_with/cohere/cohere_js.js index 585e383c3716..7fe866916029 100644 --- a/examples/use_with/cohere/cohere_js.js +++ b/examples/use_with/cohere/cohere_js.js @@ -7,7 +7,7 @@ First run Chroma ``` git clone git@github.com:chroma-core/chroma.git cd chroma -docker-compose up -d --build +chroma run --path /chroma_db_path ``` Then install chroma and cohere @@ -61,20 +61,20 @@ const main = async () => { }); // # 나는 오렌지를 좋아한다 is "I like oranges" in Korean - multilingual_texts = [ 'Hello from Cohere!', 'مرحبًا من كوهير!', - 'Hallo von Cohere!', 'Bonjour de Cohere!', - '¡Hola desde Cohere!', 'Olá do Cohere!', - 'Ciao da Cohere!', '您好,来自 Cohere!', - 'कोहेरे से नमस्ते!', '나는 오렌지를 좋아한다' ] + multilingual_texts = ['Hello from Cohere!', 'مرحبًا من كوهير!', + 'Hallo von Cohere!', 'Bonjour de Cohere!', + '¡Hola desde Cohere!', 'Olá do Cohere!', + 'Ciao da Cohere!', '您好,来自 Cohere!', + 'कोहेरे से नमस्ते!', '나는 오렌지를 좋아한다'] let ids = Array.from({ length: multilingual_texts.length }, (_, i) => String(i)); await collection.add({ - ids:ids, - documents:multilingual_texts -}) + ids: ids, + documents: multilingual_texts + }) - console.log(await collection.query({queryTexts:["citrus"], nResults:1})) + console.log(await collection.query({ queryTexts: ["citrus"], nResults: 1 })) } diff --git a/log_config.yml b/log_config.yml deleted file mode 100644 index e8da3c2c7de6..000000000000 --- a/log_config.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: 1 -disable_existing_loggers: False -formatters: - default: - format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s' - datefmt: '%Y-%m-%d %H:%M:%S' -handlers: - console: - class: logging.StreamHandler - stream: ext://sys.stdout - formatter: default - file: - class : logging.handlers.RotatingFileHandler - filename: chroma.log - formatter: default -loggers: - root: - level: WARN - handlers: [console, file] - chromadb: - level: DEBUG - uvicorn: - level: INFO diff --git a/pyproject.toml b/pyproject.toml index 926048d38ff7..8fc60673607e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,8 @@ dependencies = [ 'overrides >= 7.3.1', 'importlib-resources', 'graphlib_backport >= 1.0.3; python_version < "3.9"', - 'bcrypt >= 4.0.1' + 'bcrypt >= 4.0.1', + 'typer >= 0.9.0', ] [tool.black] @@ -43,6 +44,9 @@ target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] [tool.pytest.ini_options] pythonpath = ["."] +[project.scripts] +chroma = "chromadb.cli.cli:app" + [project.urls] "Homepage" = "https://github.com/chroma-core/chroma" "Bug Tracker" = "https://github.com/chroma-core/chroma/issues" @@ -56,3 +60,6 @@ local_scheme="no-local-version" [tool.setuptools] packages = ["chromadb"] + +[tool.setuptools.package-data] +chromadb = ["*.yml"] diff --git a/requirements.txt b/requirements.txt index e94f7caf91f4..9a9fdcc295c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,6 @@ pypika==0.48.9 requests==2.28.1 tokenizers==0.13.2 tqdm==4.65.0 +typer>=0.9.0 typing_extensions==4.5.0 uvicorn[standard]==0.18.3 From 831c027f5cfb27cf70d846a49315070ff26f3a3c Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 12 Sep 2023 06:49:55 +0300 Subject: [PATCH 51/67] [SEC]: Bandit Scan (#1113) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Added bandit scanning for all pushes to repo ## Test plan *How are these changes tested?* Manual testing of the workflow ## Documentation Changes N/A - unless we want to start a separate security section in the main docs repo. --------- Co-authored-by: Hammad Bashir --- .github/actions/bandit-scan/Dockerfile | 7 ++++++ .github/actions/bandit-scan/action.yaml | 26 +++++++++++++++++++++ .github/actions/bandit-scan/entrypoint.sh | 13 +++++++++++ .github/workflows/python-vuln.yaml | 28 +++++++++++++++++++++++ bandit.yaml | 4 ++++ 5 files changed, 78 insertions(+) create mode 100644 .github/actions/bandit-scan/Dockerfile create mode 100644 .github/actions/bandit-scan/action.yaml create mode 100755 .github/actions/bandit-scan/entrypoint.sh create mode 100644 .github/workflows/python-vuln.yaml create mode 100644 bandit.yaml diff --git a/.github/actions/bandit-scan/Dockerfile b/.github/actions/bandit-scan/Dockerfile new file mode 100644 index 000000000000..943f04fc8f37 --- /dev/null +++ b/.github/actions/bandit-scan/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.10-alpine AS base-action + +RUN pip3 install -U setuptools pip bandit + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["sh","/entrypoint.sh"] diff --git a/.github/actions/bandit-scan/action.yaml b/.github/actions/bandit-scan/action.yaml new file mode 100644 index 000000000000..e0735450f574 --- /dev/null +++ b/.github/actions/bandit-scan/action.yaml @@ -0,0 +1,26 @@ +name: 'Bandit Scan' +description: 'This action performs a security vulnerability scan of python code using bandit library.' +inputs: + bandit-config: + description: 'Bandit configuration file' + required: false + input-dir: + description: 'Directory to scan' + required: false + default: '.' + format: + description: 'Output format (txt, csv, json, xml, yaml). Default: json' + required: false + default: 'json' + output-file: + description: "The report file to produce. Make sure to align your format with the file extension to avoid confusion." + required: false + default: "bandit-scan.json" +runs: + using: 'docker' + image: 'Dockerfile' + args: + - ${{ inputs.format }} + - ${{ inputs.bandit-config }} + - ${{ inputs.input-dir }} + - ${{ inputs.output-file }} diff --git a/.github/actions/bandit-scan/entrypoint.sh b/.github/actions/bandit-scan/entrypoint.sh new file mode 100755 index 000000000000..f52daddd781e --- /dev/null +++ b/.github/actions/bandit-scan/entrypoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash +CFG="-c $2" +if [ -z "$1" ]; then + echo "No path to scan provided" + exit 1 +fi + +if [ -z "$2" ]; then + CFG = "" +fi + +bandit -f "$1" ${CFG} -r "$3" -o "$4" +exit 0 #we want to ignore the exit code of bandit (for now) diff --git a/.github/workflows/python-vuln.yaml b/.github/workflows/python-vuln.yaml new file mode 100644 index 000000000000..8e6c33a255c3 --- /dev/null +++ b/.github/workflows/python-vuln.yaml @@ -0,0 +1,28 @@ +name: Python Vulnerability Scan +on: + push: + branches: + - '*' + - '*/**' + paths: + - chromadb/** + - clients/python/** + workflow_dispatch: +jobs: + bandit-scan: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - uses: ./.github/actions/bandit-scan/ + with: + input-dir: '.' + format: 'json' + bandit-config: 'bandit.yaml' + output-file: 'bandit-report.json' + - name: Upload Bandit Report + uses: actions/upload-artifact@v3 + with: + name: bandit-artifact + path: | + bandit-report.json diff --git a/bandit.yaml b/bandit.yaml new file mode 100644 index 000000000000..9a93633ea12a --- /dev/null +++ b/bandit.yaml @@ -0,0 +1,4 @@ +# FILE: bandit.yaml +exclude_dirs: [ 'chromadb/test', 'bin', 'build', 'build', '.git', '.venv', 'venv', 'env','.github','examples','clients/js','.vscode' ] +tests: [ ] +skips: [ ] From 6681df91bfb96191c6bbea6dc7be8d48994c66d3 Mon Sep 17 00:00:00 2001 From: Sunil Kumar Dash <47926185+sunilkumardash9@users.noreply.github.com> Date: Thu, 14 Sep 2023 10:43:18 +0530 Subject: [PATCH 52/67] Enable manual workflow trigger (#1036) ## Description of changes *Summarize the changes made by this PR.* - Added a workflow_dispatch to manually trigger test workflows - will be good for development experience --------- Signed-off-by: sunilkumardash9 --- .github/workflows/chroma-client-integration-test.yml | 3 ++- .github/workflows/chroma-integration-test.yml | 1 + .github/workflows/chroma-test.yml | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/chroma-client-integration-test.yml b/.github/workflows/chroma-client-integration-test.yml index a4e70d13baff..25788090ef2e 100644 --- a/.github/workflows/chroma-client-integration-test.yml +++ b/.github/workflows/chroma-client-integration-test.yml @@ -8,7 +8,8 @@ on: branches: - main - '**' - + workflow_dispatch: + jobs: test: timeout-minutes: 90 diff --git a/.github/workflows/chroma-integration-test.yml b/.github/workflows/chroma-integration-test.yml index 91628d6f5453..963a7b6ed634 100644 --- a/.github/workflows/chroma-integration-test.yml +++ b/.github/workflows/chroma-integration-test.yml @@ -9,6 +9,7 @@ on: branches: - main - '**' + workflow_dispatch: jobs: test: diff --git a/.github/workflows/chroma-test.yml b/.github/workflows/chroma-test.yml index e0d44c2d6471..90ff2b669409 100644 --- a/.github/workflows/chroma-test.yml +++ b/.github/workflows/chroma-test.yml @@ -9,6 +9,7 @@ on: branches: - main - '**' + workflow_dispatch: jobs: test: From d090ca6f6fb646972c3d3b6325a738a07890de51 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 15 Sep 2023 10:13:30 -0700 Subject: [PATCH 53/67] Fix broken peer OpenAI dep dependency range (#1142) https://semver.npmjs.com/ Screenshot 2023-09-13 at 3 51 28 PM Screenshot 2023-09-13 at 3 51 16 PM npm strictly checks peer dep ranges, which means the `npm install` of anything with a peer dep on Chroma was affected by this. --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index 13014226bed3..cd898e5d9c37 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -62,7 +62,7 @@ "@visheratin/web-ai-node": "^1.0.0", "@xenova/transformers": "^2.0.0", "cohere-ai": "^6.0.0", - "openai": "^3.0.0 | ^4.0.0" + "openai": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { "@visheratin/web-ai": { From a0a3c35217f2252870331dfd7b91590131fdad10 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Fri, 15 Sep 2023 13:32:41 -0700 Subject: [PATCH 54/67] bump JS to 1.5.9 (#1145) Bump to release to 1.5.9 to release https://github.com/chroma-core/chroma/pull/1142 --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index cd898e5d9c37..76077d77e284 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "chromadb", - "version": "1.5.8", + "version": "1.5.9", "description": "A JavaScript interface for chroma", "keywords": [], "author": "", From dac67e7ca82589f1a0cb484ebc5668b379339951 Mon Sep 17 00:00:00 2001 From: Leonid Ganeline Date: Mon, 18 Sep 2023 10:45:34 -0700 Subject: [PATCH 55/67] simplified ut-s (#1071) ## Description of changes - Improvements - simplified ut-s - cleaned up a typing import ## Test plan - [+] Tests passed successfully locally with `pytest` for python, `yarn test` for js ## Documentation Changes N/A --- chromadb/auth/providers.py | 2 +- chromadb/test/property/test_embeddings.py | 66 ++++++++++++----------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/chromadb/auth/providers.py b/chromadb/auth/providers.py index 3123c52a54cc..a3bb23616e28 100644 --- a/chromadb/auth/providers.py +++ b/chromadb/auth/providers.py @@ -1,6 +1,6 @@ import importlib import logging -from typing import cast, Dict, TypeVar, Any, Optional +from typing import cast, Dict, TypeVar, Any import requests from overrides import override diff --git a/chromadb/test/property/test_embeddings.py b/chromadb/test/property/test_embeddings.py index 5b11f378b8d3..0e402cca1a81 100644 --- a/chromadb/test/property/test_embeddings.py +++ b/chromadb/test/property/test_embeddings.py @@ -365,39 +365,41 @@ def test_escape_chars_in_ids(api: API) -> None: assert coll.count() == 0 -def test_delete_empty_fails(api: API): +@pytest.mark.parametrize( + "kwargs", + [ + {}, + {"ids": []}, + {"where": {}}, + {"where_document": {}}, + {"where_document": {}, "where": {}}, + ], +) +def test_delete_empty_fails(api: API, kwargs: dict): api.reset() coll = api.create_collection(name="foo") - - error_valid = ( - lambda e: "You must provide either ids, where, or where_document to delete." - in e - ) - with pytest.raises(Exception) as e: - coll.delete() - assert error_valid(str(e)) - - with pytest.raises(Exception): - coll.delete(ids=[]) - assert error_valid(str(e)) - - with pytest.raises(Exception): - coll.delete(where={}) - assert error_valid(str(e)) - - with pytest.raises(Exception): - coll.delete(where_document={}) - assert error_valid(str(e)) - - with pytest.raises(Exception): - coll.delete(where_document={}, where={}) - assert error_valid(str(e)) - + coll.delete(**kwargs) + assert "You must provide either ids, where, or where_document to delete." in str(e) + + +@pytest.mark.parametrize( + "kwargs", + [ + {"ids": ["foo"]}, + {"where": {"foo": "bar"}}, + {"where_document": {"$contains": "bar"}}, + {"ids": ["foo"], "where": {"foo": "bar"}}, + {"ids": ["foo"], "where_document": {"$contains": "bar"}}, + { + "ids": ["foo"], + "where": {"foo": "bar"}, + "where_document": {"$contains": "bar"}, + }, + ], +) +def test_delete_success(api: API, kwargs: dict): + api.reset() + coll = api.create_collection(name="foo") # Should not raise - coll.delete(where_document={"$contains": "bar"}) - coll.delete(where={"foo": "bar"}) - coll.delete(ids=["foo"]) - coll.delete(ids=["foo"], where={"foo": "bar"}) - coll.delete(ids=["foo"], where_document={"$contains": "bar"}) - coll.delete(ids=["foo"], where_document={"$contains": "bar"}, where={"foo": "bar"}) + coll.delete(**kwargs) From 2b434b826642a4095e43367abd0a90b6c2e0b0e5 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Mon, 18 Sep 2023 21:30:25 +0300 Subject: [PATCH 56/67] [ENH]: JS Client Static Token support (#1114) Refs: #1083 ## Description of changes *Summarize the changes made by this PR.* - New functionality - JS Client now supports Authorization, and X-Chroma-Token auths supported - Tests and integration tests updated ## Test plan *How are these changes tested?* - [x] Tests pass locally `yarn test` for js ## Documentation Changes TBD --- bin/integration-test | 59 +++++++++++----- clients/js/package.json | 17 +++-- clients/js/src/auth.ts | 97 ++++++++++++++++++++++++++- clients/js/test/auth.basic.test.ts | 10 +-- clients/js/test/auth.token.test.ts | 59 ++++++++++++++++ clients/js/test/initClientWithAuth.ts | 13 +++- docker-compose.test-auth.yml | 8 ++- 7 files changed, 228 insertions(+), 35 deletions(-) create mode 100644 clients/js/test/auth.token.test.ts diff --git a/bin/integration-test b/bin/integration-test index e91e45e93c58..54b4e387e086 100755 --- a/bin/integration-test +++ b/bin/integration-test @@ -9,15 +9,38 @@ function cleanup { rm server.htpasswd .chroma_env } -function setup_basic_auth { - # Generate htpasswd file - docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin admin > server.htpasswd - # Create .chroma_env file - cat < .chroma_env +function setup_auth { + local auth_type="$1" + case "$auth_type" in + basic) + docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin admin > server.htpasswd + cat < .chroma_env CHROMA_SERVER_AUTH_CREDENTIALS_FILE="/chroma/server.htpasswd" -CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER='chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider' -CHROMA_SERVER_AUTH_PROVIDER='chromadb.auth.basic.BasicAuthServerProvider' +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER="chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider" +CHROMA_SERVER_AUTH_PROVIDER="chromadb.auth.basic.BasicAuthServerProvider" EOF + ;; + token) + cat < .chroma_env +CHROMA_SERVER_AUTH_CREDENTIALS="test-token" +CHROMA_SERVER_AUTH_TOKEN_TRANSPORT_HEADER="AUTHORIZATION" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER="chromadb.auth.token.TokenConfigServerAuthCredentialsProvider" +CHROMA_SERVER_AUTH_PROVIDER="chromadb.auth.token.TokenAuthServerProvider" +EOF + ;; + xtoken) + cat < .chroma_env +CHROMA_SERVER_AUTH_CREDENTIALS="test-token" +CHROMA_SERVER_AUTH_TOKEN_TRANSPORT_HEADER="X_CHROMA_TOKEN" +CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER="chromadb.auth.token.TokenConfigServerAuthCredentialsProvider" +CHROMA_SERVER_AUTH_PROVIDER="chromadb.auth.token.TokenAuthServerProvider" +EOF + ;; + *) + echo "Unknown auth type: $auth_type" + exit 1 + ;; + esac } trap cleanup EXIT @@ -28,19 +51,21 @@ export CHROMA_INTEGRATION_TEST_ONLY=1 export CHROMA_API_IMPL=chromadb.api.fastapi.FastAPI export CHROMA_SERVER_HOST=localhost export CHROMA_SERVER_HTTP_PORT=8000 - -echo testing: python -m pytest "$@" -python -m pytest "$@" +# +#echo testing: python -m pytest "$@" +#python -m pytest "$@" cd clients/js yarn yarn test:run docker compose down cd ../.. -echo "Testing auth" -setup_basic_auth #this is specific to the auth type, later on we'll have other auth types -cd clients/js -# Start docker compose - this should be auth agnostic -docker compose --env-file ../../.chroma_env -f ../../docker-compose.test-auth.yml up --build -d -yarn test:run-auth -cd ../.. +for auth_type in basic token xtoken; do + echo "Testing $auth_type auth" + setup_auth "$auth_type" + cd clients/js + docker compose --env-file ../../.chroma_env -f ../../docker-compose.test-auth.yml up --build -d + yarn test:run-auth-"$auth_type" + cd ../.. + docker compose down +done diff --git a/clients/js/package.json b/clients/js/package.json index 76077d77e284..25d3d90b94c0 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -33,15 +33,22 @@ "testnoauth": "run-s db:clean db:run test:runfull db:clean", "testauth": "run-s db:cleanauth db:run-auth test:runfull-authonly db:cleanauth", "test:set-port": "cross-env URL=localhost:8001", - "test:run": "jest --runInBand --testPathIgnorePatterns=test/auth.basic.test.ts", - "test:run-auth": "jest --runInBand --testPathPattern=test/auth.basic.test.ts", - "test:runfull": "PORT=8001 jest --runInBand --testPathIgnorePatterns=test/auth.basic.test.ts", - "test:runfull-authonly": "PORT=8001 jest --runInBand --testPathPattern=test/auth.basic.test.ts", + "test:run": "jest --runInBand --testPathIgnorePatterns=test/auth.*.test.ts", + "test:run-auth-basic": "jest --runInBand --testPathPattern=test/auth.basic.test.ts", + "test:run-auth-token": "jest --runInBand --testPathPattern=test/auth.token.test.ts", + "test:run-auth-xtoken": "XTOKEN_TEST=true jest --runInBand --testPathPattern=test/auth.token.test.ts", + "test:runfull": "PORT=8001 jest --runInBand --testPathIgnorePatterns=test/auth.*.test.ts", + "test:runfull-authonly": "run-s db:run-auth-basic test:runfull-authonly-basic db:clean db:run-auth-token test:runfull-authonly-token db:clean db:run-auth-xtoken test:runfull-authonly-xtoken db:clean", + "test:runfull-authonly-basic": "PORT=8001 jest --runInBand --testPathPattern=test/auth.basic.test.ts", + "test:runfull-authonly-token": "PORT=8001 jest --runInBand --testPathPattern=test/auth.token.test.ts", + "test:runfull-authonly-xtoken": "PORT=8001 XTOKEN_TEST=true jest --runInBand --testPathPattern=test/auth.token.test.ts", "test:update": "run-s db:clean db:run && jest --runInBand --updateSnapshot && run-s db:clean", "db:clean": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml down --volumes", "db:cleanauth": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test-auth.yml down --volumes", "db:run": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test.yml up --detach && sleep 5", - "db:run-auth": "cd ../.. && CHROMA_PORT=8001 docker-compose -f docker-compose.test-auth.yml up --detach && sleep 5", + "db:run-auth-basic": "cd ../.. && docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin admin > server.htpasswd && echo \"CHROMA_SERVER_AUTH_CREDENTIALS_FILE=/chroma/server.htpasswd\\nCHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider\\nCHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.basic.BasicAuthServerProvider\\nCHROMA_PORT=8001\" > .chroma_env && docker-compose -f docker-compose.test-auth.yml --env-file ./.chroma_env up --detach && sleep 5", + "db:run-auth-token": "cd ../.. && echo \"CHROMA_SERVER_AUTH_CREDENTIALS=test-token\nCHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.token.TokenConfigServerAuthCredentialsProvider\nCHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.token.TokenAuthServerProvider\\nCHROMA_PORT=8001\" > .chroma_env && docker-compose -f docker-compose.test-auth.yml --env-file ./.chroma_env up --detach && sleep 5", + "db:run-auth-xtoken": "cd ../.. && echo \"CHROMA_SERVER_AUTH_TOKEN_TRANSPORT_HEADER=X_CHROMA_TOKEN\nCHROMA_SERVER_AUTH_CREDENTIALS=test-token\nCHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.token.TokenConfigServerAuthCredentialsProvider\nCHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.token.TokenAuthServerProvider\\nCHROMA_PORT=8001\" > .chroma_env && docker-compose -f docker-compose.test-auth.yml --env-file ./.chroma_env up --detach && sleep 5", "clean": "rimraf dist", "build": "run-s clean build:*", "build:main": "tsc -p tsconfig.json", diff --git a/clients/js/src/auth.ts b/clients/js/src/auth.ts index e7626988f5d8..4f833f97d617 100644 --- a/clients/js/src/auth.ts +++ b/clients/js/src/auth.ts @@ -118,7 +118,10 @@ class BasicAuthClientAuthProvider implements ClientAuthProvider { * @throws {Error} If neither credentials provider or text credentials are supplied. */ - constructor(options: { textCredentials: any; credentialsProvider: ClientAuthCredentialsProvider | undefined }) { + constructor(options: { + textCredentials: any; + credentialsProvider: ClientAuthCredentialsProvider | undefined + }) { if (!options.credentialsProvider && !options.textCredentials) { throw new Error("Either credentials provider or text credentials must be supplied."); } @@ -130,6 +133,85 @@ class BasicAuthClientAuthProvider implements ClientAuthProvider { } } +class TokenAuthCredentials implements AbstractCredentials { + private readonly credentials: SecretStr; + + constructor(_creds: string) { + this.credentials = new SecretStr(_creds) + } + + getCredentials(): SecretStr { + return this.credentials; + } +} + +export class TokenCredentialsProvider implements ClientAuthCredentialsProvider { + private readonly credentials: TokenAuthCredentials; + + constructor(_creds: string | undefined) { + if (_creds === undefined && !process.env.CHROMA_CLIENT_AUTH_CREDENTIALS) throw new Error("Credentials must be supplied via environment variable (CHROMA_CLIENT_AUTH_CREDENTIALS) or passed in as configuration."); + this.credentials = new TokenAuthCredentials((_creds ?? process.env.CHROMA_CLIENT_AUTH_CREDENTIALS) as string); + } + + getCredentials(): TokenAuthCredentials { + return this.credentials; + } +} + +export class TokenClientAuthProvider implements ClientAuthProvider { + private readonly credentialsProvider: ClientAuthCredentialsProvider; + private readonly providerOptions: { headerType: TokenHeaderType }; + + constructor(options: { + textCredentials: any; + credentialsProvider: ClientAuthCredentialsProvider | undefined, + providerOptions?: { headerType: TokenHeaderType } + }) { + if (!options.credentialsProvider && !options.textCredentials) { + throw new Error("Either credentials provider or text credentials must be supplied."); + } + if (options.providerOptions === undefined || !options.providerOptions.hasOwnProperty("headerType")) { + this.providerOptions = {headerType: "AUTHORIZATION"}; + } else { + this.providerOptions = {headerType: options.providerOptions.headerType}; + } + this.credentialsProvider = options.credentialsProvider || new TokenCredentialsProvider(options.textCredentials); + } + + authenticate(): ClientAuthResponse { + return new TokenClientAuthResponse(this.credentialsProvider.getCredentials(), this.providerOptions.headerType); + } + +} + + +type TokenHeaderType = 'AUTHORIZATION' | 'X_CHROMA_TOKEN'; + +const TokenHeader: Record { key: string; value: string; }> = { + AUTHORIZATION: (value: string) => ({key: "Authorization", value: `Bearer ${value}`}), + X_CHROMA_TOKEN: (value: string) => ({key: "X-Chroma-Token", value: value}) +} + +class TokenClientAuthResponse implements ClientAuthResponse { + constructor(private readonly credentials: TokenAuthCredentials, private readonly headerType: TokenHeaderType = 'AUTHORIZATION') { + } + + getAuthInfo(): { key: string; value: string } { + if (this.headerType === 'AUTHORIZATION') { + return TokenHeader.AUTHORIZATION(this.credentials.getCredentials().getSecret()); + } else if (this.headerType === 'X_CHROMA_TOKEN') { + return TokenHeader.X_CHROMA_TOKEN(this.credentials.getCredentials().getSecret()); + } else { + throw new Error("Invalid header type: " + this.headerType + ". Valid types are: " + Object.keys(TokenHeader).join(", ")); + } + } + + getAuthInfoType(): AuthInfoType { + return AuthInfoType.HEADER; + } +} + + export class IsomorphicFetchClientAuthProtocolAdapter implements ClientAuthProtocolAdapter { authProvider: ClientAuthProvider | undefined; wrapperApi: DefaultApi | undefined; @@ -144,7 +226,17 @@ export class IsomorphicFetchClientAuthProtocolAdapter implements ClientAuthProto switch (authConfiguration.provider) { case "basic": - this.authProvider = new BasicAuthClientAuthProvider({textCredentials: authConfiguration.credentials, credentialsProvider: authConfiguration.credentialsProvider}); + this.authProvider = new BasicAuthClientAuthProvider({ + textCredentials: authConfiguration.credentials, + credentialsProvider: authConfiguration.credentialsProvider + }); + break; + case "token": + this.authProvider = new TokenClientAuthProvider({ + textCredentials: authConfiguration.credentials, + credentialsProvider: authConfiguration.credentialsProvider, + providerOptions: authConfiguration.providerOptions + }); break; default: this.authProvider = undefined; @@ -225,4 +317,5 @@ export type AuthOptions = { credentialsProvider?: ClientAuthCredentialsProvider | undefined, configProvider?: ClientAuthConfigurationProvider | undefined, credentials?: any | undefined, + providerOptions?: any | undefined } diff --git a/clients/js/test/auth.basic.test.ts b/clients/js/test/auth.basic.test.ts index a698e02c2da6..6bbcf2300876 100644 --- a/clients/js/test/auth.basic.test.ts +++ b/clients/js/test/auth.basic.test.ts @@ -1,6 +1,6 @@ import {expect, test} from "@jest/globals"; import {ChromaClient} from "../src/ChromaClient"; -import chroma from "./initClientWithAuth"; +import {chromaBasic} from "./initClientWithAuth"; import chromaNoAuth from "./initClient"; test("it should get the version without auth needed", async () => { @@ -22,12 +22,12 @@ test("it should raise error when non authenticated", async () => { }); test('it should list collections', async () => { - await chroma.reset() - let collections = await chroma.listCollections() + await chromaBasic.reset() + let collections = await chromaBasic.listCollections() expect(collections).toBeDefined() expect(collections).toBeInstanceOf(Array) expect(collections.length).toBe(0) - const collection = await chroma.createCollection({name: "test"}); - collections = await chroma.listCollections() + await chromaBasic.createCollection({name: "test"}); + collections = await chromaBasic.listCollections() expect(collections.length).toBe(1) }) diff --git a/clients/js/test/auth.token.test.ts b/clients/js/test/auth.token.test.ts new file mode 100644 index 000000000000..96612480ac87 --- /dev/null +++ b/clients/js/test/auth.token.test.ts @@ -0,0 +1,59 @@ +import {expect, test} from "@jest/globals"; +import {ChromaClient} from "../src/ChromaClient"; +import {chromaTokenDefault, chromaTokenBearer, chromaTokenXToken} from "./initClientWithAuth"; +import chromaNoAuth from "./initClient"; + +test("it should get the version without auth needed", async () => { + const version = await chromaNoAuth.version(); + expect(version).toBeDefined(); + expect(version).toMatch(/^[0-9]+\.[0-9]+\.[0-9]+$/); +}); + +test("it should get the heartbeat without auth needed", async () => { + const heartbeat = await chromaNoAuth.heartbeat(); + expect(heartbeat).toBeDefined(); + expect(heartbeat).toBeGreaterThan(0); +}); + +test("it should raise error when non authenticated", async () => { + await expect(chromaNoAuth.listCollections()).rejects.toMatchObject({ + status: 401 + }); +}); + +if (!process.env.XTOKEN_TEST) { + test('it should list collections with default token config', async () => { + await chromaTokenDefault.reset() + let collections = await chromaTokenDefault.listCollections() + expect(collections).toBeDefined() + expect(collections).toBeInstanceOf(Array) + expect(collections.length).toBe(0) + const collection = await chromaTokenDefault.createCollection({name: "test"}); + collections = await chromaTokenDefault.listCollections() + expect(collections.length).toBe(1) + }) + + test('it should list collections with explicit bearer token config', async () => { + await chromaTokenBearer.reset() + let collections = await chromaTokenBearer.listCollections() + expect(collections).toBeDefined() + expect(collections).toBeInstanceOf(Array) + expect(collections.length).toBe(0) + const collection = await chromaTokenBearer.createCollection({name: "test"}); + collections = await chromaTokenBearer.listCollections() + expect(collections.length).toBe(1) + }) +} else { + + test('it should list collections with explicit x-token token config', async () => { + await chromaTokenXToken.reset() + let collections = await chromaTokenXToken.listCollections() + expect(collections).toBeDefined() + expect(collections).toBeInstanceOf(Array) + expect(collections.length).toBe(0) + const collection = await chromaTokenXToken.createCollection({name: "test"}); + collections = await chromaTokenXToken.listCollections() + expect(collections.length).toBe(1) + }) + +} diff --git a/clients/js/test/initClientWithAuth.ts b/clients/js/test/initClientWithAuth.ts index b24c9a48d1d1..4c061d089d5c 100644 --- a/clients/js/test/initClientWithAuth.ts +++ b/clients/js/test/initClientWithAuth.ts @@ -2,6 +2,13 @@ import {ChromaClient} from "../src/ChromaClient"; const PORT = process.env.PORT || "8000"; const URL = "http://localhost:" + PORT; -const chroma = new ChromaClient({path: URL, auth: {provider: "basic", credentials: "admin:admin"}}); - -export default chroma; +export const chromaBasic = new ChromaClient({path: URL, auth: {provider: "basic", credentials: "admin:admin"}}); +export const chromaTokenDefault = new ChromaClient({path: URL, auth: {provider: "token", credentials: "test-token"}}); +export const chromaTokenBearer = new ChromaClient({ + path: URL, + auth: {provider: "token", credentials: "test-token", providerOptions: {headerType: "AUTHORIZATION"}} +}); +export const chromaTokenXToken = new ChromaClient({ + path: URL, + auth: {provider: "token", credentials: "test-token", providerOptions: {headerType: "X_CHROMA_TOKEN"}} +}); diff --git a/docker-compose.test-auth.yml b/docker-compose.test-auth.yml index 921f0749f06d..c66cfc8202bb 100644 --- a/docker-compose.test-auth.yml +++ b/docker-compose.test-auth.yml @@ -17,9 +17,11 @@ services: - ANONYMIZED_TELEMETRY=False - ALLOW_RESET=True - IS_PERSISTENT=TRUE - - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=/chroma/server.htpasswd - - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=chromadb.auth.providers.HtpasswdFileServerAuthCredentialsProvider - - CHROMA_SERVER_AUTH_PROVIDER=chromadb.auth.basic.BasicAuthServerProvider + - CHROMA_SERVER_AUTH_CREDENTIALS_FILE=${CHROMA_SERVER_AUTH_CREDENTIALS_FILE} + - CHROMA_SERVER_AUTH_CREDENTIALS=${CHROMA_SERVER_AUTH_CREDENTIALS} + - CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER=${CHROMA_SERVER_AUTH_CREDENTIALS_PROVIDER} + - CHROMA_SERVER_AUTH_PROVIDER=${CHROMA_SERVER_AUTH_PROVIDER} + - CHROMA_SERVER_AUTH_TOKEN_TRANSPORT_HEADER=${CHROMA_SERVER_AUTH_TOKEN_TRANSPORT_HEADER} ports: - ${CHROMA_PORT}:8000 networks: From 82b9c830f70e211247da03ec82bfcabaf36154a6 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Mon, 18 Sep 2023 23:00:57 +0300 Subject: [PATCH 57/67] [ENH]: CIP-5: Large Batch Handling Improvements Proposal (#1077) - Including only CIP for review. Refs: #1049 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - New proposal to handle large batches of embeddings gracefully ## Test plan *How are these changes tested?* - [ ] Tests pass locally with `pytest` for python, `yarn test` for js ## Documentation Changes TBD --------- Signed-off-by: sunilkumardash9 Co-authored-by: Sunil Kumar Dash <47926185+sunilkumardash9@users.noreply.github.com> --- chromadb/api/__init__.py | 7 ++ chromadb/api/fastapi.py | 81 +++++++++++-------- chromadb/api/segment.py | 46 +++++++++-- chromadb/api/types.py | 12 ++- chromadb/server/fastapi/__init__.py | 8 ++ chromadb/test/property/test_add.py | 81 ++++++++++++++++++- chromadb/test/test_api.py | 18 +++++ chromadb/utils/batch_utils.py | 34 ++++++++ ...CIP_5_Large_Batch_Handling_Improvements.md | 59 ++++++++++++++ 9 files changed, 302 insertions(+), 44 deletions(-) create mode 100644 chromadb/utils/batch_utils.py create mode 100644 docs/CIP_5_Large_Batch_Handling_Improvements.md diff --git a/chromadb/api/__init__.py b/chromadb/api/__init__.py index c1c83580e9e5..50f2ff1ecef5 100644 --- a/chromadb/api/__init__.py +++ b/chromadb/api/__init__.py @@ -378,3 +378,10 @@ def get_settings(self) -> Settings: """ pass + + @property + @abstractmethod + def max_batch_size(self) -> int: + """Return the maximum number of records that can be submitted in a single call + to submit_embeddings.""" + pass diff --git a/chromadb/api/fastapi.py b/chromadb/api/fastapi.py index c08458a2fcb9..2ddd537ebffd 100644 --- a/chromadb/api/fastapi.py +++ b/chromadb/api/fastapi.py @@ -1,6 +1,6 @@ import json import logging -from typing import Optional, cast +from typing import Optional, cast, Tuple from typing import Sequence from uuid import UUID @@ -23,6 +23,7 @@ GetResult, QueryResult, CollectionMetadata, + validate_batch, ) from chromadb.auth import ( ClientAuthProvider, @@ -38,6 +39,7 @@ class FastAPI(API): _settings: Settings + _max_batch_size: int = -1 @staticmethod def _validate_host(host: str) -> None: @@ -296,6 +298,29 @@ def _delete( raise_chroma_error(resp) return cast(IDs, resp.json()) + def _submit_batch( + self, + batch: Tuple[ + IDs, Optional[Embeddings], Optional[Metadatas], Optional[Documents] + ], + url: str, + ) -> requests.Response: + """ + Submits a batch of embeddings to the database + """ + resp = self._session.post( + self._api_url + url, + data=json.dumps( + { + "ids": batch[0], + "embeddings": batch[1], + "metadatas": batch[2], + "documents": batch[3], + } + ), + ) + return resp + @override def _add( self, @@ -309,18 +334,9 @@ def _add( Adds a batch of embeddings to the database - pass in column oriented data lists """ - resp = self._session.post( - self._api_url + "/collections/" + str(collection_id) + "/add", - data=json.dumps( - { - "ids": ids, - "embeddings": embeddings, - "metadatas": metadatas, - "documents": documents, - } - ), - ) - + batch = (ids, embeddings, metadatas, documents) + validate_batch(batch, {"max_batch_size": self.max_batch_size}) + resp = self._submit_batch(batch, "/collections/" + str(collection_id) + "/add") raise_chroma_error(resp) return True @@ -337,18 +353,11 @@ def _update( Updates a batch of embeddings in the database - pass in column oriented data lists """ - resp = self._session.post( - self._api_url + "/collections/" + str(collection_id) + "/update", - data=json.dumps( - { - "ids": ids, - "embeddings": embeddings, - "metadatas": metadatas, - "documents": documents, - } - ), + batch = (ids, embeddings, metadatas, documents) + validate_batch(batch, {"max_batch_size": self.max_batch_size}) + resp = self._submit_batch( + batch, "/collections/" + str(collection_id) + "/update" ) - resp.raise_for_status() return True @@ -365,18 +374,11 @@ def _upsert( Upserts a batch of embeddings in the database - pass in column oriented data lists """ - resp = self._session.post( - self._api_url + "/collections/" + str(collection_id) + "/upsert", - data=json.dumps( - { - "ids": ids, - "embeddings": embeddings, - "metadatas": metadatas, - "documents": documents, - } - ), + batch = (ids, embeddings, metadatas, documents) + validate_batch(batch, {"max_batch_size": self.max_batch_size}) + resp = self._submit_batch( + batch, "/collections/" + str(collection_id) + "/upsert" ) - resp.raise_for_status() return True @@ -434,6 +436,15 @@ def get_settings(self) -> Settings: """Returns the settings of the client""" return self._settings + @property + @override + def max_batch_size(self) -> int: + if self._max_batch_size == -1: + resp = self._session.get(self._api_url + "/pre-flight-checks") + raise_chroma_error(resp) + self._max_batch_size = cast(int, resp.json()["max_batch_size"]) + return self._max_batch_size + def raise_chroma_error(resp: requests.Response) -> None: """Raises an error if the response is not ok, using a ChromaError if possible""" diff --git a/chromadb/api/segment.py b/chromadb/api/segment.py index 7f7712922fa0..dd846891b286 100644 --- a/chromadb/api/segment.py +++ b/chromadb/api/segment.py @@ -26,6 +26,7 @@ validate_update_metadata, validate_where, validate_where_document, + validate_batch, ) from chromadb.telemetry.events import CollectionAddEvent, CollectionDeleteEvent @@ -38,6 +39,7 @@ import logging import re + logger = logging.getLogger(__name__) @@ -241,9 +243,18 @@ def _add( ) -> bool: coll = self._get_collection(collection_id) self._manager.hint_use_collection(collection_id, t.Operation.ADD) - + validate_batch( + (ids, embeddings, metadatas, documents), + {"max_batch_size": self.max_batch_size}, + ) records_to_submit = [] - for r in _records(t.Operation.ADD, ids, embeddings, metadatas, documents): + for r in _records( + t.Operation.ADD, + ids=ids, + embeddings=embeddings, + metadatas=metadatas, + documents=documents, + ): self._validate_embedding_record(coll, r) records_to_submit.append(r) self._producer.submit_embeddings(coll["topic"], records_to_submit) @@ -262,9 +273,18 @@ def _update( ) -> bool: coll = self._get_collection(collection_id) self._manager.hint_use_collection(collection_id, t.Operation.UPDATE) - + validate_batch( + (ids, embeddings, metadatas, documents), + {"max_batch_size": self.max_batch_size}, + ) records_to_submit = [] - for r in _records(t.Operation.UPDATE, ids, embeddings, metadatas, documents): + for r in _records( + t.Operation.UPDATE, + ids=ids, + embeddings=embeddings, + metadatas=metadatas, + documents=documents, + ): self._validate_embedding_record(coll, r) records_to_submit.append(r) self._producer.submit_embeddings(coll["topic"], records_to_submit) @@ -282,9 +302,18 @@ def _upsert( ) -> bool: coll = self._get_collection(collection_id) self._manager.hint_use_collection(collection_id, t.Operation.UPSERT) - + validate_batch( + (ids, embeddings, metadatas, documents), + {"max_batch_size": self.max_batch_size}, + ) records_to_submit = [] - for r in _records(t.Operation.UPSERT, ids, embeddings, metadatas, documents): + for r in _records( + t.Operation.UPSERT, + ids=ids, + embeddings=embeddings, + metadatas=metadatas, + documents=documents, + ): self._validate_embedding_record(coll, r) records_to_submit.append(r) self._producer.submit_embeddings(coll["topic"], records_to_submit) @@ -524,6 +553,11 @@ def reset(self) -> bool: def get_settings(self) -> Settings: return self._settings + @property + @override + def max_batch_size(self) -> int: + return self._producer.max_batch_size + def _topic(self, collection_id: UUID) -> str: return f"persistent://{self._tenant_id}/{self._topic_ns}/{collection_id}" diff --git a/chromadb/api/types.py b/chromadb/api/types.py index 7979dba624e8..017e356ffac7 100644 --- a/chromadb/api/types.py +++ b/chromadb/api/types.py @@ -1,4 +1,4 @@ -from typing import Optional, Union, Sequence, TypeVar, List, Dict, Any +from typing import Optional, Union, Sequence, TypeVar, List, Dict, Any, Tuple from typing_extensions import Literal, TypedDict, Protocol import chromadb.errors as errors from chromadb.types import ( @@ -367,3 +367,13 @@ def validate_embeddings(embeddings: Embeddings) -> Embeddings: f"Expected each value in the embedding to be a int or float, got {embeddings}" ) return embeddings + + +def validate_batch( + batch: Tuple[IDs, Optional[Embeddings], Optional[Metadatas], Optional[Documents]], + limits: Dict[str, Any], +) -> None: + if len(batch[0]) > limits["max_batch_size"]: + raise ValueError( + f"Batch size {len(batch[0])} exceeds maximum batch size {limits['max_batch_size']}" + ) diff --git a/chromadb/server/fastapi/__init__.py b/chromadb/server/fastapi/__init__.py index d8e43c51081e..e92d16d63ba4 100644 --- a/chromadb/server/fastapi/__init__.py +++ b/chromadb/server/fastapi/__init__.py @@ -126,6 +126,9 @@ def __init__(self, settings: Settings): self.router.add_api_route("/api/v1/reset", self.reset, methods=["POST"]) self.router.add_api_route("/api/v1/version", self.version, methods=["GET"]) self.router.add_api_route("/api/v1/heartbeat", self.heartbeat, methods=["GET"]) + self.router.add_api_route( + "/api/v1/pre-flight-checks", self.pre_flight_checks, methods=["GET"] + ) self.router.add_api_route( "/api/v1/collections", @@ -312,3 +315,8 @@ def get_nearest_neighbors( include=query.include, ) return nnresult + + def pre_flight_checks(self) -> Dict[str, Any]: + return { + "max_batch_size": self._api.max_batch_size, + } diff --git a/chromadb/test/property/test_add.py b/chromadb/test/property/test_add.py index 602df2fa81b0..1980ed2a9d91 100644 --- a/chromadb/test/property/test_add.py +++ b/chromadb/test/property/test_add.py @@ -1,11 +1,15 @@ -from typing import cast +import random +import uuid +from random import randint +from typing import cast, List, Any, Dict import pytest import hypothesis.strategies as st from hypothesis import given, settings from chromadb.api import API -from chromadb.api.types import Embeddings +from chromadb.api.types import Embeddings, Metadatas import chromadb.test.property.strategies as strategies import chromadb.test.property.invariants as invariants +from chromadb.utils.batch_utils import create_batches collection_st = st.shared(strategies.collections(with_hnsw_params=True), key="coll") @@ -44,6 +48,79 @@ def test_add( ) +def create_large_recordset( + min_size: int = 45000, + max_size: int = 50000, +) -> strategies.RecordSet: + size = randint(min_size, max_size) + + ids = [str(uuid.uuid4()) for _ in range(size)] + metadatas = [{"some_key": f"{i}"} for i in range(size)] + documents = [f"Document {i}" for i in range(size)] + embeddings = [[1, 2, 3] for _ in range(size)] + record_set: Dict[str, List[Any]] = { + "ids": ids, + "embeddings": cast(Embeddings, embeddings), + "metadatas": metadatas, + "documents": documents, + } + return record_set + + +@given(collection=collection_st) +@settings(deadline=None, max_examples=1) +def test_add_large(api: API, collection: strategies.Collection) -> None: + api.reset() + record_set = create_large_recordset( + min_size=api.max_batch_size, + max_size=api.max_batch_size + int(api.max_batch_size * random.random()), + ) + coll = api.create_collection( + name=collection.name, + metadata=collection.metadata, + embedding_function=collection.embedding_function, + ) + normalized_record_set = invariants.wrap_all(record_set) + + if not invariants.is_metadata_valid(normalized_record_set): + with pytest.raises(Exception): + coll.add(**normalized_record_set) + return + for batch in create_batches( + api=api, + ids=cast(List[str], record_set["ids"]), + embeddings=cast(Embeddings, record_set["embeddings"]), + metadatas=cast(Metadatas, record_set["metadatas"]), + documents=cast(List[str], record_set["documents"]), + ): + coll.add(*batch) + invariants.count(coll, cast(strategies.RecordSet, normalized_record_set)) + + +@given(collection=collection_st) +@settings(deadline=None, max_examples=1) +def test_add_large_exceeding(api: API, collection: strategies.Collection) -> None: + api.reset() + record_set = create_large_recordset( + min_size=api.max_batch_size, + max_size=api.max_batch_size + int(api.max_batch_size * random.random()), + ) + coll = api.create_collection( + name=collection.name, + metadata=collection.metadata, + embedding_function=collection.embedding_function, + ) + normalized_record_set = invariants.wrap_all(record_set) + + if not invariants.is_metadata_valid(normalized_record_set): + with pytest.raises(Exception): + coll.add(**normalized_record_set) + return + with pytest.raises(Exception) as e: + coll.add(**record_set) + assert "exceeds maximum batch size" in str(e.value) + + # TODO: This test fails right now because the ids are not sorted by the input order @pytest.mark.xfail( reason="This is expected to fail right now. We should change the API to sort the \ diff --git a/chromadb/test/test_api.py b/chromadb/test/test_api.py index 0583d6eede74..8a12a1d97354 100644 --- a/chromadb/test/test_api.py +++ b/chromadb/test/test_api.py @@ -1,6 +1,8 @@ # type: ignore +import requests import chromadb +from chromadb.api.fastapi import FastAPI from chromadb.api.types import QueryResult from chromadb.config import Settings import chromadb.server.fastapi @@ -164,6 +166,22 @@ def test_heartbeat(api): assert heartbeat > datetime.now() - timedelta(seconds=10) +def test_max_batch_size(api): + print(api) + batch_size = api.max_batch_size + assert batch_size > 0 + + +def test_pre_flight_checks(api): + if not isinstance(api, FastAPI): + pytest.skip("Not a FastAPI instance") + + resp = requests.get(f"{api._api_url}/pre-flight-checks") + assert resp.status_code == 200 + assert resp.json() is not None + assert "max_batch_size" in resp.json().keys() + + batch_records = { "embeddings": [[1.1, 2.3, 3.2], [1.2, 2.24, 3.2]], "ids": ["https://example.com/1", "https://example.com/2"], diff --git a/chromadb/utils/batch_utils.py b/chromadb/utils/batch_utils.py new file mode 100644 index 000000000000..c8c1ac1e4761 --- /dev/null +++ b/chromadb/utils/batch_utils.py @@ -0,0 +1,34 @@ +from typing import Optional, Tuple, List +from chromadb.api import API +from chromadb.api.types import ( + Documents, + Embeddings, + IDs, + Metadatas, +) + + +def create_batches( + api: API, + ids: IDs, + embeddings: Optional[Embeddings] = None, + metadatas: Optional[Metadatas] = None, + documents: Optional[Documents] = None, +) -> List[Tuple[IDs, Embeddings, Optional[Metadatas], Optional[Documents]]]: + _batches: List[ + Tuple[IDs, Embeddings, Optional[Metadatas], Optional[Documents]] + ] = [] + if len(ids) > api.max_batch_size: + # create split batches + for i in range(0, len(ids), api.max_batch_size): + _batches.append( + ( # type: ignore + ids[i : i + api.max_batch_size], + embeddings[i : i + api.max_batch_size] if embeddings else None, + metadatas[i : i + api.max_batch_size] if metadatas else None, + documents[i : i + api.max_batch_size] if documents else None, + ) + ) + else: + _batches.append((ids, embeddings, metadatas, documents)) # type: ignore + return _batches diff --git a/docs/CIP_5_Large_Batch_Handling_Improvements.md b/docs/CIP_5_Large_Batch_Handling_Improvements.md new file mode 100644 index 000000000000..9b03d080f0f8 --- /dev/null +++ b/docs/CIP_5_Large_Batch_Handling_Improvements.md @@ -0,0 +1,59 @@ +# CIP-5: Large Batch Handling Improvements Proposal + +## Status + +Current Status: `Under Discussion` + +## **Motivation** + +As users start putting Chroma in its paces and storing ever-increasing datasets, we must ensure that errors +related to significant and potentially expensive batches are handled gracefully. This CIP proposes to add a new +setting, `max_batch_size` API, on the local segment API and use it to split large batches into smaller ones. + +## **Public Interfaces** + +The following interfaces are impacted: + +- New Server API endpoint - `/pre-flight-checks` +- New `max_batch_size` property on the `API` interface +- Updated `_add`, `_update` and `_upsert` methods on `chromadb.api.segment.SegmentAPI` +- Updated `_add`, `_update` and `_upsert` methods on `chromadb.api.fastapi.FastAPI` +- New utility library `batch_utils.py` +- New exception raised when batch size exceeds `max_batch_size` + +## **Proposed Changes** + +We propose the following changes: + +- The new `max_batch_size` property is now available in the `API` interface. The property relies on the + underlying `Producer` class + to fetch the actual value. The property will be implemented by both `chromadb.api.segment.SegmentAPI` + and `chromadb.api.fastapi.FastAPI` +- `chromadb.api.segment.SegmentAPI` will implement the `max_batch_size` property by fetching the value from the + `Producer` class. +- `chromadb.api.fastapi.FastAPI` will implement the `max_batch_size` by fetching it from a new `/pre-flight-checks` + endpoint on the Server. +- New `/pre-flight-checks` endpoint on the Server will return a dictionary with pre-flight checks the client must + fulfil to integrate with the server side. For now, we propose using this only for `max_batch_size`, but we can + add more checks in the future. The pre-flight checks will be only fetched once per client and cached for the duration + of the client's lifetime. +- Updated `_add`, `_update` and `_upsert` method on `chromadb.api.segment.SegmentAPI` to validate batch size. +- Updated `_add`, `_update` and `_upsert` method on `chromadb.api.fastapi.FastAPI` to validate batch size (client-side + validation) +- New utility library `batch_utils.py` will contain the logic for splitting batches into smaller ones. + +## **Compatibility, Deprecation, and Migration Plan** + +The change will be fully compatible with existing implementations. The changes will be transparent to the user. + +## **Test Plan** + +New tests: + +- Batch splitting tests for `chromadb.api.segment.SegmentAPI` +- Batch splitting tests for `chromadb.api.fastapi.FastAPI` +- Tests for `/pre-flight-checks` endpoint + +## **Rejected Alternatives** + +N/A From 9e05cb372a05b87a6c890e43ee5ebe05c0f20fe6 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 19 Sep 2023 01:56:53 +0300 Subject: [PATCH 58/67] [BUG]: Fixing broken peer deps (#1153) Refs: #1104 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Removed transformers and web-ai peer dependencies. ## Test plan *How are these changes tested?* - [ ] Manual testing - `mkdir testproject && cd testproject && npm init -y && npm link chromadb && npm add langchain` ## Documentation Changes N/A --- clients/js/package.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/clients/js/package.json b/clients/js/package.json index 25d3d90b94c0..d72c655e3f17 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -65,22 +65,10 @@ "isomorphic-fetch": "^3.0.0" }, "peerDependencies": { - "@visheratin/web-ai": "^1.0.0", - "@visheratin/web-ai-node": "^1.0.0", - "@xenova/transformers": "^2.0.0", - "cohere-ai": "^6.0.0", + "cohere-ai": "^5.1.0", "openai": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { - "@visheratin/web-ai": { - "optional": true - }, - "@visheratin/web-ai-node": { - "optional": true - }, - "@xenova/transformers": { - "optional": true - }, "cohere-ai": { "optional": true }, From 3aed7b78b2ccd54f0f9306f9ef1acad174d12b3c Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 19 Sep 2023 03:43:13 +0300 Subject: [PATCH 59/67] [BUG]: Fixed BF index overflow issue with subsequent delete (#1150) Refs: #989 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - When the BF index overflows (batch_size upon insertion of large batch it is cleared, if a subsequent delete request comes to delete Ids which were in the cleared BF index a warning is raised for non-existent embedding. The issue was resolved by separately checking if BF the record exists in the BF index and conditionally execute the BF removal ## Test plan *How are these changes tested?* - [x] Tests pass locally with `pytest` for python ## Documentation Changes N/A --- chromadb/segment/impl/vector/local_persistent_hnsw.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chromadb/segment/impl/vector/local_persistent_hnsw.py b/chromadb/segment/impl/vector/local_persistent_hnsw.py index a0b52acd07a0..6e1df7b1f1f4 100644 --- a/chromadb/segment/impl/vector/local_persistent_hnsw.py +++ b/chromadb/segment/impl/vector/local_persistent_hnsw.py @@ -225,11 +225,13 @@ def _write_records(self, records: Sequence[EmbeddingRecord]) -> None: exists_in_index = self._id_to_label.get( id, None ) is not None or self._brute_force_index.has_id(id) + exists_in_bf_index = self._brute_force_index.has_id(id) if op == Operation.DELETE: if exists_in_index: self._curr_batch.apply(record) - self._brute_force_index.delete([record]) + if exists_in_bf_index: + self._brute_force_index.delete([record]) else: logger.warning(f"Delete of nonexisting embedding ID: {id}") From b930a862bad2cdaa25a8c888d04e97f33351d412 Mon Sep 17 00:00:00 2001 From: Jeff Huber Date: Mon, 18 Sep 2023 21:18:25 -0700 Subject: [PATCH 60/67] js 1.5.10 (#1155) Release https://github.com/chroma-core/chroma/pull/1153 --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index d72c655e3f17..55ca7a6c7aa0 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "chromadb", - "version": "1.5.9", + "version": "1.5.10", "description": "A JavaScript interface for chroma", "keywords": [], "author": "", From aa0387a6d7ba8b42009173d9b19a270a889eae22 Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Tue, 19 Sep 2023 19:14:16 +0300 Subject: [PATCH 61/67] [BUG]: Added cohere version 6.x support in peer dependencies (#1156) Refs: #1104 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Expanding Cohere version also to support 6.x This plays nice with the rest of the ecosystem ## Test plan *How are these changes tested?* - [x] `yarn test` for js ## Documentation Changes N/A --- clients/js/package.json | 2 +- clients/js/test/add.collections.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index 55ca7a6c7aa0..678353984f1e 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -65,7 +65,7 @@ "isomorphic-fetch": "^3.0.0" }, "peerDependencies": { - "cohere-ai": "^5.1.0", + "cohere-ai": "^5.0.0 || ^6.0.0", "openai": "^3.0.0 || ^4.0.0" }, "peerDependenciesMeta": { diff --git a/clients/js/test/add.collections.test.ts b/clients/js/test/add.collections.test.ts index 32f76c89a304..cb89fa8dbe06 100644 --- a/clients/js/test/add.collections.test.ts +++ b/clients/js/test/add.collections.test.ts @@ -4,6 +4,7 @@ import { DOCUMENTS, EMBEDDINGS, IDS } from './data'; import { METADATAS } from './data'; import { IncludeEnum } from "../src/types"; import {OpenAIEmbeddingFunction} from "../src/embeddings/OpenAIEmbeddingFunction"; +import {CohereEmbeddingFunction} from "../src/embeddings/CohereEmbeddingFunction"; test("it should add single embeddings to a collection", async () => { await chroma.reset(); const collection = await chroma.createCollection({ name: "test" }); @@ -57,6 +58,27 @@ if (!process.env.OPENAI_API_KEY) { }); } +if (!process.env.COHERE_API_KEY) { + test.skip("it should add Cohere embeddings", async () => { + }); +} else { + test("it should add Cohere embeddings", async () => { + await chroma.reset(); + const embedder = new CohereEmbeddingFunction({ cohere_api_key: process.env.COHERE_API_KEY || "" }) + const collection = await chroma.createCollection({ name: "test" ,embeddingFunction: embedder}); + const embeddings = await embedder.generate(DOCUMENTS); + await collection.add({ ids: IDS, embeddings: embeddings }); + const count = await collection.count(); + expect(count).toBe(3); + var res = await collection.get({ + ids: IDS, include: [ + IncludeEnum.Embeddings, + ] + }); + expect(res.embeddings).toEqual(embeddings); // reverse because of the order of the ids + }); +} + test("add documents", async () => { await chroma.reset(); const collection = await chroma.createCollection({ name: "test" }); From f3284f62b9c65334055149b46f8b7c545c6fd637 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 19 Sep 2023 09:34:03 -0700 Subject: [PATCH 62/67] [RELEASE] JS 1.5.11 (#1161) Releases Js 1.5.11 --- clients/js/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clients/js/package.json b/clients/js/package.json index 678353984f1e..b48269e6f648 100644 --- a/clients/js/package.json +++ b/clients/js/package.json @@ -1,6 +1,6 @@ { "name": "chromadb", - "version": "1.5.10", + "version": "1.5.11", "description": "A JavaScript interface for chroma", "keywords": [], "author": "", From 7d2dd011cf96b7993ad5290a99a64dcd7e1797ef Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Tue, 19 Sep 2023 09:44:15 -0700 Subject: [PATCH 63/67] Release 0.4.11 (#1162) Release 0.4.11 --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 804ff93404c2..12e189c4725b 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.10" +__version__ = "0.4.11" # Workaround to deal with Colab's old sqlite3 version try: From dffc8067dba7d9de5d89aaf282ff8c3810f1007d Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 20 Sep 2023 10:47:16 +0300 Subject: [PATCH 64/67] [BUG]: Docker entrypoint logging path (#1159) ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - Initial CLI PR (https://github.com/chroma-core/chroma/pull/1032) moved the logging config inside chromadb. If image is built with the current setup it will result in Error: Invalid value for '--log-config': Path 'log_config.yml' does not exist. ## Test plan *How are these changes tested?* Steps to reproduce (prior to this PR): - `docker build -t chroma:canary .` - `docker run --rm -it chroma:canary` ## Documentation Changes *Are all docstrings for user-facing APIs updated if required? Do we need to make documentation changes in the [docs repository](https://github.com/chroma-core/docs)?* --- bin/docker_entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/docker_entrypoint.sh b/bin/docker_entrypoint.sh index 3b0d146c70bb..ce500ee80b91 100755 --- a/bin/docker_entrypoint.sh +++ b/bin/docker_entrypoint.sh @@ -3,4 +3,4 @@ echo "Rebuilding hnsw to ensure architecture compatibility" pip install --force-reinstall --no-cache-dir chroma-hnswlib export IS_PERSISTENT=1 -uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --proxy-headers --log-config log_config.yml +uvicorn chromadb.app:app --workers 1 --host 0.0.0.0 --port 8000 --proxy-headers --log-config chromadb/log_config.yml From 020950470cb75a54e761a7f3ba6de2738a7ddc9c Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Wed, 20 Sep 2023 00:50:31 -0700 Subject: [PATCH 65/67] [RELEASE] 0.4.12 to fix Dockerfile log issue (#1165) Releasing a hotfix for #1159 which addresses #1164 which breaks the docker image --- chromadb/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chromadb/__init__.py b/chromadb/__init__.py index 12e189c4725b..0ff5244a80f7 100644 --- a/chromadb/__init__.py +++ b/chromadb/__init__.py @@ -44,7 +44,7 @@ __settings = Settings() -__version__ = "0.4.11" +__version__ = "0.4.12" # Workaround to deal with Colab's old sqlite3 version try: From 896822231e9444ebe41f7bc04047a686279ffa03 Mon Sep 17 00:00:00 2001 From: Hammad Bashir Date: Wed, 20 Sep 2023 02:03:07 -0700 Subject: [PATCH 66/67] [ENH] Pulsar Producer & Consumer (#921) ## Description of changes *Summarize the changes made by this PR.* - New functionality - Adds a basic pulsar producer, consumer and associated tests. As well as a docker compose for the distributed version of chroma. ## Test plan We added bin/cluster-test.sh, which starts pulsar and allows test_producer_consumer to run the pulsar fixture. ## Documentation Changes None required. --- .github/workflows/chroma-cluster-test.yml | 31 ++ .pre-commit-config.yaml | 2 +- bin/cluster-test.sh | 16 + chromadb/api/segment.py | 6 +- chromadb/config.py | 4 + chromadb/ingest/impl/pulsar.py | 304 ++++++++++++++++++ chromadb/ingest/impl/pulsar_admin.py | 81 +++++ chromadb/ingest/impl/utils.py | 20 ++ chromadb/proto/chroma.proto | 40 +++ chromadb/proto/chroma_pb2.py | 42 +++ chromadb/proto/chroma_pb2.pyi | 247 ++++++++++++++ chromadb/proto/convert.py | 150 +++++++++ chromadb/segment/impl/metadata/sqlite.py | 4 +- .../impl/vector/local_persistent_hnsw.py | 1 - .../test/ingest/test_producer_consumer.py | 132 +++++--- docker-compose.cluster.yml | 66 ++++ requirements_dev.txt | 2 + 17 files changed, 1105 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/chroma-cluster-test.yml create mode 100755 bin/cluster-test.sh create mode 100644 chromadb/ingest/impl/pulsar.py create mode 100644 chromadb/ingest/impl/pulsar_admin.py create mode 100644 chromadb/ingest/impl/utils.py create mode 100644 chromadb/proto/chroma.proto create mode 100644 chromadb/proto/chroma_pb2.py create mode 100644 chromadb/proto/chroma_pb2.pyi create mode 100644 chromadb/proto/convert.py create mode 100644 docker-compose.cluster.yml diff --git a/.github/workflows/chroma-cluster-test.yml b/.github/workflows/chroma-cluster-test.yml new file mode 100644 index 000000000000..5ae873aa1984 --- /dev/null +++ b/.github/workflows/chroma-cluster-test.yml @@ -0,0 +1,31 @@ +name: Chroma Cluster Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + - '**' + workflow_dispatch: + +jobs: + test: + strategy: + matrix: + python: ['3.7'] + platform: [ubuntu-latest] + testfile: ["chromadb/test/ingest/test_producer_consumer.py"] # Just this one test for now + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install test dependencies + run: python -m pip install -r requirements.txt && python -m pip install -r requirements_dev.txt + - name: Integration Test + run: bin/cluster-test.sh ${{ matrix.testfile }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b8fbca90794..5b2ed56635e4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,4 +32,4 @@ repos: hooks: - id: mypy args: [--strict, --ignore-missing-imports, --follow-imports=silent, --disable-error-code=type-abstract] - additional_dependencies: ["types-requests", "pydantic", "overrides", "hypothesis", "pytest", "pypika", "numpy"] \ No newline at end of file + additional_dependencies: ["types-requests", "pydantic", "overrides", "hypothesis", "pytest", "pypika", "numpy", "types-protobuf"] diff --git a/bin/cluster-test.sh b/bin/cluster-test.sh new file mode 100755 index 000000000000..b7255eae60a5 --- /dev/null +++ b/bin/cluster-test.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e + +function cleanup { + docker compose -f docker-compose.cluster.yml down --rmi local --volumes +} + +trap cleanup EXIT + +docker compose -f docker-compose.cluster.yml up -d --wait pulsar + +export CHROMA_CLUSTER_TEST_ONLY=1 + +echo testing: python -m pytest "$@" +python -m pytest "$@" diff --git a/chromadb/api/segment.py b/chromadb/api/segment.py index dd846891b286..00002f46d279 100644 --- a/chromadb/api/segment.py +++ b/chromadb/api/segment.py @@ -1,6 +1,7 @@ from chromadb.api import API from chromadb.config import Settings, System from chromadb.db.system import SysDB +from chromadb.ingest.impl.utils import create_topic_name from chromadb.segment import SegmentManager, MetadataReader, VectorReader from chromadb.telemetry import Telemetry from chromadb.ingest import Producer @@ -130,6 +131,9 @@ def create_collection( coll = t.Collection( id=id, name=name, metadata=metadata, topic=self._topic(id), dimension=None ) + # TODO: Topic creation right now lives in the producer but it should be moved to the coordinator, + # and the producer should just be responsible for publishing messages. Coordinator should + # be responsible for all management of topics. self._producer.create_topic(coll["topic"]) segments = self._manager.create_segments(coll) self._sysdb.create_collection(coll) @@ -559,7 +563,7 @@ def max_batch_size(self) -> int: return self._producer.max_batch_size def _topic(self, collection_id: UUID) -> str: - return f"persistent://{self._tenant_id}/{self._topic_ns}/{collection_id}" + return create_topic_name(self._tenant_id, self._topic_ns, str(collection_id)) # TODO: This could potentially cause race conditions in a distributed version of the # system, since the cache is only local. diff --git a/chromadb/config.py b/chromadb/config.py index 4cb7eac1aae4..6167193acd26 100644 --- a/chromadb/config.py +++ b/chromadb/config.py @@ -92,6 +92,10 @@ class Settings(BaseSettings): # type: ignore chroma_server_grpc_port: Optional[str] = None chroma_server_cors_allow_origins: List[str] = [] # eg ["http://localhost:3000"] + pulsar_broker_url: Optional[str] = None + pulsar_admin_port: Optional[str] = None + pulsar_broker_port: Optional[str] = None + chroma_server_auth_provider: Optional[str] = None @validator("chroma_server_auth_provider", pre=True, always=True, allow_reuse=True) diff --git a/chromadb/ingest/impl/pulsar.py b/chromadb/ingest/impl/pulsar.py new file mode 100644 index 000000000000..3f293c90580a --- /dev/null +++ b/chromadb/ingest/impl/pulsar.py @@ -0,0 +1,304 @@ +from __future__ import annotations +from collections import defaultdict +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple +import uuid +from chromadb.config import Settings, System +from chromadb.ingest import Consumer, ConsumerCallbackFn, Producer +from overrides import overrides, EnforceOverrides +from uuid import UUID +from chromadb.ingest.impl.pulsar_admin import PulsarAdmin +from chromadb.ingest.impl.utils import create_pulsar_connection_str +from chromadb.proto.convert import from_proto_submit, to_proto_submit +import chromadb.proto.chroma_pb2 as proto +from chromadb.types import SeqId, SubmitEmbeddingRecord +import pulsar +from concurrent.futures import wait, Future + +from chromadb.utils.messageid import int_to_pulsar, pulsar_to_int + + +class PulsarProducer(Producer, EnforceOverrides): + _connection_str: str + _topic_to_producer: Dict[str, pulsar.Producer] + _client: pulsar.Client + _admin: PulsarAdmin + _settings: Settings + + def __init__(self, system: System) -> None: + pulsar_host = system.settings.require("pulsar_broker_url") + pulsar_port = system.settings.require("pulsar_broker_port") + self._connection_str = create_pulsar_connection_str(pulsar_host, pulsar_port) + self._topic_to_producer = {} + self._settings = system.settings + self._admin = PulsarAdmin(system) + super().__init__(system) + + @overrides + def start(self) -> None: + self._client = pulsar.Client(self._connection_str) + super().start() + + @overrides + def stop(self) -> None: + self._client.close() + super().stop() + + @overrides + def create_topic(self, topic_name: str) -> None: + self._admin.create_topic(topic_name) + + @overrides + def delete_topic(self, topic_name: str) -> None: + self._admin.delete_topic(topic_name) + + @overrides + def submit_embedding( + self, topic_name: str, embedding: SubmitEmbeddingRecord + ) -> SeqId: + """Add an embedding record to the given topic. Returns the SeqID of the record.""" + producer = self._get_or_create_producer(topic_name) + proto_submit: proto.SubmitEmbeddingRecord = to_proto_submit(embedding) + # TODO: batch performance / async + msg_id: pulsar.MessageId = producer.send(proto_submit.SerializeToString()) + return pulsar_to_int(msg_id) + + @overrides + def submit_embeddings( + self, topic_name: str, embeddings: Sequence[SubmitEmbeddingRecord] + ) -> Sequence[SeqId]: + if not self._running: + raise RuntimeError("Component not running") + + if len(embeddings) == 0: + return [] + + if len(embeddings) > self.max_batch_size: + raise ValueError( + f""" + Cannot submit more than {self.max_batch_size:,} embeddings at once. + Please submit your embeddings in batches of size + {self.max_batch_size:,} or less. + """ + ) + + producer = self._get_or_create_producer(topic_name) + protos_to_submit = [to_proto_submit(embedding) for embedding in embeddings] + + def create_producer_callback( + future: Future[int], + ) -> Callable[[Any, pulsar.MessageId], None]: + def producer_callback(res: Any, msg_id: pulsar.MessageId) -> None: + if msg_id: + future.set_result(pulsar_to_int(msg_id)) + else: + future.set_exception( + Exception( + "Unknown error while submitting embedding in producer_callback" + ) + ) + + return producer_callback + + futures = [] + for proto_to_submit in protos_to_submit: + future: Future[int] = Future() + producer.send_async( + proto_to_submit.SerializeToString(), + callback=create_producer_callback(future), + ) + futures.append(future) + + wait(futures) + + results: List[SeqId] = [] + for future in futures: + exception = future.exception() + if exception is not None: + raise exception + results.append(future.result()) + + return results + + @property + @overrides + def max_batch_size(self) -> int: + # For now, we use 1,000 + # TODO: tune this to a reasonable value by default + return 1000 + + def _get_or_create_producer(self, topic_name: str) -> pulsar.Producer: + if topic_name not in self._topic_to_producer: + producer = self._client.create_producer(topic_name) + self._topic_to_producer[topic_name] = producer + return self._topic_to_producer[topic_name] + + @overrides + def reset_state(self) -> None: + if not self._settings.require("allow_reset"): + raise ValueError( + "Resetting the database is not allowed. Set `allow_reset` to true in the config in tests or other non-production environments where reset should be permitted." + ) + for topic_name in self._topic_to_producer: + self._admin.delete_topic(topic_name) + self._topic_to_producer = {} + super().reset_state() + + +class PulsarConsumer(Consumer, EnforceOverrides): + class PulsarSubscription: + id: UUID + topic_name: str + start: int + end: int + callback: ConsumerCallbackFn + consumer: pulsar.Consumer + + def __init__( + self, + id: UUID, + topic_name: str, + start: int, + end: int, + callback: ConsumerCallbackFn, + consumer: pulsar.Consumer, + ): + self.id = id + self.topic_name = topic_name + self.start = start + self.end = end + self.callback = callback + self.consumer = consumer + + _connection_str: str + _client: pulsar.Client + _subscriptions: Dict[str, Set[PulsarSubscription]] + _settings: Settings + + def __init__(self, system: System) -> None: + pulsar_host = system.settings.require("pulsar_broker_url") + pulsar_port = system.settings.require("pulsar_broker_port") + self._connection_str = create_pulsar_connection_str(pulsar_host, pulsar_port) + self._subscriptions = defaultdict(set) + self._settings = system.settings + super().__init__(system) + + @overrides + def start(self) -> None: + self._client = pulsar.Client(self._connection_str) + super().start() + + @overrides + def stop(self) -> None: + self._client.close() + super().stop() + + @overrides + def subscribe( + self, + topic_name: str, + consume_fn: ConsumerCallbackFn, + start: Optional[SeqId] = None, + end: Optional[SeqId] = None, + id: Optional[UUID] = None, + ) -> UUID: + """Register a function that will be called to recieve embeddings for a given + topic. The given function may be called any number of times, with any number of + records, and may be called concurrently. + + Only records between start (exclusive) and end (inclusive) SeqIDs will be + returned. If start is None, the first record returned will be the next record + generated, not including those generated before creating the subscription. If + end is None, the consumer will consume indefinitely, otherwise it will + automatically be unsubscribed when the end SeqID is reached. + + If the function throws an exception, the function may be called again with the + same or different records. + + Takes an optional UUID as a unique subscription ID. If no ID is provided, a new + ID will be generated and returned.""" + if not self._running: + raise RuntimeError("Consumer must be started before subscribing") + + subscription_id = ( + id or uuid.uuid4() + ) # TODO: this should really be created by the coordinator and stored in sysdb + + start, end = self._validate_range(start, end) + + def wrap_callback(consumer: pulsar.Consumer, message: pulsar.Message) -> None: + msg_data = message.data() + msg_id = pulsar_to_int(message.message_id()) + submit_embedding_record = proto.SubmitEmbeddingRecord() + proto.SubmitEmbeddingRecord.ParseFromString( + submit_embedding_record, msg_data + ) + embedding_record = from_proto_submit(submit_embedding_record, msg_id) + consume_fn([embedding_record]) + consumer.acknowledge(message) + if msg_id == end: + self.unsubscribe(subscription_id) + + consumer = self._client.subscribe( + topic_name, + subscription_id.hex, + message_listener=wrap_callback, + ) + + subscription = self.PulsarSubscription( + subscription_id, topic_name, start, end, consume_fn, consumer + ) + self._subscriptions[topic_name].add(subscription) + + # NOTE: For some reason the seek() method expects a shadowed MessageId type + # which resides in _msg_id. + consumer.seek(int_to_pulsar(start)._msg_id) + + return subscription_id + + def _validate_range( + self, start: Optional[SeqId], end: Optional[SeqId] + ) -> Tuple[int, int]: + """Validate and normalize the start and end SeqIDs for a subscription using this + impl.""" + start = start or pulsar_to_int(pulsar.MessageId.latest) + end = end or self.max_seqid() + if not isinstance(start, int) or not isinstance(end, int): + raise ValueError("SeqIDs must be integers") + if start >= end: + raise ValueError(f"Invalid SeqID range: {start} to {end}") + return start, end + + @overrides + def unsubscribe(self, subscription_id: UUID) -> None: + """Unregister a subscription. The consume function will no longer be invoked, + and resources associated with the subscription will be released.""" + for topic_name, subscriptions in self._subscriptions.items(): + for subscription in subscriptions: + if subscription.id == subscription_id: + subscription.consumer.close() + subscriptions.remove(subscription) + if len(subscriptions) == 0: + del self._subscriptions[topic_name] + return + + @overrides + def min_seqid(self) -> SeqId: + """Return the minimum possible SeqID in this implementation.""" + return pulsar_to_int(pulsar.MessageId.earliest) + + @overrides + def max_seqid(self) -> SeqId: + """Return the maximum possible SeqID in this implementation.""" + return 2**192 - 1 + + @overrides + def reset_state(self) -> None: + if not self._settings.require("allow_reset"): + raise ValueError( + "Resetting the database is not allowed. Set `allow_reset` to true in the config in tests or other non-production environments where reset should be permitted." + ) + for topic_name, subscriptions in self._subscriptions.items(): + for subscription in subscriptions: + subscription.consumer.close() + self._subscriptions = defaultdict(set) + super().reset_state() diff --git a/chromadb/ingest/impl/pulsar_admin.py b/chromadb/ingest/impl/pulsar_admin.py new file mode 100644 index 000000000000..e031e4a238ba --- /dev/null +++ b/chromadb/ingest/impl/pulsar_admin.py @@ -0,0 +1,81 @@ +# A thin wrapper around the pulsar admin api +import requests +from chromadb.config import System +from chromadb.ingest.impl.utils import parse_topic_name + + +class PulsarAdmin: + """A thin wrapper around the pulsar admin api, only used for interim development towards distributed chroma. + This functionality will be moved to the chroma coordinator.""" + + _connection_str: str + + def __init__(self, system: System): + pulsar_host = system.settings.require("pulsar_broker_url") + pulsar_port = system.settings.require("pulsar_admin_port") + self._connection_str = f"http://{pulsar_host}:{pulsar_port}" + + # Create the default tenant and namespace + # This is a temporary workaround until we have a proper tenant/namespace management system + self.create_tenant("default") + self.create_namespace("default", "default") + + def create_tenant(self, tenant: str) -> None: + """Make a PUT request to the admin api to create the tenant""" + + path = f"/admin/v2/tenants/{tenant}" + url = self._connection_str + path + response = requests.put( + url, json={"allowedClusters": ["standalone"], "adminRoles": []} + ) # TODO: how to manage clusters? + + if response.status_code != 204 and response.status_code != 409: + raise RuntimeError(f"Failed to create tenant {tenant}") + + def create_namespace(self, tenant: str, namespace: str) -> None: + """Make a PUT request to the admin api to create the namespace""" + + path = f"/admin/v2/namespaces/{tenant}/{namespace}" + url = self._connection_str + path + response = requests.put(url) + + if response.status_code != 204 and response.status_code != 409: + raise RuntimeError(f"Failed to create namespace {namespace}") + + def create_topic(self, topic: str) -> None: + # TODO: support non-persistent topics? + tenant, namespace, topic_name = parse_topic_name(topic) + + if tenant != "default": + raise ValueError(f"Only the default tenant is supported, got {tenant}") + if namespace != "default": + raise ValueError( + f"Only the default namespace is supported, got {namespace}" + ) + + # Make a PUT request to the admin api to create the topic + path = f"/admin/v2/persistent/{tenant}/{namespace}/{topic_name}" + url = self._connection_str + path + response = requests.put(url) + + if response.status_code != 204 and response.status_code != 409: + raise RuntimeError(f"Failed to create topic {topic_name}") + + def delete_topic(self, topic: str) -> None: + tenant, namespace, topic_name = parse_topic_name(topic) + + if tenant != "default": + raise ValueError(f"Only the default tenant is supported, got {tenant}") + if namespace != "default": + raise ValueError( + f"Only the default namespace is supported, got {namespace}" + ) + + # Make a PUT request to the admin api to delete the topic + path = f"/admin/v2/persistent/{tenant}/{namespace}/{topic_name}" + # Force delete the topic + path += "?force=true" + url = self._connection_str + path + response = requests.delete(url) + if response.status_code != 204 and response.status_code != 409: + raise RuntimeError(f"Failed to delete topic {topic_name}") diff --git a/chromadb/ingest/impl/utils.py b/chromadb/ingest/impl/utils.py new file mode 100644 index 000000000000..144384d75db5 --- /dev/null +++ b/chromadb/ingest/impl/utils.py @@ -0,0 +1,20 @@ +import re +from typing import Tuple + +topic_regex = r"persistent:\/\/(?P.+)\/(?P.+)\/(?P.+)" + + +def parse_topic_name(topic_name: str) -> Tuple[str, str, str]: + """Parse the topic name into the tenant, namespace and topic name""" + match = re.match(topic_regex, topic_name) + if not match: + raise ValueError(f"Invalid topic name: {topic_name}") + return match.group("tenant"), match.group("namespace"), match.group("topic") + + +def create_pulsar_connection_str(host: str, port: str) -> str: + return f"pulsar://{host}:{port}" + + +def create_topic_name(tenant: str, namespace: str, topic: str) -> str: + return f"persistent://{tenant}/{namespace}/{topic}" diff --git a/chromadb/proto/chroma.proto b/chromadb/proto/chroma.proto new file mode 100644 index 000000000000..7eefed74e121 --- /dev/null +++ b/chromadb/proto/chroma.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package chroma; + +enum Operation { + ADD = 0; + UPDATE = 1; + UPSERT = 2; + DELETE = 3; +} + +enum ScalarEncoding { + FLOAT32 = 0; + INT32 = 1; +} + +message Vector { + int32 dimension = 1; + bytes vector = 2; + ScalarEncoding encoding = 3; +} + +message UpdateMetadataValue { + oneof value { + string string_value = 1; + int64 int_value = 2; + double float_value = 3; + } +} + +message UpdateMetadata { + map metadata = 1; +} + +message SubmitEmbeddingRecord { + string id = 1; + optional Vector vector = 2; + optional UpdateMetadata metadata = 3; + Operation operation = 4; +} diff --git a/chromadb/proto/chroma_pb2.py b/chromadb/proto/chroma_pb2.py new file mode 100644 index 000000000000..ca8952697afb --- /dev/null +++ b/chromadb/proto/chroma_pb2.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: chromadb/proto/chroma.proto +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder + +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile( + b'\n\x1b\x63hromadb/proto/chroma.proto\x12\x06\x63hroma"U\n\x06Vector\x12\x11\n\tdimension\x18\x01 \x01(\x05\x12\x0e\n\x06vector\x18\x02 \x01(\x0c\x12(\n\x08\x65ncoding\x18\x03 \x01(\x0e\x32\x16.chroma.ScalarEncoding"b\n\x13UpdateMetadataValue\x12\x16\n\x0cstring_value\x18\x01 \x01(\tH\x00\x12\x13\n\tint_value\x18\x02 \x01(\x03H\x00\x12\x15\n\x0b\x66loat_value\x18\x03 \x01(\x01H\x00\x42\x07\n\x05value"\x96\x01\n\x0eUpdateMetadata\x12\x36\n\x08metadata\x18\x01 \x03(\x0b\x32$.chroma.UpdateMetadata.MetadataEntry\x1aL\n\rMetadataEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12*\n\x05value\x18\x02 \x01(\x0b\x32\x1b.chroma.UpdateMetadataValue:\x02\x38\x01"\xb5\x01\n\x15SubmitEmbeddingRecord\x12\n\n\x02id\x18\x01 \x01(\t\x12#\n\x06vector\x18\x02 \x01(\x0b\x32\x0e.chroma.VectorH\x00\x88\x01\x01\x12-\n\x08metadata\x18\x03 \x01(\x0b\x32\x16.chroma.UpdateMetadataH\x01\x88\x01\x01\x12$\n\toperation\x18\x04 \x01(\x0e\x32\x11.chroma.OperationB\t\n\x07_vectorB\x0b\n\t_metadata*8\n\tOperation\x12\x07\n\x03\x41\x44\x44\x10\x00\x12\n\n\x06UPDATE\x10\x01\x12\n\n\x06UPSERT\x10\x02\x12\n\n\x06\x44\x45LETE\x10\x03*(\n\x0eScalarEncoding\x12\x0b\n\x07\x46LOAT32\x10\x00\x12\t\n\x05INT32\x10\x01\x62\x06proto3' +) + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages( + DESCRIPTOR, "chromadb.proto.chroma_pb2", _globals +) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _UPDATEMETADATA_METADATAENTRY._options = None + _UPDATEMETADATA_METADATAENTRY._serialized_options = b"8\001" + _globals["_OPERATION"]._serialized_start = 563 + _globals["_OPERATION"]._serialized_end = 619 + _globals["_SCALARENCODING"]._serialized_start = 621 + _globals["_SCALARENCODING"]._serialized_end = 661 + _globals["_VECTOR"]._serialized_start = 39 + _globals["_VECTOR"]._serialized_end = 124 + _globals["_UPDATEMETADATAVALUE"]._serialized_start = 126 + _globals["_UPDATEMETADATAVALUE"]._serialized_end = 224 + _globals["_UPDATEMETADATA"]._serialized_start = 227 + _globals["_UPDATEMETADATA"]._serialized_end = 377 + _globals["_UPDATEMETADATA_METADATAENTRY"]._serialized_start = 301 + _globals["_UPDATEMETADATA_METADATAENTRY"]._serialized_end = 377 + _globals["_SUBMITEMBEDDINGRECORD"]._serialized_start = 380 + _globals["_SUBMITEMBEDDINGRECORD"]._serialized_end = 561 +# @@protoc_insertion_point(module_scope) diff --git a/chromadb/proto/chroma_pb2.pyi b/chromadb/proto/chroma_pb2.pyi new file mode 100644 index 000000000000..b13327e982fb --- /dev/null +++ b/chromadb/proto/chroma_pb2.pyi @@ -0,0 +1,247 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import collections.abc +import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.internal.enum_type_wrapper +import google.protobuf.message +import sys +import typing + +if sys.version_info >= (3, 10): + import typing as typing_extensions +else: + import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class _Operation: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _OperationEnumTypeWrapper( + google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[_Operation.ValueType], + builtins.type, +): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + ADD: _Operation.ValueType # 0 + UPDATE: _Operation.ValueType # 1 + UPSERT: _Operation.ValueType # 2 + DELETE: _Operation.ValueType # 3 + +class Operation(_Operation, metaclass=_OperationEnumTypeWrapper): ... + +ADD: Operation.ValueType # 0 +UPDATE: Operation.ValueType # 1 +UPSERT: Operation.ValueType # 2 +DELETE: Operation.ValueType # 3 +global___Operation = Operation + +class _ScalarEncoding: + ValueType = typing.NewType("ValueType", builtins.int) + V: typing_extensions.TypeAlias = ValueType + +class _ScalarEncodingEnumTypeWrapper( + google.protobuf.internal.enum_type_wrapper._EnumTypeWrapper[ + _ScalarEncoding.ValueType + ], + builtins.type, +): + DESCRIPTOR: google.protobuf.descriptor.EnumDescriptor + FLOAT32: _ScalarEncoding.ValueType # 0 + INT32: _ScalarEncoding.ValueType # 1 + +class ScalarEncoding(_ScalarEncoding, metaclass=_ScalarEncodingEnumTypeWrapper): ... + +FLOAT32: ScalarEncoding.ValueType # 0 +INT32: ScalarEncoding.ValueType # 1 +global___ScalarEncoding = ScalarEncoding + +@typing_extensions.final +class Vector(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + DIMENSION_FIELD_NUMBER: builtins.int + VECTOR_FIELD_NUMBER: builtins.int + ENCODING_FIELD_NUMBER: builtins.int + dimension: builtins.int + vector: builtins.bytes + encoding: global___ScalarEncoding.ValueType + def __init__( + self, + *, + dimension: builtins.int = ..., + vector: builtins.bytes = ..., + encoding: global___ScalarEncoding.ValueType = ..., + ) -> None: ... + def ClearField( + self, + field_name: typing_extensions.Literal[ + "dimension", b"dimension", "encoding", b"encoding", "vector", b"vector" + ], + ) -> None: ... + +global___Vector = Vector + +@typing_extensions.final +class UpdateMetadataValue(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + STRING_VALUE_FIELD_NUMBER: builtins.int + INT_VALUE_FIELD_NUMBER: builtins.int + FLOAT_VALUE_FIELD_NUMBER: builtins.int + string_value: builtins.str + int_value: builtins.int + float_value: builtins.float + def __init__( + self, + *, + string_value: builtins.str = ..., + int_value: builtins.int = ..., + float_value: builtins.float = ..., + ) -> None: ... + def HasField( + self, + field_name: typing_extensions.Literal[ + "float_value", + b"float_value", + "int_value", + b"int_value", + "string_value", + b"string_value", + "value", + b"value", + ], + ) -> builtins.bool: ... + def ClearField( + self, + field_name: typing_extensions.Literal[ + "float_value", + b"float_value", + "int_value", + b"int_value", + "string_value", + b"string_value", + "value", + b"value", + ], + ) -> None: ... + def WhichOneof( + self, oneof_group: typing_extensions.Literal["value", b"value"] + ) -> ( + typing_extensions.Literal["string_value", "int_value", "float_value"] | None + ): ... + +global___UpdateMetadataValue = UpdateMetadataValue + +@typing_extensions.final +class UpdateMetadata(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + @typing_extensions.final + class MetadataEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.str + @property + def value(self) -> global___UpdateMetadataValue: ... + def __init__( + self, + *, + key: builtins.str = ..., + value: global___UpdateMetadataValue | None = ..., + ) -> None: ... + def HasField( + self, field_name: typing_extensions.Literal["value", b"value"] + ) -> builtins.bool: ... + def ClearField( + self, + field_name: typing_extensions.Literal["key", b"key", "value", b"value"], + ) -> None: ... + + METADATA_FIELD_NUMBER: builtins.int + @property + def metadata( + self, + ) -> google.protobuf.internal.containers.MessageMap[ + builtins.str, global___UpdateMetadataValue + ]: ... + def __init__( + self, + *, + metadata: collections.abc.Mapping[builtins.str, global___UpdateMetadataValue] + | None = ..., + ) -> None: ... + def ClearField( + self, field_name: typing_extensions.Literal["metadata", b"metadata"] + ) -> None: ... + +global___UpdateMetadata = UpdateMetadata + +@typing_extensions.final +class SubmitEmbeddingRecord(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + + ID_FIELD_NUMBER: builtins.int + VECTOR_FIELD_NUMBER: builtins.int + METADATA_FIELD_NUMBER: builtins.int + OPERATION_FIELD_NUMBER: builtins.int + id: builtins.str + @property + def vector(self) -> global___Vector: ... + @property + def metadata(self) -> global___UpdateMetadata: ... + operation: global___Operation.ValueType + def __init__( + self, + *, + id: builtins.str = ..., + vector: global___Vector | None = ..., + metadata: global___UpdateMetadata | None = ..., + operation: global___Operation.ValueType = ..., + ) -> None: ... + def HasField( + self, + field_name: typing_extensions.Literal[ + "_metadata", + b"_metadata", + "_vector", + b"_vector", + "metadata", + b"metadata", + "vector", + b"vector", + ], + ) -> builtins.bool: ... + def ClearField( + self, + field_name: typing_extensions.Literal[ + "_metadata", + b"_metadata", + "_vector", + b"_vector", + "id", + b"id", + "metadata", + b"metadata", + "operation", + b"operation", + "vector", + b"vector", + ], + ) -> None: ... + @typing.overload + def WhichOneof( + self, oneof_group: typing_extensions.Literal["_metadata", b"_metadata"] + ) -> typing_extensions.Literal["metadata"] | None: ... + @typing.overload + def WhichOneof( + self, oneof_group: typing_extensions.Literal["_vector", b"_vector"] + ) -> typing_extensions.Literal["vector"] | None: ... + +global___SubmitEmbeddingRecord = SubmitEmbeddingRecord diff --git a/chromadb/proto/convert.py b/chromadb/proto/convert.py new file mode 100644 index 000000000000..15d1363b05cf --- /dev/null +++ b/chromadb/proto/convert.py @@ -0,0 +1,150 @@ +import array +from typing import Optional, Tuple, Union +from chromadb.api.types import Embedding +import chromadb.proto.chroma_pb2 as proto +from chromadb.types import ( + EmbeddingRecord, + Metadata, + Operation, + ScalarEncoding, + SeqId, + SubmitEmbeddingRecord, + Vector, +) + + +def to_proto_vector(vector: Vector, encoding: ScalarEncoding) -> proto.Vector: + if encoding == ScalarEncoding.FLOAT32: + as_bytes = array.array("f", vector).tobytes() + proto_encoding = proto.ScalarEncoding.FLOAT32 + elif encoding == ScalarEncoding.INT32: + as_bytes = array.array("i", vector).tobytes() + proto_encoding = proto.ScalarEncoding.INT32 + else: + raise ValueError( + f"Unknown encoding {encoding}, expected one of {ScalarEncoding.FLOAT32} \ + or {ScalarEncoding.INT32}" + ) + + return proto.Vector(dimension=len(vector), vector=as_bytes, encoding=proto_encoding) + + +def from_proto_vector(vector: proto.Vector) -> Tuple[Embedding, ScalarEncoding]: + encoding = vector.encoding + as_array: array.array[float] | array.array[int] + if encoding == proto.ScalarEncoding.FLOAT32: + as_array = array.array("f") + out_encoding = ScalarEncoding.FLOAT32 + elif encoding == proto.ScalarEncoding.INT32: + as_array = array.array("i") + out_encoding = ScalarEncoding.INT32 + else: + raise ValueError( + f"Unknown encoding {encoding}, expected one of \ + {proto.ScalarEncoding.FLOAT32} or {proto.ScalarEncoding.INT32}" + ) + + as_array.frombytes(vector.vector) + return (as_array.tolist(), out_encoding) + + +def from_proto_operation(operation: proto.Operation.ValueType) -> Operation: + if operation == proto.Operation.ADD: + return Operation.ADD + elif operation == proto.Operation.UPDATE: + return Operation.UPDATE + elif operation == proto.Operation.UPSERT: + return Operation.UPSERT + elif operation == proto.Operation.DELETE: + return Operation.DELETE + else: + raise RuntimeError(f"Unknown operation {operation}") # TODO: full error + + +def from_proto_metadata(metadata: proto.UpdateMetadata) -> Optional[Metadata]: + if not metadata.metadata: + return None + out_metadata = {} + for key, value in metadata.metadata.items(): + if value.HasField("string_value"): + out_metadata[key] = value.string_value + elif value.HasField("int_value"): + out_metadata[key] = value.int_value + elif value.HasField("float_value"): + out_metadata[key] = value.float_value + else: + raise RuntimeError(f"Unknown metadata value type {value}") + return out_metadata + + +def from_proto_submit( + submit_embedding_record: proto.SubmitEmbeddingRecord, seq_id: SeqId +) -> EmbeddingRecord: + embedding, encoding = from_proto_vector(submit_embedding_record.vector) + record = EmbeddingRecord( + id=submit_embedding_record.id, + seq_id=seq_id, + embedding=embedding, + encoding=encoding, + metadata=from_proto_metadata(submit_embedding_record.metadata), + operation=from_proto_operation(submit_embedding_record.operation), + ) + return record + + +def to_proto_metadata_update_value( + value: Union[str, int, float, None] +) -> proto.UpdateMetadataValue: + if isinstance(value, str): + return proto.UpdateMetadataValue(string_value=value) + elif isinstance(value, int): + return proto.UpdateMetadataValue(int_value=value) + elif isinstance(value, float): + return proto.UpdateMetadataValue(float_value=value) + elif value is None: + return proto.UpdateMetadataValue() + else: + raise ValueError( + f"Unknown metadata value type {type(value)}, expected one of str, int, \ + float, or None" + ) + + +def to_proto_operation(operation: Operation) -> proto.Operation.ValueType: + if operation == Operation.ADD: + return proto.Operation.ADD + elif operation == Operation.UPDATE: + return proto.Operation.UPDATE + elif operation == Operation.UPSERT: + return proto.Operation.UPSERT + elif operation == Operation.DELETE: + return proto.Operation.DELETE + else: + raise ValueError( + f"Unknown operation {operation}, expected one of {Operation.ADD}, \ + {Operation.UPDATE}, {Operation.UPDATE}, or {Operation.DELETE}" + ) + + +def to_proto_submit( + submit_record: SubmitEmbeddingRecord, +) -> proto.SubmitEmbeddingRecord: + vector = None + if submit_record["embedding"] is not None and submit_record["encoding"] is not None: + vector = to_proto_vector(submit_record["embedding"], submit_record["encoding"]) + + metadata = None + if submit_record["metadata"] is not None: + metadata = { + k: to_proto_metadata_update_value(v) + for k, v in submit_record["metadata"].items() + } + + return proto.SubmitEmbeddingRecord( + id=submit_record["id"], + vector=vector, + metadata=proto.UpdateMetadata(metadata=metadata) + if metadata is not None + else None, + operation=to_proto_operation(submit_record["operation"]), + ) diff --git a/chromadb/segment/impl/metadata/sqlite.py b/chromadb/segment/impl/metadata/sqlite.py index 8e9649a627d0..a7098d7808b6 100644 --- a/chromadb/segment/impl/metadata/sqlite.py +++ b/chromadb/segment/impl/metadata/sqlite.py @@ -469,9 +469,9 @@ def delete(self) -> None: def _encode_seq_id(seq_id: SeqId) -> bytes: """Encode a SeqID into a byte array""" - if seq_id.bit_length() < 64: + if seq_id.bit_length() <= 64: return int.to_bytes(seq_id, 8, "big") - elif seq_id.bit_length() < 192: + elif seq_id.bit_length() <= 192: return int.to_bytes(seq_id, 24, "big") else: raise ValueError(f"Unsupported SeqID: {seq_id}") diff --git a/chromadb/segment/impl/vector/local_persistent_hnsw.py b/chromadb/segment/impl/vector/local_persistent_hnsw.py index 6e1df7b1f1f4..f8c74bd0fe7d 100644 --- a/chromadb/segment/impl/vector/local_persistent_hnsw.py +++ b/chromadb/segment/impl/vector/local_persistent_hnsw.py @@ -207,7 +207,6 @@ def _write_records(self, records: Sequence[EmbeddingRecord]) -> None: """Add a batch of embeddings to the index""" if not self._running: raise RuntimeError("Cannot add embeddings to stopped component") - with WriteRWLock(self._lock): for record in records: if record["embedding"] is not None: diff --git a/chromadb/test/ingest/test_producer_consumer.py b/chromadb/test/ingest/test_producer_consumer.py index 84aa69ffd076..1163889a2460 100644 --- a/chromadb/test/ingest/test_producer_consumer.py +++ b/chromadb/test/ingest/test_producer_consumer.py @@ -1,3 +1,4 @@ +import asyncio import os import shutil import tempfile @@ -16,6 +17,7 @@ ) from chromadb.ingest import Producer, Consumer from chromadb.db.impl.sqlite import SqliteDB +from chromadb.ingest.impl.utils import create_topic_name from chromadb.test.conftest import ProducerFn from chromadb.types import ( SubmitEmbeddingRecord, @@ -51,8 +53,33 @@ def sqlite_persistent() -> Generator[Tuple[Producer, Consumer], None, None]: shutil.rmtree(save_path) +def pulsar() -> Generator[Tuple[Producer, Consumer], None, None]: + """Fixture generator for pulsar Producer + Consumer. This fixture requires a running + pulsar cluster. You can use bin/cluster-test.sh to start a standalone pulsar and run this test + """ + system = System( + Settings( + allow_reset=True, + chroma_producer_impl="chromadb.ingest.impl.pulsar.PulsarProducer", + chroma_consumer_impl="chromadb.ingest.impl.pulsar.PulsarConsumer", + pulsar_broker_url="localhost", + pulsar_admin_port="8080", + pulsar_broker_port="6650", + ) + ) + producer = system.require(Producer) + consumer = system.require(Consumer) + system.start() + yield producer, consumer + system.stop() + + def fixtures() -> List[Callable[[], Generator[Tuple[Producer, Consumer], None, None]]]: - return [sqlite, sqlite_persistent] + fixtures = [sqlite, sqlite_persistent] + if "CHROMA_CLUSTER_TEST_ONLY" in os.environ: + fixtures = [pulsar] + + return fixtures @pytest.fixture(scope="module", params=fixtures()) @@ -89,14 +116,20 @@ class CapturingConsumeFn: waiters: List[Tuple[int, Event]] def __init__(self) -> None: + """A function that captures embeddings and allows you to wait for a certain + number of embeddings to be available. It must be constructed in the thread with + the main event loop + """ self.embeddings = [] self.waiters = [] + self._loop = asyncio.get_event_loop() def __call__(self, embeddings: Sequence[EmbeddingRecord]) -> None: self.embeddings.extend(embeddings) for n, event in self.waiters: if len(self.embeddings) >= n: - event.set() + # event.set() is not thread safe, so we need to call it in the main event loop + self._loop.call_soon_threadsafe(event.set) async def get(self, n: int, timeout_secs: int = 10) -> Sequence[EmbeddingRecord]: "Wait until at least N embeddings are available, then return all embeddings" @@ -132,6 +165,10 @@ def assert_records_match( assert_approx_equal(inserted["embedding"], consumed["embedding"]) +def full_topic_name(topic_name: str) -> str: + return create_topic_name("default", "default", topic_name) + + @pytest.mark.asyncio async def test_backfill( producer_consumer: Tuple[Producer, Consumer], @@ -140,12 +177,14 @@ async def test_backfill( ) -> None: producer, consumer = producer_consumer producer.reset_state() + consumer.reset_state() - producer.create_topic("test_topic") - embeddings = produce_fns(producer, "test_topic", sample_embeddings, 3)[0] + topic_name = full_topic_name("test_topic") + producer.create_topic(topic_name) + embeddings = produce_fns(producer, topic_name, sample_embeddings, 3)[0] consume_fn = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn, start=consumer.min_seqid()) + consumer.subscribe(topic_name, consume_fn, start=consumer.min_seqid()) recieved = await consume_fn.get(3) assert_records_match(embeddings, recieved) @@ -158,18 +197,21 @@ async def test_notifications( ) -> None: producer, consumer = producer_consumer producer.reset_state() - producer.create_topic("test_topic") + consumer.reset_state() + topic_name = full_topic_name("test_topic") + + producer.create_topic(topic_name) embeddings: List[SubmitEmbeddingRecord] = [] consume_fn = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn, start=consumer.min_seqid()) + consumer.subscribe(topic_name, consume_fn, start=consumer.min_seqid()) for i in range(10): e = next(sample_embeddings) embeddings.append(e) - producer.submit_embedding("test_topic", e) + producer.submit_embedding(topic_name, e) received = await consume_fn.get(i + 1) assert_records_match(embeddings, received) @@ -181,8 +223,11 @@ async def test_multiple_topics( ) -> None: producer, consumer = producer_consumer producer.reset_state() - producer.create_topic("test_topic_1") - producer.create_topic("test_topic_2") + consumer.reset_state() + topic_name_1 = full_topic_name("test_topic_1") + topic_name_2 = full_topic_name("test_topic_2") + producer.create_topic(topic_name_1) + producer.create_topic(topic_name_2) embeddings_1: List[SubmitEmbeddingRecord] = [] embeddings_2: List[SubmitEmbeddingRecord] = [] @@ -190,19 +235,19 @@ async def test_multiple_topics( consume_fn_1 = CapturingConsumeFn() consume_fn_2 = CapturingConsumeFn() - consumer.subscribe("test_topic_1", consume_fn_1, start=consumer.min_seqid()) - consumer.subscribe("test_topic_2", consume_fn_2, start=consumer.min_seqid()) + consumer.subscribe(topic_name_1, consume_fn_1, start=consumer.min_seqid()) + consumer.subscribe(topic_name_2, consume_fn_2, start=consumer.min_seqid()) for i in range(10): e_1 = next(sample_embeddings) embeddings_1.append(e_1) - producer.submit_embedding("test_topic_1", e_1) + producer.submit_embedding(topic_name_1, e_1) results_2 = await consume_fn_1.get(i + 1) assert_records_match(embeddings_1, results_2) e_2 = next(sample_embeddings) embeddings_2.append(e_2) - producer.submit_embedding("test_topic_2", e_2) + producer.submit_embedding(topic_name_2, e_2) results_2 = await consume_fn_2.get(i + 1) assert_records_match(embeddings_2, results_2) @@ -215,21 +260,23 @@ async def test_start_seq_id( ) -> None: producer, consumer = producer_consumer producer.reset_state() - producer.create_topic("test_topic") + consumer.reset_state() + topic_name = full_topic_name("test_topic") + producer.create_topic(topic_name) consume_fn_1 = CapturingConsumeFn() consume_fn_2 = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn_1, start=consumer.min_seqid()) + consumer.subscribe(topic_name, consume_fn_1, start=consumer.min_seqid()) - embeddings = produce_fns(producer, "test_topic", sample_embeddings, 5)[0] + embeddings = produce_fns(producer, topic_name, sample_embeddings, 5)[0] results_1 = await consume_fn_1.get(5) assert_records_match(embeddings, results_1) start = consume_fn_1.embeddings[-1]["seq_id"] - consumer.subscribe("test_topic", consume_fn_2, start=start) - second_embeddings = produce_fns(producer, "test_topic", sample_embeddings, 5)[0] + consumer.subscribe(topic_name, consume_fn_2, start=start) + second_embeddings = produce_fns(producer, topic_name, sample_embeddings, 5)[0] assert isinstance(embeddings, list) embeddings.extend(second_embeddings) results_2 = await consume_fn_2.get(5) @@ -244,20 +291,22 @@ async def test_end_seq_id( ) -> None: producer, consumer = producer_consumer producer.reset_state() - producer.create_topic("test_topic") + consumer.reset_state() + topic_name = full_topic_name("test_topic") + producer.create_topic(topic_name) consume_fn_1 = CapturingConsumeFn() consume_fn_2 = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn_1, start=consumer.min_seqid()) + consumer.subscribe(topic_name, consume_fn_1, start=consumer.min_seqid()) - embeddings = produce_fns(producer, "test_topic", sample_embeddings, 10)[0] + embeddings = produce_fns(producer, topic_name, sample_embeddings, 10)[0] results_1 = await consume_fn_1.get(10) assert_records_match(embeddings, results_1) end = consume_fn_1.embeddings[-5]["seq_id"] - consumer.subscribe("test_topic", consume_fn_2, start=consumer.min_seqid(), end=end) + consumer.subscribe(topic_name, consume_fn_2, start=consumer.min_seqid(), end=end) results_2 = await consume_fn_2.get(6) assert_records_match(embeddings[:6], results_2) @@ -274,14 +323,16 @@ async def test_submit_batch( ) -> None: producer, consumer = producer_consumer producer.reset_state() + consumer.reset_state() + topic_name = full_topic_name("test_topic") embeddings = [next(sample_embeddings) for _ in range(100)] - producer.create_topic("test_topic") - producer.submit_embeddings("test_topic", embeddings=embeddings) + producer.create_topic(topic_name) + producer.submit_embeddings(topic_name, embeddings=embeddings) consume_fn = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn, start=consumer.min_seqid()) + consumer.subscribe(topic_name, consume_fn, start=consumer.min_seqid()) recieved = await consume_fn.get(100) assert_records_match(embeddings, recieved) @@ -295,13 +346,16 @@ async def test_multiple_topics_batch( ) -> None: producer, consumer = producer_consumer producer.reset_state() + consumer.reset_state() - N_TOPICS = 100 + N_TOPICS = 2 consume_fns = [CapturingConsumeFn() for _ in range(N_TOPICS)] for i in range(N_TOPICS): - producer.create_topic(f"test_topic_{i}") + producer.create_topic(full_topic_name(f"test_topic_{i}")) consumer.subscribe( - f"test_topic_{i}", consume_fns[i], start=consumer.min_seqid() + full_topic_name(f"test_topic_{i}"), + consume_fns[i], + start=consumer.min_seqid(), ) embeddings_n: List[List[SubmitEmbeddingRecord]] = [[] for _ in range(N_TOPICS)] @@ -310,17 +364,17 @@ async def test_multiple_topics_batch( N_TO_PRODUCE = 100 total_produced = 0 for i in range(N_TO_PRODUCE // PRODUCE_BATCH_SIZE): - for i in range(N_TOPICS): - embeddings_n[i].extend( + for n in range(N_TOPICS): + embeddings_n[n].extend( produce_fns( producer, - f"test_topic_{i}", + full_topic_name(f"test_topic_{n}"), sample_embeddings, PRODUCE_BATCH_SIZE, )[0] ) - recieved = await consume_fns[i].get(total_produced + PRODUCE_BATCH_SIZE) - assert_records_match(embeddings_n[i], recieved) + recieved = await consume_fns[n].get(total_produced + PRODUCE_BATCH_SIZE) + assert_records_match(embeddings_n[n], recieved) total_produced += PRODUCE_BATCH_SIZE @@ -331,19 +385,21 @@ async def test_max_batch_size( ) -> None: producer, consumer = producer_consumer producer.reset_state() - max_batch_size = producer_consumer[0].max_batch_size + consumer.reset_state() + topic_name = full_topic_name("test_topic") + max_batch_size = producer.max_batch_size assert max_batch_size > 0 # Make sure that we can produce a batch of size max_batch_size embeddings = [next(sample_embeddings) for _ in range(max_batch_size)] consume_fn = CapturingConsumeFn() - consumer.subscribe("test_topic", consume_fn, start=consumer.min_seqid()) - producer.submit_embeddings("test_topic", embeddings=embeddings) + consumer.subscribe(topic_name, consume_fn, start=consumer.min_seqid()) + producer.submit_embeddings(topic_name, embeddings=embeddings) received = await consume_fn.get(max_batch_size, timeout_secs=120) assert_records_match(embeddings, received) embeddings = [next(sample_embeddings) for _ in range(max_batch_size + 1)] # Make sure that we can't produce a batch of size > max_batch_size with pytest.raises(ValueError) as e: - producer.submit_embeddings("test_topic", embeddings=embeddings) + producer.submit_embeddings(topic_name, embeddings=embeddings) assert "Cannot submit more than" in str(e.value) diff --git a/docker-compose.cluster.yml b/docker-compose.cluster.yml new file mode 100644 index 000000000000..d36ed16906f8 --- /dev/null +++ b/docker-compose.cluster.yml @@ -0,0 +1,66 @@ +# This docker compose file is not meant to be used. It is a work in progress +# for the distributed version of Chroma. It is not yet functional. + +version: '3.9' + +networks: + net: + driver: bridge + +services: + server: + image: server + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/chroma + - index_data:/index_data + command: uvicorn chromadb.app:app --reload --workers 1 --host 0.0.0.0 --port 8000 --log-config log_config.yml + environment: + - IS_PERSISTENT=TRUE + - CHROMA_PRODUCER_IMPL=chromadb.ingest.impl.pulsar.PulsarProducer + - CHROMA_CONSUMER_IMPL=chromadb.ingest.impl.pulsar.PulsarConsumer + - PULSAR_BROKER_URL=pulsar + - PULSAR_BROKER_PORT=6650 + - PULSAR_ADMIN_PORT=8080 + ports: + - 8000:8000 + depends_on: + pulsar: + condition: service_healthy + networks: + - net + + pulsar: + image: apachepulsar/pulsar + volumes: + - pulsardata:/pulsar/data + - pulsarconf:/pulsar/conf + command: bin/pulsar standalone + ports: + - 6650:6650 + - 8080:8080 + networks: + - net + healthcheck: + test: + [ + "CMD", + "curl", + "-f", + "localhost:8080/admin/v2/brokers/health" + ] + interval: 3s + timeout: 1m + retries: 10 + +volumes: + index_data: + driver: local + backups: + driver: local + pulsardata: + driver: local + pulsarconf: + driver: local diff --git a/requirements_dev.txt b/requirements_dev.txt index 68546b27796a..9354d39b7255 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,8 +3,10 @@ build httpx hypothesis hypothesis[numpy] +mypy-protobuf pre-commit pytest pytest-asyncio setuptools_scm +types-protobuf types-requests==2.30.0.0 From 5436bd5de1930256c98cd5f2eb3cbcd0a6e73f2b Mon Sep 17 00:00:00 2001 From: Trayan Azarov Date: Wed, 20 Sep 2023 20:28:38 +0300 Subject: [PATCH 67/67] [ENH]: Support for $in and $nin metadata filters (#1151) Refs: #1105 ## Description of changes *Summarize the changes made by this PR.* - Improvements & Bug fixes - JS Client support for $in and $nin ## Test plan *How are these changes tested?* - [x] Tests pass locally `yarn test` for js ## Documentation Changes TBD --- clients/js/src/types.ts | 8 +-- clients/js/test/query.collection.test.ts | 68 ++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 3 deletions(-) diff --git a/clients/js/src/types.ts b/clients/js/src/types.ts index 8787d5e5659e..1f1dd04c4c82 100644 --- a/clients/js/src/types.ts +++ b/clients/js/src/types.ts @@ -20,13 +20,15 @@ export type IDs = ID[]; export type PositiveInteger = number; -type LiteralValue = string | number; +type LiteralValue = string | number | boolean; +type ListLiteralValue = LiteralValue[]; type LiteralNumber = number; type LogicalOperator = "$and" | "$or"; +type InclusionOperator = "$in" | "$nin"; type WhereOperator = "$gt" | "$gte" | "$lt" | "$lte" | "$ne" | "$eq"; type OperatorExpression = { - [key in WhereOperator | LogicalOperator]?: LiteralValue | LiteralNumber; + [key in WhereOperator | InclusionOperator | LogicalOperator ]?: LiteralValue | ListLiteralValue; }; type BaseWhere = { @@ -77,4 +79,4 @@ export type CollectionMetadata = Record; // see all options here: https://www.jsdocs.io/package/@types/node-fetch#RequestInit export type ConfigOptions = { options?: RequestInit; -}; \ No newline at end of file +}; diff --git a/clients/js/test/query.collection.test.ts b/clients/js/test/query.collection.test.ts index 05125a27ffa0..878ed0a71df3 100644 --- a/clients/js/test/query.collection.test.ts +++ b/clients/js/test/query.collection.test.ts @@ -86,3 +86,71 @@ test("it should query a collection with text", async () => { expect.arrayContaining(results.documents[0]) ); }) + + +test("it should query a collection with text and where", async () => { + await chroma.reset(); + let embeddingFunction = new TestEmbeddingFunction(); + const collection = await chroma.createCollection({ name: "test", embeddingFunction: embeddingFunction }); + await collection.add({ ids: IDS, embeddings: EMBEDDINGS, metadatas: METADATAS, documents: DOCUMENTS }); + + const results = await collection.query({ + queryTexts: ["test"], + nResults: 3, + where: { "float_value" : 2 } + }); + + expect(results).toBeDefined(); + expect(results).toBeInstanceOf(Object); + expect(results.ids.length).toBe(1); + expect(["test3"]).toEqual(expect.arrayContaining(results.ids[0])); + expect(["test2"]).not.toEqual(expect.arrayContaining(results.ids[0])); + expect(["This is a third test"]).toEqual( + expect.arrayContaining(results.documents[0]) + ); +}) + + +test("it should query a collection with text and where in", async () => { + await chroma.reset(); + let embeddingFunction = new TestEmbeddingFunction(); + const collection = await chroma.createCollection({ name: "test", embeddingFunction: embeddingFunction }); + await collection.add({ ids: IDS, embeddings: EMBEDDINGS, metadatas: METADATAS, documents: DOCUMENTS }); + + const results = await collection.query({ + queryTexts: ["test"], + nResults: 3, + where: { "float_value" : { '$in': [2,5,10] }} + }); + + expect(results).toBeDefined(); + expect(results).toBeInstanceOf(Object); + expect(results.ids.length).toBe(1); + expect(["test3"]).toEqual(expect.arrayContaining(results.ids[0])); + expect(["test2"]).not.toEqual(expect.arrayContaining(results.ids[0])); + expect(["This is a third test"]).toEqual( + expect.arrayContaining(results.documents[0]) + ); +}) + +test("it should query a collection with text and where nin", async () => { + await chroma.reset(); + let embeddingFunction = new TestEmbeddingFunction(); + const collection = await chroma.createCollection({ name: "test", embeddingFunction: embeddingFunction }); + await collection.add({ ids: IDS, embeddings: EMBEDDINGS, metadatas: METADATAS, documents: DOCUMENTS }); + + const results = await collection.query({ + queryTexts: ["test"], + nResults: 3, + where: { "float_value" : { '$nin': [-2,0] }} + }); + + expect(results).toBeDefined(); + expect(results).toBeInstanceOf(Object); + expect(results.ids.length).toBe(1); + expect(["test3"]).toEqual(expect.arrayContaining(results.ids[0])); + expect(["test2"]).not.toEqual(expect.arrayContaining(results.ids[0])); + expect(["This is a third test"]).toEqual( + expect.arrayContaining(results.documents[0]) + ); +})