[
  {
    "path": ".github/workflows/deploy-docs-on-demand.yml",
    "content": "name: Deploy Docs On Demand\n\non:\n  workflow_dispatch:\n\njobs:\n  deploy-docs-on-demand:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out\n        uses: actions/checkout@v3\n\n      - name: Set up the environment\n        uses: ./.github/actions/setup-poetry-env\n\n      - name: Install the latest version of rye\n        uses: eifinger/setup-rye@v2\n\n      - name: Install dependencies\n        run: make install\n\n      - name: Deploy documentation\n        run: rye run python deploy_docs.py\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: Main\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    types: [opened, synchronize, reopened]\n\njobs:\n  tox:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\"]\n      fail-fast: false\n    steps:\n      - name: Check out\n        uses: actions/checkout@v3\n\n      - name: Set up python\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install the latest version of rye\n        uses: eifinger/setup-rye@v2\n\n      - name: Pin python-version ${{ matrix.python-version }}\n        run: rye pin ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: make install\n\n      - name: Run tests\n        run: make test\n\n      - name: Run check\n        run: make check\n"
  },
  {
    "path": ".github/workflows/on-release-main.yml",
    "content": "name: release-main\n\non:\n  release:\n    types: [published]\n    branches: [main]\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check out\n        uses: actions/checkout@v3\n\n      - name: Export tag\n        id: vars\n        run: echo tag=${GITHUB_REF#refs/*/} >> $GITHUB_OUTPUT\n\n      - name: Set up python\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install the latest version of rye\n        uses: eifinger/setup-rye@v2\n\n      # https://github.com/astral-sh/rye/issues/1180\n      - name: Patch Rye\n        run: |\n          echo \"Patching Rye with Twine 5.1.1\"\n          $RYE_HOME/self/bin/pip install twine==5.1.1\n\n      - name: Build and publish\n        run: |\n          rye build\n          rye publish --token $PYPI_TOKEN --yes\n        env:\n          PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}\n          RELEASE_VERSION: ${{ steps.vars.outputs.tag }}\n"
  },
  {
    "path": ".gitignore",
    "content": "docs/source\n\n# From https://raw.githubusercontent.com/github/gitignore/main/Python.gitignore\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n.DS_Store\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\ncover/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\n.pybuilder/\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n#   For a library or package, you might want to ignore these files since the code is\n#   intended to run in multiple environments; otherwise, check them in:\n# .python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n\n# pdm\n#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.\n#pdm.lock\n#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it\n#   in version control.\n#   https://pdm.fming.dev/#use-with-ide\n.pdm.toml\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# pytype static type analyzer\n.pytype/\n\n# Cython debug symbols\ncython_debug/\n\n# Vscode config files\n.vscode/\n\n# PyCharm\n#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can\n#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore\n#  and can be added to the global gitignore or merged into this file.  For a more nuclear\n#  option (not recommended) you can uncomment the following to ignore the entire idea folder.\n.idea/\n"
  },
  {
    "path": ".python-version",
    "content": "3.11.9\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024, Mani Mozaffar\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: install\ninstall: ## Install the rye environment\n\t@echo \"🚀 Creating virtual environment using rye and uv\"\n\trye sync\n\n.PHONY: check\ncheck: ## Run the quality checks on the code\n\t@echo \"🚀 Running quality checks\"\n\trye run ruff .\n\trye run pyright .\n\n.PHONY: test\ntest: ## Test the code with pytest\n\t@echo \"🚀 Testing code: Running pytest\"\n\trye run pytest\n\n\n.PHONY: docs\ndocs:  ## Build and serve the documentation\n\t@echo \"🚀 Testing documentation: Building and testing\"\n\trye run mkdocs serve\n\n.PHONY: deploy-docs\ndeploy-docs: ## Build and serve the documentation\n\t@echo \"🚀 Deploying documentation\"\n\trye run python deploy_docs.py\n\n\n.PHONY: docs-test\ndocs-test: ## Test if documentation can be built without warnings or errors\n\t@rye run mkdocs build -s\n\n.PHONY: help\nhelp:\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-20s\\033[0m %s\\n\", $$1, $$2}'\n\n.DEFAULT_GOAL := help\n"
  },
  {
    "path": "README.md",
    "content": "# aioclock\n\n[![Release](https://img.shields.io/github/v/release/ManiMozaffar/aioclock)](https://img.shields.io/github/v/release/ManiMozaffar/aioclock)\n[![Build status](https://img.shields.io/github/actions/workflow/status/ManiMozaffar/aioclock/main.yml?branch=main)](https://github.com/ManiMozaffar/aioclock/actions/workflows/main.yml?query=branch%3Amain)\n[![Commit activity](https://img.shields.io/github/commit-activity/m/ManiMozaffar/aioclock)](https://img.shields.io/github/commit-activity/m/ManiMozaffar/aioclock)\n[![License](https://img.shields.io/github/license/ManiMozaffar/aioclock)](https://img.shields.io/github/license/ManiMozaffar/aioclock)\n\nAn asyncio-based scheduling framework designed for execution of periodic task with integrated support for dependency injection, enabling efficient and flexiable task management\n\n- **Github repository**: <https://github.com/ManiMozaffar/aioclock/>\n\n## Features\n\nAioclock offers:\n\n- Async: 100% Async, very light, fast and resource friendly\n- Scheduling: Keep scheduling tasks for you\n- Group: Group your task, for better code maintainability\n- Trigger: Already defined and easily extendable triggers, to trigger your scheduler to be started\n- Easy syntax: Library's syntax is very easy and enjoyable, no confusing hierarchy\n- Pydantic v2 validation: Validate all your trigger on startup using pydantic 2. Fastest to fail possible!\n- **Soon**: Running the task dispatcher (scheduler) on different process by default, so CPU intensive stuff on task won't delay the scheduling\n- **Soon**: Backend support, to allow horizontal scalling, by synchronizing, maybe using Redis\n\n## Getting started\n\nTo Install aioclock, simply do\n\n```\npip install aioclock\n```\n\n## Help\n\nSee [documentation](https://ManiMozaffar.github.io/aioclock/) for more details.\n\n## A Sample Example\n\n```python\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\nimport asyncio\n\nfrom aioclock import AioClock, At, Depends, Every, Forever, Once\nfrom aioclock.group import Group\n\n# groups.py\ngroup = Group()\n\n\ndef more_useless_than_me():\n    return \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Every(seconds=10))\nasync def every():\n    print(\"Every 10 seconds, I make a quantum leap. Where will I land next?\")\n\n\n@group.task(trigger=Every(seconds=5))\ndef even_sync_works():\n    print(\"I'm a synchronous task. I work even in async world.\")\n\n\n@group.task(trigger=At(tz=\"UTC\", hour=0, minute=0, second=0))\nasync def at():\n    print(\n        \"When the clock strikes midnight... I turn into a pumpkin. Just kidding, I run this task!\"\n    )\n\n\n@group.task(trigger=Forever())\nasync def forever(val: str = Depends(more_useless_than_me)):\n    await asyncio.sleep(2)\n    print(\"Heartbeat detected. Still not a zombie. Will check again in a bit.\")\n    assert val == \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Once())\nasync def once():\n    print(\"Just once, I get to say something. Here it goes... I love lamp.\")\n\n\n@asynccontextmanager\nasync def lifespan(aio_clock: AioClock) -> AsyncGenerator[AioClock]:\n    # starting up\n    print(\n        \"Welcome to the Async Chronicles! Did you know a group of unicorns is called a blessing? Well, now you do!\"\n    )\n    yield aio_clock\n    # shuting down\n    print(\"Going offline. Remember, if your code is running, you better go catch it!\")\n\n\n# app.py\napp = AioClock(lifespan=lifespan)\napp.include_group(group)\n\n\n# main.py\nif __name__ == \"__main__\":\n    asyncio.run(app.serve())\n```\n"
  },
  {
    "path": "aioclock/__init__.py",
    "content": "from fast_depends import Depends\n\nfrom aioclock.app import AioClock\nfrom aioclock.group import Group\nfrom aioclock.triggers import At, Cron, Every, Forever, Once, OnShutDown, OnStartUp, OrTrigger\n\n__all__ = [\n    \"Depends\",\n    \"Once\",\n    \"OnStartUp\",\n    \"OnShutDown\",\n    \"Every\",\n    \"Forever\",\n    \"Group\",\n    \"AioClock\",\n    \"At\",\n    \"Cron\",\n    \"OrTrigger\",\n]\n\n__version__ = \"0.3.0\"\n"
  },
  {
    "path": "aioclock/api.py",
    "content": "\"\"\"\nExternal API of the aioclock package, that can be used to interact with the AioClock instance.\nThis module could be very useful if you intend to use aioclock in a web application or a CLI tool.\n\nOther tools and extension are written from this tool.\n\n!!! danger \"Note when writing to aioclock API and changing its state.\"\n    Right now the state of AioClock instance is on the memory level,\n    so if you write an API and change a task's trigger time, it will not persist.\n    In the future, we might store the state of AioClock instance in a database, so that it always remains same.\n    But this is a bit tricky and implicit because then your code gets ignored\n    and database is preferred over the codebase.\n    For now, you may consider it as a way to change something without redeploying the application,\n    but it is not very recommended to write.\n\"\"\"\n\nimport sys\nfrom typing import Any, Awaitable, Callable, TypeVar, Union\nfrom uuid import UUID\n\nfrom fast_depends import inject\nfrom pydantic import BaseModel\n\nfrom aioclock.app import AioClock\nfrom aioclock.exceptions import TaskIdNotFound\nfrom aioclock.provider import get_provider\nfrom aioclock.triggers import TriggerT\n\nif sys.version_info < (3, 10):\n    from typing_extensions import ParamSpec\nelse:\n    from typing import ParamSpec\n\nT = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n\n\nclass TaskMetadata(BaseModel):\n    \"\"\"Metadata of the task that is included in the AioClock instance.\n\n    Attributes:\n        id: UUID: Task ID that is unique for each task, and changes every time you run the aioclock app.\n            In the future, we might store task ID in a database, so that it always remains same.\n        trigger: Union[TriggerT, Any]: Trigger that is used to run the task,\n            type is also any to ease implementing new triggers.\n        task_name: str: Name of the task function.\n    \"\"\"\n\n    id: UUID\n\n    trigger: Union[TriggerT, Any]\n\n    task_name: str\n\n\nasync def run_specific_task(task_id: UUID, app: AioClock):\n    \"\"\"Run a specific task immediately by its ID, from the AioClock instance.\n\n    params:\n        task_id: Task ID that is unique for each task, and changes every time you run the aioclock app.\n            In the future, we might store task ID in a database, so that it always remains same.\n        app: AioClock instance to run the task from.\n\n    Example:\n        ```python\n        from aioclock import  AioClock, Once\n        from aioclock.api import run_specific_task\n\n        app = AioClock()\n\n        @app.task(trigger=Once())\n        async def main():\n            print(\"Hello World\")\n\n        async def some_other_func():\n            await run_specific_task(app._tasks[0].id, app)\n        ```\n\n    \"\"\"\n    task = next((task for task in app._tasks if task.id == task_id), None)\n    if not task:\n        raise TaskIdNotFound\n    return await run_with_injected_deps(task.func)\n\n\nasync def run_with_injected_deps(func: Callable[P, Awaitable[T]]) -> T:\n    \"\"\"Runs an aioclock decorated function, with all the dependencies injected.\n\n    Can be used to run a task function with all the dependencies injected.\n\n    params:\n        func: Function to run with all the dependencies injected. Must be decorated with `@app.task` decorator.\n\n    Example:\n        ```python\n        from aioclock import Once, AioClock, Depends\n        from aioclock.api import run_with_injected_deps\n\n        app = AioClock()\n\n        def some_dependency():\n            return 1\n\n        @app.task(trigger=Once())\n        async def main(bar: int = Depends(some_dependency)):\n            print(\"Hello World\")\n            return bar\n\n        async def some_other_func():\n            foo = await run_with_injected_deps(main)\n            assert foo == 1\n        ```\n\n    \"\"\"\n    return await inject(func, dependency_overrides_provider=get_provider())()  # type: ignore\n\n\nasync def get_metadata_of_all_tasks(app: AioClock) -> list[TaskMetadata]:\n    \"\"\"Get metadata of all tasks that are included in the AioClock instance.\n\n    This function can be used to mutate the `TaskMetadata` object, i.e. to change the trigger of a task.\n    But for now it is yet not recommended to do this, as you might experience some unexpected behavior.\n    But in next versions, I'd like to make it more stable and reliable on mutating the data.\n\n    params:\n        app: AioClock instance to get the metadata of all tasks.\n\n    Example:\n        ```python\n        from aioclock import AioClock, Once\n        from aioclock.api import get_metadata_of_all_tasks\n\n        app = AioClock()\n        @app.task(trigger=Once())\n        async def main(): ...\n\n        async def some_other_func():\n            metadata = await get_metadata_of_all_tasks(app)\n        ```\n    \"\"\"\n    return [\n        TaskMetadata(\n            id=task.id,\n            trigger=task.trigger,\n            task_name=task.func.__name__,\n        )\n        for task in app._get_tasks(exclude_type=set())\n    ]\n"
  },
  {
    "path": "aioclock/app.py",
    "content": "\"\"\"\nTo initialize the AioClock instance, you need to import the AioClock class from the aioclock module.\nAioClock class represent the aioclock, and handle the tasks and groups that will be run by the aioclock.\n\nAnother way to modularize your code is to use `Group` which is kinda the same idea as router in web frameworks.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nimport sys\nfrom functools import wraps\nfrom typing import (\n    Any,\n    AsyncContextManager,\n    Callable,\n    ContextManager,\n    Optional,\n    TypeVar,\n    Union,\n)\n\nimport anyio\n\nif sys.version_info < (3, 10):\n    from typing_extensions import ParamSpec\nelse:\n    from typing import ParamSpec\n\nif sys.version_info < (3, 11):\n    from typing_extensions import assert_never\nelse:\n    from typing import assert_never\n\nfrom asyncer import asyncify\nfrom fast_depends import inject\n\nfrom aioclock.custom_types import Triggers\nfrom aioclock.group import Group, Task\nfrom aioclock.provider import get_provider\nfrom aioclock.triggers import BaseTrigger\nfrom aioclock.utils import flatten_chain\n\nT = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n\n\nclass AioClock:\n    \"\"\"\n    AioClock is the main class that will be used to run the tasks.\n    It will be responsible for running the tasks in the right order.\n\n    Example:\n        ```python\n        from aioclock import AioClock, Once\n        app = AioClock()\n\n        @app.task(trigger=Once())\n        async def main():\n            print(\"Hello World\")\n        ```\n\n    To run the aioclock final app simply do:\n\n    Example:\n        ```python\n        from aioclock import AioClock, Once\n        import asyncio\n\n        app = AioClock()\n\n        # whatever next comes here\n        asyncio.run(app.serve())\n        ```\n\n    ## Lifespan\n\n    You can define this startup and shutdown logic using the lifespan parameter of the AioClock instance.\n    It should be as an  AsyncContextManager which get AioClock application as argument.\n    You can find the example below.\n\n    Example:\n        ```python\n            import asyncio\n            from contextlib import asynccontextmanager\n\n            from aioclock import AioClock\n\n            ML_MODEL = [] # just some imaginary component that needs to be started and stopped\n\n\n            @asynccontextmanager\n            async def lifespan(app: AioClock):\n                ML_MODEL.append(2)\n                print(\"UP!\")\n                yield app\n                ML_MODEL.clear()\n                print(\"DOWN!\")\n\n\n            app = AioClock(lifespan=lifespan)\n\n\n            if __name__ == \"__main__\":\n                asyncio.run(app.serve())\n        ```\n\n    Here we are simulating the expensive startup operation of loading the model by putting the (fake)\n    model function in the dictionary with machine learning models before the yield.\n    This code will be executed before the application starts operating, during the startup.\n\n    And then, right after the yield, we unload the model.\n    This code will be executed after the application finishes handling requests, right before the shutdown.\n    This could, for example, release resources like memory, a GPU or some database connection.\n\n    It would also happen when you're stopping your application gracefully,\n    for example, when you're shutting down your container.\n\n    Lifespan could also be synchronous context manager. Check the example below.\n\n\n    Example:\n        ```python\n            from contextlib import contextmanager\n\n            from aioclock import AioClock\n\n            ML_MODEL = []\n\n            @contextmanager\n            def lifespan_sync(sync_app: AioClock):\n                ML_MODEL.append(2)\n                print(\"UP!\")\n                yield sync_app\n                ML_MODEL.clear()\n                print(\"DOWN!\")\n\n            sync_app = AioClock(lifespan=lifespan_sync)\n\n            if __name__ == \"__main__\":\n                asyncio.run(app.serve())\n        ```\n\n    \"\"\"\n\n    def __init__(\n        self,\n        *,\n        lifespan: Optional[\n            Callable[[AioClock], AsyncContextManager[AioClock] | ContextManager[AioClock]]\n        ] = None,\n        limiter: Optional[anyio.CapacityLimiter] = None,\n    ):\n        \"\"\"\n        Initialize AioClock instance.\n        No parameters are needed.\n\n        Attributes:\n            lifespan:\n                A context manager that will be used to handle the startup and shutdown of the application.\n                If not provided, the application will run without any startup and shutdown logic.\n                To understand it better, check the examples and documentation above.\n\n            limiter:\n                Anyio CapacityLimiter. capacity limiter to use to limit the total amount of threads running\n                Limiter that will be used to limit the number of tasks that are running at the same time.\n                If not provided, it will fall back to the default limiter set on Application level.\n                If no limiter is set on Application level, it will fall back to the default limiter set by AnyIO.\n\n        \"\"\"\n        self._groups: list[Group] = []\n        self._app_tasks: list[Task] = []\n        self._limiter = limiter\n        self.lifespan = lifespan\n        group = Group()\n        group._tasks = self._app_tasks\n        self.include_group(group)\n\n    _groups: list[Group]\n    \"\"\"List of groups that will be run by AioClock.\"\"\"\n\n    _app_tasks: list[Task]\n    \"\"\"List of tasks that will be run by AioClock.\"\"\"\n\n    @property\n    def dependencies(self):\n        \"\"\"Dependencies provider that will be used to inject dependencies in tasks.\"\"\"\n        return get_provider()\n\n    def override_dependencies(\n        self, original: Callable[..., Any], override: Callable[..., Any]\n    ) -> None:\n        \"\"\"Override a dependency with a new one.\n\n        params:\n            original:\n                Original dependency that will be overridden.\n            override:\n                New dependency that will override the original one.\n\n        Example:\n            ```python\n            from aioclock import AioClock\n\n            def original_dependency():\n                return 1\n\n            def new_dependency():\n                return 2\n\n            app = AioClock()\n            app.override_dependencies(original=original_dependency, override=new_dependency)\n            ```\n\n        \"\"\"\n        self.dependencies.override(original, override)\n\n    def include_group(self, group: Group) -> None:\n        \"\"\"Include a group of tasks that will be run by AioClock.\n\n        params:\n            group:\n                Group of tasks that will be run together.\n\n        Example:\n            ```python\n            from aioclock import AioClock, Group, Once\n\n            app = AioClock()\n\n            group = Group()\n            @group.task(trigger=Once())\n            async def main():\n                print(\"Hello World\")\n\n            app.include_group(group)\n            ```\n        \"\"\"\n        self._groups.append(group)\n        return None\n\n    def task(self, *, trigger: BaseTrigger, timeout: Optional[float] = None):\n        \"\"\"\n        Decorator to add a task to the AioClock instance.\n        If decorated function is sync, aioclock will run it in a thread pool executor, using AnyIO.\n        But if you try to run the decorated function, it will run in the same thread, blocking the event loop.\n        It is intended to not change all your `sync functions` to coroutine functions,\n            and they can be used outside aioclock, if needed.\n\n        params:\n            trigger: BaseTrigger\n                Trigger that will trigger the task to be running.\n\n            timeout: float | None (defaults to None)\n                Set a timeout for the task.\n                If the task completion took longer than timeout,\n                it will be cancelled and a `TaskTimeoutError` be raised by the Application.\n\n        Example:\n            ```python\n\n            from aioclock import AioClock, Once\n\n            app = AioClock()\n\n            @app.task(trigger=Once())\n            async def main():\n                print(\"Hello World\")\n            ```\n\n        Example:\n            ```python\n\n            from aioclock import AioClock, Once\n\n            app = AioClock()\n\n            @app.task(trigger=Once(), timeout=3)\n            async def main():\n                await some_io_task()\n            ```\n        \"\"\"\n\n        def decorator(func):\n            @wraps(func)\n            async def wrapped_function(*args, **kwargs):\n                if asyncio.iscoroutinefunction(func):\n                    return await func(*args, **kwargs)\n                else:  # run in threadpool to make sure it's not blocking the event loop\n                    return await asyncify(func, limiter=self._limiter)(*args, **kwargs)\n\n            self._app_tasks.append(\n                Task(\n                    func=inject(wrapped_function, dependency_overrides_provider=get_provider()),\n                    trigger=trigger,\n                    timeout=timeout,\n                )\n            )\n            return wrapped_function\n\n        return decorator\n\n    @property\n    def _tasks(self) -> list[Task]:\n        result = flatten_chain([group._tasks for group in self._groups])\n\n        return result\n\n    def _get_shutdown_task(self) -> list[Task]:\n        return [task for task in self._tasks if task.trigger.type_ == Triggers.ON_SHUT_DOWN]\n\n    def _get_startup_task(self) -> list[Task]:\n        return [task for task in self._tasks if task.trigger.type_ == Triggers.ON_START_UP]\n\n    def _get_tasks(self, exclude_type: Union[set[Triggers], None] = None) -> list[Task]:\n        exclude_type = (\n            exclude_type\n            if exclude_type is not None\n            else {Triggers.ON_START_UP, Triggers.ON_SHUT_DOWN}\n        )\n\n        return [task for task in self._tasks if task.trigger.type_ not in exclude_type]\n\n    async def serve(self) -> None:\n        \"\"\"\n        Serves AioClock\n        Run the tasks in the right order.\n        First, run the startup tasks, then run the tasks, and finally run the shutdown tasks.\n        \"\"\"\n        if self.lifespan is None:\n            await self._run_tasks()\n            return\n\n        ctx = self.lifespan(self)\n\n        if isinstance(ctx, AsyncContextManager):\n            async with ctx:\n                await self._run_tasks()\n\n        elif isinstance(ctx, ContextManager):\n            with ctx:\n                await self._run_tasks()\n\n        else:\n            assert_never(ctx)\n\n    async def _run_tasks(self) -> None:\n        try:\n            await asyncio.gather(\n                *(task.run() for task in self._get_startup_task()), return_exceptions=False\n            )\n\n            await asyncio.gather(\n                *(task.run() for task in self._get_tasks()), return_exceptions=False\n            )\n        finally:\n            shutdown_tasks = self._get_shutdown_task()\n            await asyncio.gather(*(task.run() for task in shutdown_tasks), return_exceptions=False)\n"
  },
  {
    "path": "aioclock/custom_types.py",
    "content": "from enum import auto\nfrom typing import Annotated, Literal, Union\n\nfrom annotated_types import Interval\n\nfrom aioclock.utils import StrEnum\n\nEveryT = Literal[\n    \"every monday\",\n    \"every tuesday\",\n    \"every wednesday\",\n    \"every thursday\",\n    \"every friday\",\n    \"every saturday\",\n    \"every sunday\",\n    \"every day\",\n]\n\nSecondT = Annotated[int, Interval(ge=0, le=59)]\nMinuteT = Annotated[int, Interval(ge=0, le=59)]\nHourT = Annotated[int, Interval(ge=0, le=24)]\n\nPositiveNumber = Annotated[Union[int, float], Interval(ge=0)]\n\n\nclass Triggers(StrEnum):\n    CRON = auto()\n    \"\"\"Cron job trigger.\"\"\"\n    EVERY = auto()\n    \"\"\"Every (x) time units, it gets triggered.\"\"\"\n    ONCE = auto()\n    \"\"\"Trigger once, then stop.\"\"\"\n    FOREVER = auto()\n    \"\"\"Keep running, as long as application is running.\"\"\"\n    ON_START_UP = auto()\n    \"\"\"Trigger on application start up.\"\"\"\n    ON_SHUT_DOWN = auto()\n    \"\"\"Trigger on application shut down.\"\"\"\n    AT = auto()\n    \"\"\"Trigger at a specific time.\"\"\"\n\n    OR = auto()\n    \"\"\"Trigger when any of the triggers are met.\"\"\"\n"
  },
  {
    "path": "aioclock/exceptions.py",
    "content": "class BaseAioClockException(Exception):\n    \"\"\"Base exception for aioclock.\"\"\"\n\n\nclass TaskIdNotFound(BaseAioClockException):\n    \"\"\"Task not found in the AioClock app.\"\"\"\n\n\nclass TaskTimeoutError(BaseAioClockException, TimeoutError):\n    \"\"\"A task took longer than its timeout\"\"\"\n"
  },
  {
    "path": "aioclock/ext/__init__.py",
    "content": "\"\"\"\nExtensions for aioclock.\n\nAioClock is very extensible, and you can add your own extensions to it.\nThe extension would allow you to interact with your AioClock instance, from different layers of your application.\nFor instance, the FastAPI plugin allows you to run a specific task immediately from an HTTP API, or see your tasks in an HTTP API, and when they are going to run next.\n\"\"\"\n"
  },
  {
    "path": "aioclock/ext/fast.py",
    "content": "\"\"\"FastAPI extension to manage the tasks of the AioClock instance in HTTP Layer.\n\nUse cases:\n    - Expose the tasks of the AioClock instance in an HTTP API.\n    - Show to your client which task is going to be run next, and at which time.\n    - Run a specific task from an HTTP API immidiately if needed.\n\nTo use FastAPI Extension, please make sure you do `pip install aioclock[fastapi]`.\n\n\"\"\"\n\nfrom typing import Union\nfrom uuid import UUID\n\nfrom aioclock.api import TaskMetadata, get_metadata_of_all_tasks, run_specific_task\nfrom aioclock.app import AioClock\nfrom aioclock.exceptions import TaskIdNotFound\n\ntry:\n    from fastapi.exceptions import HTTPException\n    from fastapi.routing import APIRouter\nexcept ImportError:\n    raise ImportError(\n        \"You need to install fastapi to use aioclock with FastAPI. Please run `pip install aioclock[fastapi]`\"\n    )\n\n\ndef make_fastapi_router(aioclock: AioClock, router: Union[APIRouter, None] = None):\n    \"\"\"Make a FastAPI router that exposes the tasks of the AioClock instance and its external python API in HTTP Layer.\n    You can pass a router to this function, and have dependencies injected in the router, or any authorization logic that you want to have.\n\n\n    params:\n        aioclock: AioClock instance to get the tasks from.\n        router: FastAPI router to add the routes to. If not provided, a new router will be created.\n\n    Example:\n        ```python\n        import asyncio\n        from contextlib import asynccontextmanager\n\n        from fastapi import FastAPI\n\n        from aioclock import AioClock\n        from aioclock.ext.fast import make_fastapi_router\n        from aioclock.triggers import Every, OnStartUp\n\n        clock_app = AioClock()\n\n        @clock_app.task(trigger=OnStartUp())\n        async def startup():\n            print(\"Starting...\")\n\n        @clock_app.task(trigger=Every(seconds=3600))\n        async def foo():\n            print(\"Foo is processing...\")\n\n\n        @asynccontextmanager\n        async def lifespan(app: FastAPI):\n            task = asyncio.create_task(clock_app.serve())\n            yield\n\n            try:\n                task.cancel()\n                await task\n            except asyncio.CancelledError:\n                ...\n\n\n        app = FastAPI(lifespan=lifespan)\n        app.include_router(make_fastapi_router(clock_app))\n\n        if __name__ == \"__main__\":\n            import uvicorn\n            # uvicorn.run(app)\n        ```\n    \"\"\"\n    router = router or APIRouter()\n\n    @router.get(\"/tasks\")\n    async def get_tasks() -> list[TaskMetadata]:\n        return await get_metadata_of_all_tasks(aioclock)\n\n    @router.post(\"/task/{task_id}\")\n    async def run_task(task_id: UUID):\n        try:\n            await run_specific_task(task_id, aioclock)\n        except TaskIdNotFound:\n            raise HTTPException(status_code=404, detail=\"Task not found\")\n\n    return router\n"
  },
  {
    "path": "aioclock/group.py",
    "content": "import asyncio\nimport sys\nfrom functools import wraps\nfrom typing import Optional, TypeVar\n\nfrom asyncer import asyncify\n\nif sys.version_info < (3, 10):\n    from typing_extensions import ParamSpec\nelse:\n    from typing import ParamSpec\n\nimport anyio\nfrom fast_depends import inject\n\nfrom aioclock.provider import get_provider\nfrom aioclock.task import Task\nfrom aioclock.triggers import BaseTrigger\n\nT = TypeVar(\"T\")\nP = ParamSpec(\"P\")\n\n\nclass Group:\n    def __init__(\n        self,\n        *,\n        limiter: Optional[anyio.CapacityLimiter] = None,\n        timeout: Optional[float] = None\n    ):\n        \"\"\"\n        Group of tasks that will be run together.\n\n        Best use case is to have a good modularity and separation of concerns.\n        For example, you can have a group of tasks that are responsible for sending emails.\n        And another group of tasks that are responsible for sending notifications.\n\n        Params:\n            limiter:\n                Anyio CapacityLimiter. capacity limiter to use to limit the total amount of threads running\n                Limiter that will be used to limit the number of tasks that are running at the same time.\n                If not provided, it will fall back to the default limiter set on Application level.\n                If no limiter is set on Application level, it will fall back to the default limiter set by AnyIO.\n\n            timeout:\n                General timeout for the group's tasks.\n                If a task overrides this value, the new value will be used\n                for the task.\n\n        Example:\n            ```python\n\n            from aioclock import Group, AioClock, Forever\n\n            email_group = Group()\n\n            # consider this as different file\n            @email_group.task(trigger=Forever())\n            async def send_email():\n                ...\n\n            # app.py\n            aio_clock = AioClock()\n            aio_clock.include_group(email_group)\n            ```\n\n        Example:\n            ```python\n\n            from aioclock import Group, AioClock, Forever\n\n            email_group = Group(timeout=5)\n\n            # consider this as different file\n            @email_group.task(trigger=Forever())\n            async def send_email():\n                ...\n\n            # app.py\n            aio_clock = AioClock()\n            aio_clock.include_group(email_group)\n            ```\n\n        \"\"\"\n        self._tasks: list[Task] = []\n        self._limiter = limiter\n        self._timeout = timeout\n\n    def task(self, *, trigger: BaseTrigger, timeout: Optional[float] = None):\n        \"\"\"\n        Decorator to add a task to the group.\n        If decorated function is sync, aioclock will run it in a thread pool executor, using AnyIO.\n        But if you try to run the decorated function, it will run in the same thread, blocking the event loop.\n        It is intended to not change all your `sync functions` to coroutine functions,\n            and they can be used outside aioclock, if needed.\n\n        params:\n            trigger: BaseTrigger\n                Trigger that will trigger the task to be running.\n\n            timeout: float | None (defaults to None)\n                Set a timeout for the task.\n                If the task completion took longer than timeout,\n                it will be cancelled and a `TaskTimeoutError` be raised by the Application.\n\n        Example:\n            ```python\n\n            from aioclock import AioClock, Group, Once\n\n            group = Group()\n\n            @group.task(trigger=Once())\n            async def main():\n                print(\"Hello World\")\n\n            app = AioClock()\n            app.include_group(group)\n            ```\n\n        Example:\n            ```python\n\n            from aioclock import AioClock, Group, Once, Every\n\n            group = Group(timeout=5)\n\n            @group.task(trigger=Every(seconds=5))\n            async def main():\n                print(\"Hello World\")\n\n            @group.task(trigger=Once(), timeout=4)  # this task will get 4 as timeout\n            async def main():\n                print(\"Hello World\")\n\n            app = AioClock()\n            app.include_group(group)\n            ```\n        \"\"\"\n\n        def decorator(func):\n            @wraps(func)\n            async def wrapped_function(*args, **kwargs):\n                if asyncio.iscoroutinefunction(func):\n                    return await func(*args, **kwargs)\n                else:  # run in threadpool to make sure it's not blocking the event loop\n                    return await asyncify(func, limiter=self._limiter)(*args, **kwargs)\n\n            to = self._timeout\n            if timeout is not None:\n                to = timeout\n            self._tasks.append(\n                Task(\n                    func=inject(wrapped_function, dependency_overrides_provider=get_provider()),\n                    trigger=trigger,\n                    timeout=to,\n                )\n            )\n\n            return wrapped_function\n\n        return decorator\n\n    async def _run(self):\n        \"\"\"\n        Just for purpose of being able to run all task in group\n        Private method, should not be used outside the library\n        \"\"\"\n        await asyncio.gather(\n            *(task.run() for task in self._tasks),\n            return_exceptions=False,\n        )\n"
  },
  {
    "path": "aioclock/logger.py",
    "content": "import logging\n\nlogger = logging.getLogger(\"aioclock\")\n"
  },
  {
    "path": "aioclock/provider.py",
    "content": "from functools import lru_cache\n\nfrom fast_depends import Provider\n\n\n@lru_cache\ndef get_provider():\n    \"\"\"Return a Provider instance, which is singleton.\n    This singleton is used to inject dependencies in tasks.\n    \"\"\"\n    return Provider()\n"
  },
  {
    "path": "aioclock/py.typed",
    "content": ""
  },
  {
    "path": "aioclock/task.py",
    "content": "\"\"\"\nAioclock wrap your functions with a task object,\nand append the task to the list of tasks in the AioClock instance.\nAfter collecting all the tasks from decorated functions,\naioclock serve them in order it has to be (startup, normal, shutdown).\n\nThese tasks keep running forever until the trigger's method `should_trigger` returns False.\n\"\"\"\n\nimport asyncio\nfrom dataclasses import dataclass, field\nfrom datetime import datetime, timedelta, timezone\nfrom typing import Any, Awaitable, Callable, Optional\nfrom uuid import UUID, uuid4\n\nfrom aioclock.exceptions import TaskTimeoutError\nfrom aioclock.logger import logger\nfrom aioclock.triggers import BaseTrigger\n\nUTC = timezone.utc\n\n\n@dataclass\nclass Task:\n    \"\"\"Task that will be run by AioClock.\n    Which always has a function and a trigger.\n    This is internally used, when you decorate your function with `aioclock.task`.\n\n    Attributes:\n        func: Callable[..., Awaitable[Any]]: Decorated function that will be run by AioClock.\n        trigger: BaseTrigger: Trigger that will be used to run the function.\n        id: UUID: Task ID that is unique for each task, and changes every time you run the aioclock app.\n            In the future, we might store task ID in a database, so that it always remains same.\n\n    \"\"\"\n\n    func: Callable[..., Awaitable[Any]]\n\n    trigger: BaseTrigger\n\n    timeout: Optional[float] = None\n    id: UUID = field(default_factory=uuid4)\n\n    async def run(self):\n        \"\"\"\n        Run the task, and handle the exceptions.\n        If the task fails, log the error with exception, but keep running the tasks.\n        \"\"\"\n        while self.trigger.should_trigger():\n            try:\n                next_trigger = await self.trigger.get_waiting_time_till_next_trigger()\n                if next_trigger is not None:\n                    logger.info(f\"Triggering next task {self.func.__name__} in {next_trigger}\")\n                    self.trigger.expected_trigger_time = datetime.now(UTC) + timedelta(\n                        seconds=next_trigger\n                    )\n                await self.trigger.trigger_next()\n\n                if self.timeout is not None:\n                    logger.debug(\n                        f\"Running task {self.func.__name__} with timeout of {self.timeout}\"\n                    )\n                    try:\n                        await asyncio.wait_for(self.func(), self.timeout)\n                    except asyncio.TimeoutError:\n                        raise TaskTimeoutError(\n                            f\"Task {self.func.__name__!r} took longer than {self.timeout} seconds to run!\"\n                        ) from None\n                else:\n                    logger.debug(f\"Running task {self.func.__name__}\")\n                    await self.func()\n            except Exception as error:\n                # Log the error, but keep running the tasks.\n                # don't crash the whole application.\n                logger.exception(f\"Error running task {self.func.__name__}: {error}\")\n\n        self.trigger.expected_trigger_time = None\n"
  },
  {
    "path": "aioclock/triggers.py",
    "content": "\"\"\"\nTriggers are used to determine when the event should be triggered. It can be based on time, or some other condition.\nYou can create custom triggers by inheriting from `BaseTrigger` class.\n\n!!! info \"Don't run CPU intensitve or thread-block IO task \"\n    AioClock's trigger are all running in async, only on one CPU.\n    So, if you run a CPU intensive task, or a task that blocks the thread, then it will block the entire event loop.\n    If you have a sync IO task, then it's recommended to use `run_in_executor` to run the task in a separate thread.\n    Or use similiar libraries like `asyncer` or `trio` to run the task in a separate thread.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport asyncio\nfrom abc import ABC, abstractmethod\nfrom copy import deepcopy\nfrom datetime import datetime, timedelta\nfrom typing import Annotated, Generic, Literal, TypeVar, Union\n\nimport zoneinfo\nfrom annotated_types import Interval\nfrom croniter import croniter\nfrom dateutil.relativedelta import relativedelta\nfrom pydantic import BaseModel, Field, PositiveInt, model_validator\nfrom typing_extensions import deprecated\n\nfrom aioclock.custom_types import PositiveNumber, Triggers\n\nTriggerTypeT = TypeVar(\"TriggerTypeT\")\n\n\nWEEKDAY_MAPPER: dict[\n    Literal[\n        \"every monday\",\n        \"every tuesday\",\n        \"every wednesday\",\n        \"every thursday\",\n        \"every friday\",\n        \"every saturday\",\n        \"every sunday\",\n        \"every day\",\n    ],\n    int,\n] = {\n    \"every monday\": 0,\n    \"every tuesday\": 1,\n    \"every wednesday\": 2,\n    \"every thursday\": 3,\n    \"every friday\": 4,\n    \"every saturday\": 5,\n    \"every sunday\": 6,\n}\n\n\nclass BaseTrigger(BaseModel, ABC, Generic[TriggerTypeT]):\n    \"\"\"\n    Base class for all triggers.\n    A trigger is a way to determine when the event should be triggered. It can be based on time, or some other condition.\n\n\n    The way trigger are used is as follows:\n        1. An async function which is a task, is decorated with framework, and trigger is the arguement for the decorator\n        2. `get_waiting_time_till_next_trigger` is called to get the time in seconds, after which the event should be triggered.\n        3. If the time is not None, then it logs the time that is predicted for the event to be triggered.\n        4. `trigger_next` is called immediately after that, which triggers the event.\n\n    You can create trigger by yourself, by inheriting from `BaseTrigger` class.\n\n    Example:\n        ```python\n        from aioclock.triggers import BaseTrigger\n        from typing import Literal\n\n        class Forever(BaseTrigger[Literal[\"Forever\"]]):\n            type_: Literal[\"Forever\"] = \"Forever\"\n\n            def should_trigger(self) -> bool:\n                return True\n\n            async def trigger_next(self) -> None:\n                return None\n\n            async def get_waiting_time_till_next_trigger(self):\n                if self.should_trigger():\n                    return 0\n                return None\n        ```\n\n    Attributes:\n        type_: Type of the trigger. It is a string, which is used to identify the trigger's name.\n            You can change the type by using `Generic` type when inheriting from `BaseTrigger`.\n\n        expected_trigger_time: Expected time when the event should be triggered. This gets updated\n            by Task Runner. It can be used on API layer, to know when the event is expected to be triggered.\n    \"\"\"\n\n    type_: TriggerTypeT\n\n    expected_trigger_time: Union[datetime, None] = None\n\n    @abstractmethod\n    async def trigger_next(self) -> None:\n        \"\"\"\n        `trigger_next` keep track of the event, and triggers the event.\n        The function shall return when the event is triggered and should be executed.\n        \"\"\"\n\n    def should_trigger(self) -> bool:\n        \"\"\"\n        `should_trigger` checks if the event should be triggered or not.\n        If not, then the event will not be triggered anymore.\n        You can save the state of the trigger and task inside the instance, and then check if the event should be triggered or not.\n        For instance, in `LoopCounter` trigger, it keeps track of the number of times the event has been triggered,\n        and then checks if the event should be triggered or not.\n        \"\"\"\n        return True\n\n    @abstractmethod\n    async def get_waiting_time_till_next_trigger(self) -> Union[float, None]:\n        \"\"\"\n        Returns the time in seconds, after which the event should be triggered.\n        Returns None, if the event should not trigger anymore.\n        \"\"\"\n        ...\n\n\nclass Forever(BaseTrigger[Literal[Triggers.FOREVER]]):\n    \"\"\"A trigger that is always triggered immediately.\n\n    Example:\n        ```python\n\n            from aioclock import AioClock, Forever\n\n            app = AioClock()\n\n            # instead of this:\n            async def my_task():\n                while True:\n                    try:\n                        await asyncio.sleep(3)\n                        1/0\n                    except DivisionByZero:\n                        pass\n\n            # use this:\n            @app.task(trigger=Forever())\n            async def my_task():\n                await asyncio.sleep(3)\n                1/0\n        ```\n\n    Attributes:\n        type_: Type of the trigger. It is a string, which is used to identify the trigger's name.\n            You can change the type by using `Generic` type when inheriting from `BaseTrigger`.\n\n    \"\"\"\n\n    type_: Literal[Triggers.FOREVER] = Triggers.FOREVER\n\n    def should_trigger(self) -> bool:\n        return True\n\n    async def trigger_next(self) -> None:\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        return 0\n\n\nclass LoopController(BaseTrigger, ABC, Generic[TriggerTypeT]):\n    \"\"\"\n    Base class for all triggers that have loop control.\n\n    Attributes:\n        type_: Type of the trigger. It is a string, which is used to identify the trigger's name.\n            You can change the type by using `Generic` type when inheriting from `LoopController`.\n\n        max_loop_count: The maximum number of times the event should be triggered.\n            If set to 3, then 4th time the event will not be triggered.\n            If set to None, it will keep running forever.\n            This is available for all triggers that inherit from `LoopController`.\n\n        _current_loop_count: Current loop count, which is used to keep track of the number of times the event has been triggered.\n            Private attribute, should not be accessed directly.\n            This is available for all triggers that inherit from `LoopController`.\n    \"\"\"\n\n    type_: TriggerTypeT\n    _current_loop_count: int = 0\n    max_loop_count: Union[PositiveInt, None] = None\n\n    @model_validator(mode=\"after\")\n    def validate_loop_control(self):\n        if \"_current_loop_count\" in self.model_fields_set:\n            raise ValueError(\"_current_loop_count is a private attribute, should not be provided.\")\n        return self\n\n    def _increment_loop_counter(self) -> None:\n        self._current_loop_count += 1\n\n    def should_trigger(self) -> bool:\n        if self.max_loop_count is None:\n            return True\n        if self.max_loop_count > self._current_loop_count:\n            return True\n        return False\n\n    async def get_waiting_time_till_next_trigger(self):\n        return 0\n\n\nclass Once(LoopController[Literal[Triggers.ONCE]]):\n    \"\"\"A trigger that is triggered only once. It is used to trigger the event only once, and then stop.\n\n    Example:\n        ```python\n        from aioclock import AioClock, Once\n        app = AioClock()\n\n        app.task(trigger=Once())\n        async def task():\n            print(\"Hello World!\")\n        ```\n    \"\"\"\n\n    type_: Literal[Triggers.ONCE] = Triggers.ONCE\n    max_loop_count: Literal[1] = 1\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        if self._current_loop_count == 0:\n            return 0\n        return None\n\n\n@deprecated(\n    \"Use `lifespan` instead of using Triggers for startup/shutdown events. This feature be removed in version 1.0.0\"\n)\nclass OnStartUp(LoopController[Literal[Triggers.ON_START_UP]]):\n    \"\"\"Just like Once, but it triggers the event only once, when the application starts up.\n\n    Example:\n        ```python\n        from aioclock import AioClock, OnStartUp\n        app = AioClock()\n\n        app.task(trigger=OnStartUp())\n        async def task():\n            print(\"Hello World!\")\n        ```\n    \"\"\"\n\n    type_: Literal[Triggers.ON_START_UP] = Triggers.ON_START_UP\n    max_loop_count: Literal[1] = 1\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        if self._current_loop_count == 0:\n            return 0\n        return None\n\n\n@deprecated(\n    \"Use `lifespan` instead of using Triggers for startup/shutdown events. This feature be removed in version 1.0.0\"\n)\nclass OnShutDown(LoopController[Literal[Triggers.ON_SHUT_DOWN]]):\n    \"\"\"Just like Once, but it triggers the event only once, when the application shuts down.\n\n    Example:\n        ```python\n        from aioclock import AioClock, OnShutDown\n        app = AioClock()\n\n        app.task(trigger=OnShutDown())\n        async def task():\n            print(\"Hello World!\")\n        ```\n    \"\"\"\n\n    type_: Literal[Triggers.ON_SHUT_DOWN] = Triggers.ON_SHUT_DOWN\n    max_loop_count: Literal[1] = 1\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        if self._current_loop_count == 0:\n            return 0\n        return None\n\n\nclass Every(LoopController[Literal[Triggers.EVERY]]):\n    \"\"\"A trigger that is triggered every x time units.\n\n    Example:\n        ```python\n        from aioclock import AioClock, Every\n        app = AioClock()\n\n        app.task(trigger=Every(seconds=3))\n        async def task():\n            print(\"Hello World!\")\n        ```\n\n    Attributes:\n        first_run_strategy: Strategy to use for the first run.\n            If `immediate`, then the event will be triggered immediately,\n                and then wait for the time to trigger the event again.\n            If `wait`, then the event will wait for the time to trigger the event for the first time.\n\n        seconds: Seconds to wait before triggering the event.\n        minutes: Minutes to wait before triggering the event.\n        hours: Hours to wait before triggering the event.\n        days: Days to wait before triggering the event.\n        weeks: Weeks to wait before triggering the event.\n\n        max_loop_count: The maximum number of times the event should be triggered.\n            If set to 3, then 4th time the event will not be triggered.\n            If set to None, it will keep running forever.\n            This is available for all triggers that inherit from `LoopController`.\n\n    \"\"\"\n\n    type_: Literal[Triggers.EVERY] = Triggers.EVERY\n    first_run_strategy: Literal[\"immediate\", \"wait\"] = \"wait\"\n    seconds: Union[PositiveNumber, None] = None\n    minutes: Union[PositiveNumber, None] = None\n    hours: Union[PositiveNumber, None] = None\n    days: Union[PositiveNumber, None] = None\n    weeks: Union[PositiveNumber, None] = None\n    max_loop_count: Union[PositiveInt, None] = None\n\n    @model_validator(mode=\"after\")\n    def validate_time_units(self):\n        if (\n            self.seconds is None\n            and self.minutes is None\n            and self.hours is None\n            and self.days is None\n            and self.weeks is None\n        ):\n            raise ValueError(\"At least one time unit must be provided.\")\n\n        return self\n\n    @property\n    def to_seconds(self) -> float:\n        result = self.seconds or 0\n        if self.weeks is not None:\n            result += self.weeks * WEEK_TO_SECOND\n        if self.days is not None:\n            result += self.days * DAY_TO_SECOND\n        if self.hours is not None:\n            result += self.hours * HOUR_TO_SECOND\n        if self.minutes is not None:\n            result += self.minutes * MINUTE_TO_SECOND\n\n        return result\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        if self._current_loop_count == 1 and self.first_run_strategy == \"immediate\":\n            return None\n        await asyncio.sleep(self.to_seconds)\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        # not incremented yet, so the counter is 0\n        if self._current_loop_count == 0 and self.first_run_strategy == \"immediate\":\n            return 0\n\n        if self.should_trigger():\n            return self.to_seconds\n        return None\n\n\nMINUTE_TO_SECOND = 60\nHOUR_TO_SECOND = 60 * MINUTE_TO_SECOND\nDAY_TO_SECOND = 24 * HOUR_TO_SECOND\nWEEK_TO_SECOND = 7 * DAY_TO_SECOND\n\n\nclass At(LoopController[Literal[Triggers.AT]]):\n    \"\"\"A trigger that is triggered at a specific time.\n\n    Example:\n        ```python\n\n        from aioclock import AioClock, At\n\n        app = AioClock()\n\n        @app.task(trigger=At(hour=12, minute=30, tz=\"Asia/Kolkata\"))\n        async def task():\n            print(\"Hello World!\")\n        ```\n\n    Attributes:\n        second: Second to trigger the event.\n        minute: Minute to trigger the event.\n        hour: Hour to trigger the event.\n        at: Day of week to trigger the event. You would get the in-line typing support when using the trigger.\n        tz: Timezone to use for the event.\n\n        max_loop_count: The maximum number of times the event should be triggered.\n            If set to 3, then 4th time the event will not be triggered.\n            If set to None, it will keep running forever.\n            This is available for all triggers that inherit from `LoopController`.\n\n\n    \"\"\"\n\n    type_: Literal[Triggers.AT] = Triggers.AT\n    max_loop_count: Union[PositiveInt, None] = None\n    second: Annotated[int, Interval(ge=0, le=59)] = 0\n    minute: Annotated[int, Interval(ge=0, le=59)] = 0\n    hour: Annotated[int, Interval(ge=0, le=24)] = 0\n    at: Literal[\n        \"every monday\",\n        \"every tuesday\",\n        \"every wednesday\",\n        \"every thursday\",\n        \"every friday\",\n        \"every saturday\",\n        \"every sunday\",\n        \"every day\",\n    ] = \"every day\"\n    tz: str\n\n    @model_validator(mode=\"after\")\n    def validate_time_units(self):\n        if self.second is None and self.minute is None and self.hour is None:\n            raise ValueError(\"At least one time unit must be provided.\")\n\n        if self.tz is not None:\n            try:\n                zoneinfo.ZoneInfo(self.tz)\n            except Exception as error:\n                raise ValueError(f\"Invalid timezone provided: {error}\")\n\n        return self\n\n    def _shift_to_declared_weekday(self, target_time: datetime, tz_aware_now: datetime):\n        if self.at == \"every day\":\n            if tz_aware_now > target_time:  # if the time is already passed, then shift to next day\n                target_time += timedelta(days=1)\n            return target_time\n\n        target_weekday: int = WEEKDAY_MAPPER[self.at]\n        if tz_aware_now > target_time:  # if the time is already passed, then shift to next week\n            return target_time + relativedelta(weeks=1)\n\n        days_ahead = abs(target_weekday - tz_aware_now.weekday())\n        return target_time + timedelta(days_ahead)\n\n    def _get_next_ts(self, now: datetime) -> float:\n        target_time = deepcopy(now).replace(\n            hour=self.hour, minute=self.minute, second=self.second, microsecond=0\n        )\n        target_time = self._shift_to_declared_weekday(target_time, now)\n        return (target_time - now).total_seconds()\n\n    async def get_waiting_time_till_next_trigger(self, now: Union[datetime, None] = None):\n        if now is None:\n            now = datetime.now(tz=zoneinfo.ZoneInfo(self.tz))\n\n        sleep_for = self._get_next_ts(now)\n        return sleep_for\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        await asyncio.sleep(await self.get_waiting_time_till_next_trigger())\n\n\nclass Cron(LoopController[Literal[Triggers.CRON]]):\n    \"\"\"A trigger that is triggered at a specific time, using cron job format.\n    If you are not familiar with the cron format, you may read about in [this wikipedia article](https://en.wikipedia.org/wiki/Cron).\n    Or if you need an online tool to generate cron job, you may use [crontab.guru](https://crontab.guru/).\n\n    Example:\n        ```python\n        from aioclock import AioClock, Cron\n\n        app = AioClock()\n\n        @app.task(trigger=Cron(cron=\"0 12 * * *\", tz=\"Asia/Kolkata\"))\n        async def task():\n            print(\"Hello World!\")\n        ```\n\n    Attributes:\n        cron: Cron job format to trigger the event.\n        tz: Timezone to use for the event.\n\n        max_loop_count: The maximum number of times the event should be triggered.\n            If set to 3, then 4th time the event will not be triggered.\n            If set to None, it will keep running forever.\n            This is available for all triggers that inherit from `LoopController`.\n\n    \"\"\"\n\n    type_: Literal[Triggers.CRON] = Triggers.CRON\n    max_loop_count: Union[PositiveInt, None] = None\n    cron: str\n    tz: str\n\n    @model_validator(mode=\"after\")\n    def validate_time_units(self):\n        if self.tz is not None:\n            try:\n                zoneinfo.ZoneInfo(self.tz)\n            except Exception as error:\n                raise ValueError(f\"Invalid timezone provided: {error}\")\n\n        if croniter.is_valid(self.cron) is False:\n            raise ValueError(\"Invalid cron format provided.\")\n        return self\n\n    async def get_waiting_time_till_next_trigger(self, now: Union[datetime, None] = None):\n        if now is None:\n            now = datetime.now(tz=zoneinfo.ZoneInfo(self.tz))\n\n        cron_iter = croniter(self.cron, now)\n        next_dt: datetime = cron_iter.get_next(datetime)\n        return (next_dt - now).total_seconds()\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        await asyncio.sleep(await self.get_waiting_time_till_next_trigger())\n\n\nclass OrTrigger(LoopController[Literal[Triggers.OR]]):\n    \"\"\"\n    A trigger that triggers the event if any of the inner triggers are met.\n\n    Example:\n        ```python\n        from aioclock import AioClock, OrTrigger, Every, At\n\n        app = AioClock()\n\n        @app.task(trigger=OrTrigger(triggers=[Every(seconds=3), At(hour=12, minute=30, tz=\"Asia/Kolkata\")]))\n        async def task():\n            print(\"Hello World!\")\n        ```\n\n    Not that any trigger used with OrTrigger, is fully respected, hence if you have two trigger with `max_loop_count=1`,\n        then each trigger will be triggered only once, and then stop, which result in the OrTrigger run only twice.\n        Check example to understand this intended behaviour.\n\n    Example:\n        ```python\n        from aioclock import AioClock, OrTrigger, Every, At\n\n        app = AioClock()\n\n        @app.task(trigger=OrTrigger( # this get triggered 20 times because :...\n            triggers=[\n                Every(seconds=3, max_loop_count=10), # will trigger the event 10 times\n                At(hour=12, minute=30, tz=\"Asia/Kolkata\", max_loop_count=10) # will trigger the event 10 times\n            ]\n        ))\n        async def task():\n            print(\"Hello World!\")\n        ```\n\n    Attributes:\n        triggers: List of triggers to use.\n        max_loop_count: The maximum number of times the event should be triggered.\n            If set to 3, then 4th time the event will not be triggered.\n            If set to None, it will keep running forever.\n            This is available for all triggers that inherit from `LoopController`.\n\n    \"\"\"\n\n    type_: Literal[Triggers.OR] = Triggers.OR\n    triggers: list[TriggerT]\n    max_loop_count: Union[PositiveInt, None] = None\n\n    def should_trigger(self) -> bool:\n        all_triggers = {trigger.should_trigger() for trigger in self.triggers}\n        if all_triggers == {False}:\n            return False  # if all inner triggers should not trigger, then this shouldn't too.\n        return super().should_trigger()\n\n    async def find_closest_trigger(self) -> tuple[BaseTrigger, float | None]:\n        triggers_with_next_trigger: list[tuple[BaseTrigger, float]] = []\n\n        for trigger in self.triggers:\n            if trigger.should_trigger():\n                next_trigger = await trigger.get_waiting_time_till_next_trigger()\n                if next_trigger is None:\n                    # just return it as this should be executed immediately\n                    return trigger, next_trigger\n                triggers_with_next_trigger.append((trigger, next_trigger))\n\n        return min(triggers_with_next_trigger, key=lambda x: x[1])\n\n    async def trigger_next(self) -> None:\n        self._increment_loop_counter()\n        next_trigger, _ = await self.find_closest_trigger()\n        await next_trigger.trigger_next()\n        return None\n\n    async def get_waiting_time_till_next_trigger(self):\n        _, to_sleep = await self.find_closest_trigger()\n        return to_sleep\n\n\nTriggerT = Annotated[\n    Union[\n        Forever,\n        Once,\n        Every,\n        At,\n        OnStartUp,\n        OnShutDown,\n        Cron,\n        OrTrigger,\n    ],\n    Field(discriminator=\"type_\"),\n]\n"
  },
  {
    "path": "aioclock/utils.py",
    "content": "from enum import Enum\nfrom itertools import chain\nfrom typing import Iterable, TypeVar\n\nT = TypeVar(\"T\")\n\n\ndef flatten_chain(matrix: list[Iterable[T]]) -> list[T]:\n    return list(chain.from_iterable(matrix))\n\n\nclass StrEnum(str, Enum):\n    \"\"\"\n    StrEnum subclasses that create variants using `auto()` will have values equal to their names\n\n    Enums inheriting from this class that set values using `enum.auto()` will have variant values\n        equal to their names\n    \"\"\"\n\n    @staticmethod\n    def _generate_next_value_(name: str, start: int, count: int, last_values: list[str]) -> str:\n        \"\"\"\n        Uses the name as the automatic value, rather than an integer\n        See https://docs.python.org/3/library/enum.html#using-automatic-values for reference\n        \"\"\"\n        return name\n\n    def __str__(self) -> str:\n        return str(self.value)\n"
  },
  {
    "path": "deploy_docs.py",
    "content": "import logging\nimport subprocess\nimport sys\n\nfrom aioclock import __version__\n\nlogger = logging.getLogger(__name__)\n\n\ndef run_command(command: str) -> None:\n    try:\n        result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)\n        print(result.stdout)\n    except subprocess.CalledProcessError:\n        logger.exception(\"Error executing command\")\n        sys.exit(1)\n\n\nif __name__ == \"__main__\":\n    command = \"mike set-default latest\"\n    run_command(command)\n\n    command = f\"mike deploy --push --update-aliases {__version__} latest\"\n    run_command(command)\n"
  },
  {
    "path": "docs/alternative.md",
    "content": "# AioClock VS Alternatives\n\nThere are other alternatives for scheduling as well.\nThis section contains comparisons between AioClock\nand other scheduling tools.\nCredit to Rocketry library, as the comparison is inspired by that.\n\nFeatures unique for **AioClock**:\n\n- **Simplicity**: With being super-simple, it's very easy to extend the library as you wish, which is not the usual case with other solutions!\n- **Trigger-based scheduling**: Trigger based scheduling allows flexibility, making it very easy to run a task at a certain time in future.\n- **Dependency Injection System**: Just like FastAPI, AioClock has a very similiar injection system which you can use to decouple your dependency.\n- **Declarative Syntax**: AioClock promotes declarative syntax which makes the library easy to use.\n\n## AioClock vs Rocketry\n\nRocketry is a modern statement-based scheduling framework for Python. It is simple, clean and extensive. It is suitable for small and big projects.\n\nWhen **AioClock** might be a better choice:\n\n- You don't want to be dependent to other unnecessary libraries like [redbird](https://github.com/Miksus/red-bird)\n- You need a truly light weight solution.\n- You are using Pydantic v2.\n- Type safety is important to you. All triggers are type safe, but some statements are stringly typed in rocketry.\n- You need more reliable and preditcable time based scheduling that logs when the next event is going to be triggered.\n\nWhen **Rocketry** might be a better choice:\n\n- You need a task pipelining that is heavily cpu intensive.\n- You have heavy cpu bound tasks\n- You are still using Pydantic v1.\n\n!!! success \"Coming next...\"\n\n    In future versions, aioclock will feature a more advanced architecture, leveraging multiprocessing to handle heavy tasks efficiently.\n\n## AioClock vs Crontab\n\nCrontab is a scheduler for Unix-like operating systems. It is light weight and it is able to run tasks (or jobs) periodically, ie. hourly, weekly or on fixed dates.\n\nWhen **AioClock** might be a better choice:\n\n- You are building a system and not just running individual scripts.\n- You need task pipelining.\n- You need more complex and custom scheduling.\n- You are not familiar Unix-Linux or you work with Windows.\n- You need dependency injection on top of your framework layer.\n\nWhen **Crontab** might be a better choice:\n\n- If you need a truly light weight solution.\n- You are not familiar with Python.\n- You only want to run scripts independently at given periods.\n\n## AioClock vs APScheduler\n\nAPScheduler is a relatively simple scheduler library for Python.\nIt provides Cron-style scheduling and some interval based scheduling.\n\nWhen **AioClock** might be a better choice:\n\n- You are building an automation system.\n- You need more complex and customized scheduling.\n- You need to pipeline tasks.\n- You need dependency injection on top of your framework layer.\n\nWhen **APScheduler** might be a better choice:\n\n- You wish to have the tasks stored in a database (and not in Python code)\n\n!!! info \"You can do this by yourself already...\"\n\n    There is already External APIs from library that you can use, to implement storing task metadata on a database.\n    It is very easy, but aioclock might actually not do it, to not couple library to a dependency.\n    Read about [how to use the external API](api/external_api.md).\n\n## AioClock vs Celery\n\nCelery is a task queue system meant for distributed execution and\nscheduling background tasks for web back-ends.\n\nWhen **AioClock** might be a better choice:\n\n- You are building an automation system.\n- You need more complex and customized scheduling.\n- You work with Windows.\n- You want to fully control your broker behavior, and have high flexability.\n- You need dependency injection on top of your framework layer.\n\nWhen **Celery** might be a better choice:\n\n- You are running background tasks for web servers.\n- You are not very familiar with message brokers, and you need very easy solution that abstract away all details.\n\n!!! info \"Integrate broker is easier than you can imagine, with aioclock!\"\n\n    Celery works via task queues but such mechanism could be implemented to AioClock as well by creating a `once trigger` that reads from queue. You may make this as decorator and even create new libraries using AioClock.\n    For implementation details, see [how to integrate a broker into AioClock App](examples/brokers.md).\n\n## AioClock vs Airflow\n\nAirflow is a a workflow management system used heavily\nin data pipelines. It has a scheduler and a built-in monitor.\n\nWhen **AioClock** might be a better choice:\n\n- You work with Windows.\n- You need something that is easy to set up and quick to get produtive with.\n- You are building an application.\n- You want more customization.\n\nWhen **Airflow** might be a better choice:\n\n- You are building standard data pipelines.\n- You would like to have more out-of-the-box.\n- You need distributed execution.\n- You work in data engineering.\n\n## AioClock vs FastStream\n\nFastStream is a powerful and easy-to-use Python framework for building asynchronous services interacting with event streams such as Apache Kafka, RabbitMQ, NATS and Redis.\n\nWhen **AioClock** might be a better choice:\n\n- You need more complex and customized scheduling.\n- You need high flexability and low level APIs of your broker.\n\nWhen **FastStream** might be a better choice:\n\n- You are not very familiar with message brokers, and you need very easy solution that abstract away all details.\n- You need auto generated asyncapi documentation\n- You are building a distributed data streaming application\n\n!!! info \"They can be used together...\"\n\n    Note that you can use both beside each other, just like FastAPI. All you'd have to do is to serve both application at same time.\n"
  },
  {
    "path": "docs/api/external_api.md",
    "content": "::: aioclock.api\n"
  },
  {
    "path": "docs/api/getting_started.md",
    "content": "::: aioclock.app\n::: aioclock.group\n"
  },
  {
    "path": "docs/api/plugin.md",
    "content": "::: aioclock.ext\n::: aioclock.ext.fast\n"
  },
  {
    "path": "docs/api/task.md",
    "content": "::: aioclock.task\n"
  },
  {
    "path": "docs/api/triggers.md",
    "content": "# Triggers\n\n::: aioclock.triggers\n"
  },
  {
    "path": "docs/diagrams/aioclock.excalidraw",
    "content": "{\n  \"type\": \"excalidraw\",\n  \"version\": 2,\n  \"source\": \"https://excalidraw.com\",\n  \"elements\": [\n    {\n      \"type\": \"arrow\",\n      \"version\": 1870,\n      \"versionNonce\": 562660048,\n      \"index\": \"aR\",\n      \"isDeleted\": false,\n      \"id\": \"umIUdT2Pg_dV6Yj3OxRkn\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 631.8702637667776,\n      \"y\": 56.86698670706318,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 260.23891777104006,\n      \"height\": 28.060608173103617,\n      \"seed\": 400598064,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"URbMlcmauXmJNE4154eWu\",\n        \"focus\": -0.14154036538490047,\n        \"gap\": 10.016775821461238,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"b3EdqBYxnZK7_xrKoWpEZ\",\n        \"focus\": -0.24081634913640015,\n        \"gap\": 1.0371662412074443,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          260.23891777104006,\n          28.060608173103617\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 646,\n      \"versionNonce\": 2009813200,\n      \"index\": \"aS\",\n      \"isDeleted\": false,\n      \"id\": \"jGMhw9Odj78Yt6mAP27K3\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.09974755060020257,\n      \"x\": 665.3042807320827,\n      \"y\": 36.30932819124689,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 171.17919658226998,\n      \"height\": 21.098102858351695,\n      \"seed\": 3246640,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757251178,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 16.878482286681354,\n      \"fontFamily\": 5,\n      \"text\": \"def include_group()\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"def include_group()\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1610,\n      \"versionNonce\": 2058722512,\n      \"index\": \"aa4\",\n      \"isDeleted\": false,\n      \"id\": \"b3EdqBYxnZK7_xrKoWpEZ\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 893.1463477790252,\n      \"y\": -116.12620250637502,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffec99\",\n      \"width\": 891.0632487039327,\n      \"height\": 383.0330657293356,\n      \"seed\": 734090960,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"umIUdT2Pg_dV6Yj3OxRkn\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1399,\n      \"versionNonce\": 1357949136,\n      \"index\": \"aa8\",\n      \"isDeleted\": false,\n      \"id\": \"T8_mi4ZCb-ZGtGclGYrQb\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 969.6874368433648,\n      \"y\": -100.65652553224646,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"transparent\",\n      \"width\": 96.32254493525791,\n      \"height\": 41.85028122224372,\n      \"seed\": 1468254768,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 33.48022497779499,\n      \"fontFamily\": 5,\n      \"text\": \"Group\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Group\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 1336,\n      \"versionNonce\": 344133328,\n      \"index\": \"aaG\",\n      \"isDeleted\": false,\n      \"id\": \"1-nChtDmy9xBSz25x5Tdy\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 919.9022234788671,\n      \"y\": -34.98141186613114,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 172.41095637580733,\n      \"height\": 78,\n      \"seed\": 1465639632,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"Ug96RmiLOJxVdqw8JLOl4\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"ICGWc2_oP7ooflk821IPB\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"dREYVkyhboXiS53A17F-t\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1340,\n      \"versionNonce\": 1155052752,\n      \"index\": \"aaO\",\n      \"isDeleted\": false,\n      \"id\": \"Ug96RmiLOJxVdqw8JLOl4\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 968.5321079585611,\n      \"y\": -10.460148756903092,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 74.94570922851562,\n      \"height\": 28.957473781543904,\n      \"seed\": 1418761776,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Task 1\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"1-nChtDmy9xBSz25x5Tdy\",\n      \"originalText\": \"Task 1\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 1640,\n      \"versionNonce\": 995960528,\n      \"index\": \"aaV\",\n      \"isDeleted\": false,\n      \"id\": \"679z3nf1w9xVLbf3f8AdN\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1233.3981057694639,\n      \"y\": -67.91630887978769,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 190.0690779031065,\n      \"height\": 78,\n      \"seed\": 446189616,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"ZovWm7KTns9gYTw6Alczq\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"gBeFANJTXm0QDmAM0WzrH\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"WYORfi6MQ5vqe_kGKqXHJ\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1612,\n      \"versionNonce\": 951045328,\n      \"index\": \"aad\",\n      \"isDeleted\": false,\n      \"id\": \"ZovWm7KTns9gYTw6Alczq\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1287.7811818248304,\n      \"y\": -43.39504577055965,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 81.26838684082031,\n      \"height\": 28.957473781543904,\n      \"seed\": 1300154928,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Task 2\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"679z3nf1w9xVLbf3f8AdN\",\n      \"originalText\": \"Task 2\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 1669,\n      \"versionNonce\": 2020294352,\n      \"index\": \"aal\",\n      \"isDeleted\": false,\n      \"id\": \"vsA_CIfMv5pa_E8ntMeiT\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1340.6872159208458,\n      \"y\": 55.18372300040164,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 180.59766388142287,\n      \"height\": 85.51233077137486,\n      \"seed\": 65450544,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"h9vDlzUov3hi0exBJmXql\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"QCOc4HbolRRMUaYbYgevA\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"43_r9mRXWwrQYCAcyRAFA\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1633,\n      \"versionNonce\": 1547363536,\n      \"index\": \"aat\",\n      \"isDeleted\": false,\n      \"id\": \"h9vDlzUov3hi0exBJmXql\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1391.2677994937405,\n      \"y\": 83.5830688024734,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 79.13766479492188,\n      \"height\": 28.957473781543904,\n      \"seed\": 45149232,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Task 3\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"vsA_CIfMv5pa_E8ntMeiT\",\n      \"originalText\": \"Task 3\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1231,\n      \"versionNonce\": 296572624,\n      \"index\": \"ab\",\n      \"isDeleted\": false,\n      \"id\": \"PEU_XU8Sh3D4biyxvy4HU\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1514.9518100115115,\n      \"y\": -110.71170442618455,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 1514127408,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"RZxT1str5sU0IsNoY2jST\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"WYORfi6MQ5vqe_kGKqXHJ\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1264,\n      \"versionNonce\": 897187024,\n      \"index\": \"ab8\",\n      \"isDeleted\": false,\n      \"id\": \"RZxT1str5sU0IsNoY2jST\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1555.6078490084465,\n      \"y\": -96.8786225664656,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 106.81385803222656,\n      \"height\": 28.957473781543904,\n      \"seed\": 950688304,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Trigger 2\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"PEU_XU8Sh3D4biyxvy4HU\",\n      \"originalText\": \"Trigger 2\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1391,\n      \"versionNonce\": 1597016624,\n      \"index\": \"abG\",\n      \"isDeleted\": false,\n      \"id\": \"7nMEXGNJzk7AaKIUHZYgN\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1080.8975851818275,\n      \"y\": 99.49504911229374,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 1550810160,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"AagPjJF25n7fL0j1dkYKb\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"dREYVkyhboXiS53A17F-t\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757450575,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1424,\n      \"versionNonce\": 117425200,\n      \"index\": \"abV\",\n      \"isDeleted\": false,\n      \"id\": \"AagPjJF25n7fL0j1dkYKb\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1124.7149629849148,\n      \"y\": 113.3281309720127,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 100.49118041992188,\n      \"height\": 28.957473781543904,\n      \"seed\": 596670000,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757450575,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Trigger 1\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"7nMEXGNJzk7AaKIUHZYgN\",\n      \"originalText\": \"Trigger 1\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1266,\n      \"versionNonce\": 2073164496,\n      \"index\": \"abl\",\n      \"isDeleted\": false,\n      \"id\": \"L1oF-vTjx-r7uE4ozO6yW\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1578.0877317826946,\n      \"y\": 109.78177027891613,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 1582500912,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"7w2g1VYZInR5vuWaicPT-\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"QCOc4HbolRRMUaYbYgevA\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1306,\n      \"versionNonce\": 1126834384,\n      \"index\": \"ac\",\n      \"isDeleted\": false,\n      \"id\": \"7w2g1VYZInR5vuWaicPT-\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1619.8091318025788,\n      \"y\": 123.61485213863509,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 104.68313598632812,\n      \"height\": 28.957473781543904,\n      \"seed\": 1115817520,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Trigger 3\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"L1oF-vTjx-r7uE4ozO6yW\",\n      \"originalText\": \"Trigger 3\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 2589,\n      \"versionNonce\": 618118704,\n      \"index\": \"ad\",\n      \"isDeleted\": false,\n      \"id\": \"dREYVkyhboXiS53A17F-t\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1052.2934151720121,\n      \"y\": 31.6871389549543,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 72.65101292680833,\n      \"height\": 71.20679435860492,\n      \"seed\": 1357628464,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757450575,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"1-nChtDmy9xBSz25x5Tdy\",\n        \"focus\": 0.021328620465046026,\n        \"gap\": 8.713122096692402,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"7nMEXGNJzk7AaKIUHZYgN\",\n        \"focus\": -0.2495745298142544,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          72.65101292680833,\n          71.20679435860492\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 3150,\n      \"versionNonce\": 1788521008,\n      \"index\": \"ae\",\n      \"isDeleted\": false,\n      \"id\": \"QCOc4HbolRRMUaYbYgevA\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1505.9138290121784,\n      \"y\": 115.04897709364587,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 68.45677489333957,\n      \"height\": 21.345873788386115,\n      \"seed\": 1524351024,\n      \"groupIds\": [\n        \"kz6Om3WP3ZIzj3WwTqs5f\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446553,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"vsA_CIfMv5pa_E8ntMeiT\",\n        \"focus\": -0.14628403082450295,\n        \"gap\": 8.885249593051313,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"L1oF-vTjx-r7uE4ozO6yW\",\n        \"focus\": -0.7039819943540665,\n        \"gap\": 3.843006315232685,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          68.45677489333957,\n          21.345873788386115\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 1324,\n      \"versionNonce\": 1520916176,\n      \"index\": \"at\",\n      \"isDeleted\": false,\n      \"id\": \"mp5cf4rOe7-TBZhYPXY93\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.006345883568884325,\n      \"x\": 644.4979240201953,\n      \"y\": 359.25214630499306,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 173.20703124999997,\n      \"height\": 79.8671875,\n      \"seed\": 426496208,\n      \"groupIds\": [\n        \"EzkF_X72G6C04wpKYmGYo\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"xyEhwiCsaF_GlM_qi4kL_\"\n        },\n        {\n          \"id\": \"jtmxjvYMT8IyTg6YsEhEo\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"qdG1HK_ivqJvkFxfXu_bj\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"HRt4IaHAZ1RGroBskO7Y4\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757424418,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1261,\n      \"versionNonce\": 628108336,\n      \"index\": \"au\",\n      \"isDeleted\": false,\n      \"id\": \"xyEhwiCsaF_GlM_qi4kL_\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.006345883568884325,\n      \"x\": 697.3597022794727,\n      \"y\": 386.71894317999306,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ff8787\",\n      \"width\": 67.87995910644531,\n      \"height\": 25,\n      \"seed\": 1935226576,\n      \"groupIds\": [\n        \"EzkF_X72G6C04wpKYmGYo\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757418188,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Task 4\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"mp5cf4rOe7-TBZhYPXY93\",\n      \"originalText\": \"Task 4\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1341,\n      \"versionNonce\": 383682096,\n      \"index\": \"av\",\n      \"isDeleted\": false,\n      \"id\": \"l1-m9PMckNOmTsLOCicxB\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.03810438733463428,\n      \"x\": 861.8797925835937,\n      \"y\": 327.34831220020493,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 162.48975984463084,\n      \"height\": 49,\n      \"seed\": 917101776,\n      \"groupIds\": [\n        \"EzkF_X72G6C04wpKYmGYo\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"rmTtNCvSYi_osiDfmjP7W\"\n        },\n        {\n          \"id\": \"qdG1HK_ivqJvkFxfXu_bj\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757412156,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1315,\n      \"versionNonce\": 260980784,\n      \"index\": \"aw\",\n      \"isDeleted\": false,\n      \"id\": \"rmTtNCvSYi_osiDfmjP7W\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.03810438733463428,\n      \"x\": 898.2059039024226,\n      \"y\": 339.5241960611345,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 89.93992614746094,\n      \"height\": 25,\n      \"seed\": 1425392336,\n      \"groupIds\": [\n        \"EzkF_X72G6C04wpKYmGYo\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757412156,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Trigger 4\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"l1-m9PMckNOmTsLOCicxB\",\n      \"originalText\": \"Trigger 4\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 2670,\n      \"versionNonce\": 890691632,\n      \"index\": \"ax\",\n      \"isDeleted\": false,\n      \"id\": \"qdG1HK_ivqJvkFxfXu_bj\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.024769842411049,\n      \"x\": 792.7955223699694,\n      \"y\": 376.2767822063056,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 87.57268219796742,\n      \"height\": 0.5282969080340081,\n      \"seed\": 419426352,\n      \"groupIds\": [\n        \"EzkF_X72G6C04wpKYmGYo\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757418188,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"mp5cf4rOe7-TBZhYPXY93\",\n        \"focus\": 0.11419552780004952,\n        \"gap\": 1.1291855844196732,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"l1-m9PMckNOmTsLOCicxB\",\n        \"focus\": -0.23292659823893222,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          87.57268219796742,\n          0.5282969080340081\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 742,\n      \"versionNonce\": 599753264,\n      \"index\": \"b05\",\n      \"isDeleted\": false,\n      \"id\": \"WhZAKrfIBY1WfWup23z04\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1539.2381134992843,\n      \"y\": 8.612451809277985,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 754706128,\n      \"groupIds\": [\n        \"i5wOMyEkj5AdEKIlQn9ba\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"r-dwj1uZUT6jpFw5M_7eg\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"gBeFANJTXm0QDmAM0WzrH\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723753697539,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 793,\n      \"versionNonce\": 1324334128,\n      \"index\": \"b06\",\n      \"isDeleted\": false,\n      \"id\": \"r-dwj1uZUT6jpFw5M_7eg\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1579.4772747496372,\n      \"y\": 22.445533668996934,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 107.64761352539062,\n      \"height\": 28.957473781543904,\n      \"seed\": 141987024,\n      \"groupIds\": [\n        \"i5wOMyEkj5AdEKIlQn9ba\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723753697539,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Callable 2\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"WhZAKrfIBY1WfWup23z04\",\n      \"originalText\": \"Callable 2\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 907,\n      \"versionNonce\": 12432080,\n      \"index\": \"b07\",\n      \"isDeleted\": false,\n      \"id\": \"gBeFANJTXm0QDmAM0WzrH\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1412.1801059262384,\n      \"y\": -14.87264932207451,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 126.85949483626405,\n      \"height\": 47.810475354948395,\n      \"seed\": 1199146192,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"679z3nf1w9xVLbf3f8AdN\",\n        \"focus\": -0.4492016684540374,\n        \"gap\": 8.707041440834715,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"WhZAKrfIBY1WfWup23z04\",\n        \"focus\": -0.7112738678621304,\n        \"gap\": 1.1025252940076768,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          126.85949483626405,\n          47.810475354948395\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 926,\n      \"versionNonce\": 256996400,\n      \"index\": \"b08\",\n      \"isDeleted\": false,\n      \"id\": \"s89xH-4yu5r6IAP8S9KE8\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 921.0041842510022,\n      \"y\": 175.33420387831111,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 840094768,\n      \"groupIds\": [\n        \"7T3F0-rwS5jf_PHTJroJR\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"ZF1CqhL5fwb4Ws-zY_XLW\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"ICGWc2_oP7ooflk821IPB\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757221361,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 981,\n      \"versionNonce\": 1583729872,\n      \"index\": \"b09\",\n      \"isDeleted\": false,\n      \"id\": \"ZF1CqhL5fwb4Ws-zY_XLW\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 964.4046843075075,\n      \"y\": 189.16728573803007,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 101.32493591308594,\n      \"height\": 28.957473781543904,\n      \"seed\": 453561904,\n      \"groupIds\": [\n        \"7T3F0-rwS5jf_PHTJroJR\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757221361,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Callable 1\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"s89xH-4yu5r6IAP8S9KE8\",\n      \"originalText\": \"Callable 1\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 854,\n      \"versionNonce\": 801233104,\n      \"index\": \"b0A\",\n      \"isDeleted\": false,\n      \"id\": \"KWb-DkqaKPnpVL7f6V4bL\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1370.069653799254,\n      \"y\": 191.40622146641806,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 188.21171841881053,\n      \"height\": 56.75664861182606,\n      \"seed\": 332013104,\n      \"groupIds\": [\n        \"PkHndZTqTLVxxXgSDZxvW\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"-Iu1CEC1DMo3CeL80AL-H\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"43_r9mRXWwrQYCAcyRAFA\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723753719986,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 911,\n      \"versionNonce\": 609235504,\n      \"index\": \"b0B\",\n      \"isDeleted\": false,\n      \"id\": \"-Iu1CEC1DMo3CeL80AL-H\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1411.374176072556,\n      \"y\": 205.23930332613702,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 105.51689147949219,\n      \"height\": 28.957473781543904,\n      \"seed\": 457627696,\n      \"groupIds\": [\n        \"PkHndZTqTLVxxXgSDZxvW\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723753715755,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.165979025235124,\n      \"fontFamily\": 5,\n      \"text\": \"Callable 3\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"KWb-DkqaKPnpVL7f6V4bL\",\n      \"originalText\": \"Callable 3\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 621,\n      \"versionNonce\": 1737071312,\n      \"index\": \"b0C\",\n      \"isDeleted\": false,\n      \"id\": \"ICGWc2_oP7ooflk821IPB\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1000.4847504534591,\n      \"y\": 44.85883247767296,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 1.021474072241972,\n      \"height\": 126.31065788274074,\n      \"seed\": 1934117584,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"1-nChtDmy9xBSz25x5Tdy\",\n        \"focus\": 0.06905856012815444,\n        \"gap\": 3.99435700483167,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"s89xH-4yu5r6IAP8S9KE8\",\n        \"focus\": -0.14186582145959503,\n        \"gap\": 4.479043609614649,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          1.021474072241972,\n          126.31065788274074\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 49,\n      \"versionNonce\": 1153557200,\n      \"index\": \"b0D\",\n      \"isDeleted\": false,\n      \"id\": \"WYORfi6MQ5vqe_kGKqXHJ\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1423.6927813940347,\n      \"y\": -35.91522196403305,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 93.20078197532757,\n      \"height\": 37.93587818765758,\n      \"seed\": 969630768,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"679z3nf1w9xVLbf3f8AdN\",\n        \"focus\": 0.815379469998369,\n        \"gap\": 6.560548878821066,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"PEU_XU8Sh3D4biyxvy4HU\",\n        \"focus\": 0.6089969123958293,\n        \"gap\": 1.9020541028933877,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          93.20078197532757,\n          -37.93587818765758\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 52,\n      \"versionNonce\": 2009329872,\n      \"index\": \"b0E\",\n      \"isDeleted\": false,\n      \"id\": \"43_r9mRXWwrQYCAcyRAFA\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1437.9674926622693,\n      \"y\": 140.24593205610267,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 9.254842268716857,\n      \"height\": 52.1801369969441,\n      \"seed\": 1634053840,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757446539,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"vsA_CIfMv5pa_E8ntMeiT\",\n        \"focus\": 0.005781863342302937,\n        \"gap\": 2.5808713031511914,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"KWb-DkqaKPnpVL7f6V4bL\",\n        \"focus\": -0.12844787041016997,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          9.254842268716857,\n          52.1801369969441\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1354,\n      \"versionNonce\": 1998775504,\n      \"index\": \"b0F\",\n      \"isDeleted\": false,\n      \"id\": \"g3vzES2f7nTxgtOAQHxlD\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.08267458081857892,\n      \"x\": 883.3984270653652,\n      \"y\": 445.3473162712001,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 172.18810636886062,\n      \"height\": 51.92460878852706,\n      \"seed\": 602337488,\n      \"groupIds\": [\n        \"Z2WNd7VHjMQwMoXLdzYAJ\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"meTPYjzpBqS9qJaNPrOwm\"\n        },\n        {\n          \"id\": \"jtmxjvYMT8IyTg6YsEhEo\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757403784,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1352,\n      \"versionNonce\": 424861392,\n      \"index\": \"b0G\",\n      \"isDeleted\": false,\n      \"id\": \"meTPYjzpBqS9qJaNPrOwm\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.08267458081857892,\n      \"x\": 921.58768418448,\n      \"y\": 458.205425502508,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 96.05421447753906,\n      \"height\": 26.492147341085236,\n      \"seed\": 1097881296,\n      \"groupIds\": [\n        \"Z2WNd7VHjMQwMoXLdzYAJ\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757402200,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 21.193717872868188,\n      \"fontFamily\": 5,\n      \"text\": \"Callable 4\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"g3vzES2f7nTxgtOAQHxlD\",\n      \"originalText\": \"Callable 4\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 698,\n      \"versionNonce\": 1161193168,\n      \"index\": \"b0H\",\n      \"isDeleted\": false,\n      \"id\": \"jtmxjvYMT8IyTg6YsEhEo\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 762.4465004005942,\n      \"y\": 423.5712587597044,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 123.52352373817519,\n      \"height\": 38.4442066961376,\n      \"seed\": 20683984,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757421788,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"mp5cf4rOe7-TBZhYPXY93\",\n        \"focus\": 0.3656434333139051,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"g3vzES2f7nTxgtOAQHxlD\",\n        \"focus\": -0.506230526903665,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          123.52352373817519,\n          38.4442066961376\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 661,\n      \"versionNonce\": 383714512,\n      \"index\": \"b0N\",\n      \"isDeleted\": false,\n      \"id\": \"_ov2Za4vMppZrq6E8DrGP\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 151.76561742716592,\n      \"y\": 952.6445385915624,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 222.1171875,\n      \"height\": 153.02734374999997,\n      \"seed\": 1113625136,\n      \"groupIds\": [\n        \"bkA1fovZjPU8kOsGeKQ3-\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"TaKI_Zsm5EtJ2RAoG-piT\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754636194,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 681,\n      \"versionNonce\": 803213520,\n      \"index\": \"b0O\",\n      \"isDeleted\": false,\n      \"id\": \"1mKnrf8qs4l8Oo-0yvtPA\",\n      \"fillStyle\": \"solid\",\n      \"strokeWidth\": 2,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 161.04686742716592,\n      \"y\": 1016.0898510915624,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 192.65985107421875,\n      \"height\": 25,\n      \"seed\": 384748592,\n      \"groupIds\": [\n        \"bkA1fovZjPU8kOsGeKQ3-\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754636194,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"AioClock Application\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"AioClock Application\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 600,\n      \"versionNonce\": 1145432784,\n      \"index\": \"b0P\",\n      \"isDeleted\": false,\n      \"id\": \"TaKI_Zsm5EtJ2RAoG-piT\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 366.722107585323,\n      \"y\": 1070.018365180686,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 201.3027018317324,\n      \"height\": 88.58508972177424,\n      \"seed\": 1313068752,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754636194,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"_ov2Za4vMppZrq6E8DrGP\",\n        \"focus\": -0.05354174431970359,\n        \"gap\": 7.743665962249281,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"pFHt3Tj9bXJpp0z2vze_z\",\n        \"focus\": -0.05824999527775626,\n        \"gap\": 6.017602848102115,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          201.3027018317324,\n          88.58508972177424\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 363,\n      \"versionNonce\": 1642239184,\n      \"index\": \"b0U\",\n      \"isDeleted\": false,\n      \"id\": \"pFHt3Tj9bXJpp0z2vze_z\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 490.9445948885584,\n      \"y\": 1164.6210577505624,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 192.7956882911392,\n      \"height\": 126.8196202531644,\n      \"seed\": 761130192,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"97hUh-7kc56-J9lFD3Dlz\"\n        },\n        {\n          \"id\": \"TaKI_Zsm5EtJ2RAoG-piT\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"HUMibXR2jjlLuG3kA-AVo\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754634921,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 392,\n      \"versionNonce\": 268047568,\n      \"index\": \"b0V\",\n      \"isDeleted\": false,\n      \"id\": \"97hUh-7kc56-J9lFD3Dlz\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 499.052514412546,\n      \"y\": 1190.5308678771446,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 176.57984924316406,\n      \"height\": 75,\n      \"seed\": 1653197520,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754844423,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Collect all \\nassociated tasks \\nto group or itself\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"pFHt3Tj9bXJpp0z2vze_z\",\n      \"originalText\": \"Collect all associated tasks to group or itself\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 355,\n      \"versionNonce\": 379821616,\n      \"index\": \"b0W\",\n      \"isDeleted\": false,\n      \"id\": \"PP-ng3MaLs-Vkm56oZm-j\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 66.92703154630999,\n      \"y\": 1245.7362012582953,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 215.98101265822788,\n      \"height\": 220,\n      \"seed\": 729412304,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"HUMibXR2jjlLuG3kA-AVo\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"type\": \"text\",\n          \"id\": \"sHlsZZl8Ld-3G8tEpmfC8\"\n        },\n        {\n          \"id\": \"tFQlXQLJobY8BmOyCFy-C\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"bSYZ54fDSJlCiYxFIoqY3\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754297337,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 148,\n      \"versionNonce\": 1402237136,\n      \"index\": \"b0WV\",\n      \"isDeleted\": false,\n      \"id\": \"sHlsZZl8Ld-3G8tEpmfC8\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 130.18230973528102,\n      \"y\": 1305.7362012582953,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 89.47994995117188,\n      \"height\": 100,\n      \"seed\": 1549808848,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754034163,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Is there \\nany \\nstartup \\ntask?\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"PP-ng3MaLs-Vkm56oZm-j\",\n      \"originalText\": \"Is there any startup task?\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 434,\n      \"versionNonce\": 1276378672,\n      \"index\": \"b0X\",\n      \"isDeleted\": false,\n      \"id\": \"HUMibXR2jjlLuG3kA-AVo\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 479.3000141923558,\n      \"y\": 1233.7891803622645,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 259.49965686083056,\n      \"height\": 31.466995486930045,\n      \"seed\": 890192080,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756062790,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"pFHt3Tj9bXJpp0z2vze_z\",\n        \"focus\": -0.024001291159690904,\n        \"gap\": 11.644580696202638,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"PP-ng3MaLs-Vkm56oZm-j\",\n        \"focus\": -0.7375903662329082,\n        \"gap\": 18.35325627629757,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -132.86603922833922,\n          5.100516049794578\n        ],\n        [\n          -259.49965686083056,\n          31.466995486930045\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 281,\n      \"versionNonce\": 1258562608,\n      \"index\": \"b0Y\",\n      \"isDeleted\": false,\n      \"id\": \"tFQlXQLJobY8BmOyCFy-C\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 287.56539981706385,\n      \"y\": 1370.7373442086102,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 186.4989873674889,\n      \"height\": 78.94752973813888,\n      \"seed\": 73693232,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756074327,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"PP-ng3MaLs-Vkm56oZm-j\",\n        \"focus\": -0.2971292009789168,\n        \"gap\": 13.832646064717963,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"n9ToP1sTho6bZwvzaSX1v\",\n        \"focus\": 0.24979688288014204,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          186.4989873674889,\n          78.94752973813888\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 178,\n      \"versionNonce\": 1493577776,\n      \"index\": \"b0Z\",\n      \"isDeleted\": false,\n      \"id\": \"n9ToP1sTho6bZwvzaSX1v\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 474.5694147504952,\n      \"y\": 1446.1572874752337,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 186.3429588607594,\n      \"height\": 141.38647151898752,\n      \"seed\": 526215728,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"tFQlXQLJobY8BmOyCFy-C\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"type\": \"text\",\n          \"id\": \"xqy3QNcMbTQdr-7Y-CAPu\"\n        },\n        {\n          \"id\": \"K6tLFhiw5Uin1d-gyZMMa\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723755086801,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 277,\n      \"versionNonce\": 1410606288,\n      \"index\": \"b0ZG\",\n      \"isDeleted\": false,\n      \"id\": \"xqy3QNcMbTQdr-7Y-CAPu\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 487.16095031533723,\n      \"y\": 1491.8505232347275,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 161.1598877310753,\n      \"height\": 50,\n      \"seed\": 1089100336,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723755913354,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Run the task, \\nwith task runner\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"n9ToP1sTho6bZwvzaSX1v\",\n      \"originalText\": \"Run the task, with task runner\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 7,\n      \"versionNonce\": 1431708368,\n      \"index\": \"b0a\",\n      \"isDeleted\": false,\n      \"id\": \"toXI5aAPeghGS-UojT5Dr\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 498.18197616656346,\n      \"y\": 1544.9994528405732,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 0,\n      \"height\": 0,\n      \"seed\": 45031120,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754060374,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0,\n          0\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 189,\n      \"versionNonce\": 1948207152,\n      \"index\": \"b0b\",\n      \"isDeleted\": false,\n      \"id\": \"HEyjXhFgmGSg4l_cVGwpo\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.38252949035119954,\n      \"x\": 367.22159326534563,\n      \"y\": 1362.8041428944932,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 40.7856642036145,\n      \"height\": 31.587420437145056,\n      \"seed\": 1679624912,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756076678,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 25.26993634971605,\n      \"fontFamily\": 5,\n      \"text\": \"Yes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Yes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 262,\n      \"versionNonce\": 108425776,\n      \"index\": \"b0c\",\n      \"isDeleted\": false,\n      \"id\": \"bSYZ54fDSJlCiYxFIoqY3\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 120.14961629042963,\n      \"y\": 1427.5564598860278,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 30.311807014930793,\n      \"height\": 138.16917102811112,\n      \"seed\": 1295594544,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754482167,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"PP-ng3MaLs-Vkm56oZm-j\",\n        \"focus\": 0.6529344937699867,\n        \"gap\": 12.334950998376627,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"62AnXeQAIeDdvmIAHLsql\",\n        \"focus\": 0.18626692128435784,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          30.311807014930793,\n          138.16917102811112\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 225,\n      \"versionNonce\": 1193407536,\n      \"index\": \"b0d\",\n      \"isDeleted\": false,\n      \"id\": \"UB0T4WsJRhDst_ROu99CV\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.193731223285352,\n      \"x\": 69.15357436383988,\n      \"y\": 1481.907016430885,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 35.288696305989404,\n      \"height\": 35.68840598676002,\n      \"seed\": 1811917360,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754219032,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 28.550724789408026,\n      \"fontFamily\": 5,\n      \"text\": \"No\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"No\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 202,\n      \"versionNonce\": 1599069744,\n      \"index\": \"b0g\",\n      \"isDeleted\": false,\n      \"id\": \"K6tLFhiw5Uin1d-gyZMMa\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 473.5694147504952,\n      \"y\": 1577.2840394159837,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 222.31777593586875,\n      \"height\": 74.27226068666982,\n      \"seed\": 658627120,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723755086801,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"n9ToP1sTho6bZwvzaSX1v\",\n        \"focus\": -0.2845466033490556,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"62AnXeQAIeDdvmIAHLsql\",\n        \"focus\": 0.09532978924614155,\n        \"gap\": 7.742822789853733,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -222.31777593586875,\n          74.27226068666982\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 522,\n      \"versionNonce\": 1066451664,\n      \"index\": \"b0i\",\n      \"isDeleted\": false,\n      \"id\": \"NTr0E-uAaLqNxcqR-y62m\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 5.950908548183436,\n      \"x\": 259.0621979655733,\n      \"y\": 1572.151673564935,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 204.45667309363057,\n      \"height\": 24.853187128468736,\n      \"seed\": 393975504,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754267288,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.88254970277498,\n      \"fontFamily\": 5,\n      \"text\": \"Eventually it finishes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Eventually it finishes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 415,\n      \"versionNonce\": 1831616048,\n      \"index\": \"b0j\",\n      \"isDeleted\": false,\n      \"id\": \"62AnXeQAIeDdvmIAHLsql\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 46.010126614696446,\n      \"y\": 1563.5479955472367,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 215.98101265822788,\n      \"height\": 220,\n      \"seed\": 2045036240,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"-ThMjAXaf2xYE8np1dZMC\"\n        },\n        {\n          \"id\": \"bSYZ54fDSJlCiYxFIoqY3\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"K6tLFhiw5Uin1d-gyZMMa\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"F8dKp3225ek0XJx7j5nTi\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754482166,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 215,\n      \"versionNonce\": 1130690608,\n      \"index\": \"b0k\",\n      \"isDeleted\": false,\n      \"id\": \"-ThMjAXaf2xYE8np1dZMC\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 106.71540282479327,\n      \"y\": 1636.0479955472367,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 94.57995390892029,\n      \"height\": 75,\n      \"seed\": 88304848,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754482166,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Is there \\nany other\\ntask?\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"62AnXeQAIeDdvmIAHLsql\",\n      \"originalText\": \"Is there any other task?\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 73,\n      \"versionNonce\": 1371776560,\n      \"index\": \"b0l\",\n      \"isDeleted\": false,\n      \"id\": \"F8dKp3225ek0XJx7j5nTi\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 261.20140736398764,\n      \"y\": 1700.7333461944968,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 177.29937870676588,\n      \"height\": 111.56354900062706,\n      \"seed\": 1441168592,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754482167,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"62AnXeQAIeDdvmIAHLsql\",\n        \"focus\": -0.36608614517760474,\n        \"gap\": 18.481385916632377,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          177.29937870676588,\n          111.56354900062706\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 208,\n      \"versionNonce\": 978137296,\n      \"index\": \"b0m\",\n      \"isDeleted\": false,\n      \"id\": \"pTPLz0pnwCGZc1m9XPrVU\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 444.23109718020464,\n      \"y\": 1790.604336075719,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 186.3429588607594,\n      \"height\": 141.38647151898752,\n      \"seed\": 1098873904,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"Jx4puZXo9muudZA7Is-ul\"\n        },\n        {\n          \"id\": \"hsgEKlKrummvl8bI0o12-\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723755916084,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 284,\n      \"versionNonce\": 1694913744,\n      \"index\": \"b0n\",\n      \"isDeleted\": false,\n      \"id\": \"Jx4puZXo9muudZA7Is-ul\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 456.8226327450467,\n      \"y\": 1836.2975718352127,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 161.1598877310753,\n      \"height\": 50,\n      \"seed\": 1721792048,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723755917136,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Run the task, \\nwith task runner\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"pTPLz0pnwCGZc1m9XPrVU\",\n      \"originalText\": \"Run the task, with task runner\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 233,\n      \"versionNonce\": 785521712,\n      \"index\": \"b0o\",\n      \"isDeleted\": false,\n      \"id\": \"AZ8Lb1Qf-UBPg__N6Ni3k\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.5690110473277814,\n      \"x\": 354.8587843212615,\n      \"y\": 1727.2910370892278,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 40.7856642036145,\n      \"height\": 31.587420437145056,\n      \"seed\": 471380016,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754472533,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 25.26993634971605,\n      \"fontFamily\": 5,\n      \"text\": \"Yes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Yes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 308,\n      \"versionNonce\": 1701565648,\n      \"index\": \"b0p\",\n      \"isDeleted\": false,\n      \"id\": \"tgY3B-VJozPI8jwqQqydk\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 155.4336876594124,\n      \"y\": 1780.766675660775,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 30.611100340859906,\n      \"height\": 139.6509163254666,\n      \"seed\": 257645264,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754456616,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": null,\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          30.611100340859906,\n          139.6509163254666\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 272,\n      \"versionNonce\": 1220066000,\n      \"index\": \"b0q\",\n      \"isDeleted\": false,\n      \"id\": \"PcRpuw1zwSsRVNUhXieye\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.193731223285352,\n      \"x\": 104.43764573282266,\n      \"y\": 1835.1172322056318,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 35.288696305989404,\n      \"height\": 35.68840598676002,\n      \"seed\": 1455915216,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754456617,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 28.550724789408026,\n      \"fontFamily\": 5,\n      \"text\": \"No\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"No\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 479,\n      \"versionNonce\": 1734766640,\n      \"index\": \"b0r\",\n      \"isDeleted\": false,\n      \"id\": \"j6CjjAOcI4Pq1MVQ1M24r\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 66.74944636156056,\n      \"y\": 1929.4260765331521,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 215.98101265822788,\n      \"height\": 220,\n      \"seed\": 909420752,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"n5-9AXrgT7uZ3e_HhcKwI\"\n        },\n        {\n          \"id\": \"hsgEKlKrummvl8bI0o12-\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"8-EVaSqf6ZR64L11bet06\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754520983,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 299,\n      \"versionNonce\": 515520560,\n      \"index\": \"b0s\",\n      \"isDeleted\": false,\n      \"id\": \"n5-9AXrgT7uZ3e_HhcKwI\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 128.39473156957456,\n      \"y\": 1989.4260765331521,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 92.69993591308594,\n      \"height\": 100,\n      \"seed\": 826740432,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754452537,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Is there \\nany \\nshutdown\\ntask?\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"j6CjjAOcI4Pq1MVQ1M24r\",\n      \"originalText\": \"Is there any shutdown task?\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 264,\n      \"versionNonce\": 1454587088,\n      \"index\": \"b0t\",\n      \"isDeleted\": false,\n      \"id\": \"hsgEKlKrummvl8bI0o12-\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 487.1046231869103,\n      \"y\": 1939.953951382775,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 199.08000455324208,\n      \"height\": 98.80596639902069,\n      \"seed\": 371455696,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723755916084,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"pTPLz0pnwCGZc1m9XPrVU\",\n        \"focus\": -0.45916668906629854,\n        \"gap\": 7.963143788068464,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"j6CjjAOcI4Pq1MVQ1M24r\",\n        \"focus\": 0.5126982685313142,\n        \"gap\": 5.335906061696036,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -199.08000455324208,\n          98.80596639902069\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 697,\n      \"versionNonce\": 572080176,\n      \"index\": \"b0u\",\n      \"isDeleted\": false,\n      \"id\": \"yww7vnZfDtkn23qZV_9MI\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 5.847034041833071,\n      \"x\": 250.532589632297,\n      \"y\": 1953.4169380927945,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 204.45667309363057,\n      \"height\": 24.853187128468736,\n      \"seed\": 840735952,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754414914,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.88254970277498,\n      \"fontFamily\": 5,\n      \"text\": \"Eventually it finishes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Eventually it finishes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 399,\n      \"versionNonce\": 361233616,\n      \"index\": \"b0w\",\n      \"isDeleted\": false,\n      \"id\": \"X-0iInYecUG1BjmK_DZBY\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 5.811901839719698,\n      \"x\": 298.9133895614428,\n      \"y\": 2003.2861778230986,\n      \"strokeColor\": \"#e03131\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 229.47476490363073,\n      \"height\": 25.90321232416628,\n      \"seed\": 1632365104,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756400828,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20.722569859333024,\n      \"fontFamily\": 5,\n      \"text\": \"Or graceful shutdown\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Or graceful shutdown\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 382,\n      \"versionNonce\": 1475396304,\n      \"index\": \"b0x\",\n      \"isDeleted\": false,\n      \"id\": \"8-EVaSqf6ZR64L11bet06\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 179.27769129424638,\n      \"y\": 2147.664404904931,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 17.031897197660243,\n      \"height\": 135.17219951404422,\n      \"seed\": 1723002064,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723754619242,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"j6CjjAOcI4Pq1MVQ1M24r\",\n        \"focus\": 0.13740406747958717,\n        \"gap\": 2.003952060075946,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"mqiZadO7gUjCdwEG95rN-\",\n        \"focus\": 0.08481186725898551,\n        \"gap\": 19.5792698927537,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          17.031897197660243,\n          135.17219951404422\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 328,\n      \"versionNonce\": 874861264,\n      \"index\": \"b0y\",\n      \"isDeleted\": false,\n      \"id\": \"sliZS3n4NM5VWGWLk07qL\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.193731223285352,\n      \"x\": 131.1640305530422,\n      \"y\": 2195.4529447085906,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 35.288696305989404,\n      \"height\": 35.68840598676002,\n      \"seed\": 1096602320,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754523120,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 28.550724789408026,\n      \"fontFamily\": 5,\n      \"text\": \"No\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"No\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 611,\n      \"versionNonce\": 1697792048,\n      \"index\": \"b0z\",\n      \"isDeleted\": false,\n      \"id\": \"jlL5CJ4DT5zCd_RjrZb1x\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 259.14555139206345,\n      \"y\": 2089.8461994879863,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 185.6005502713267,\n      \"height\": 92.47327948141628,\n      \"seed\": 1638863568,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723755096802,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": null,\n      \"endBinding\": {\n        \"elementId\": \"SLDezLygbRJ2VvbEepnBf\",\n        \"focus\": 0.2602874090619763,\n        \"gap\": 1,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          185.6005502713267,\n          92.47327948141628\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 452,\n      \"versionNonce\": 1390644272,\n      \"index\": \"b10\",\n      \"isDeleted\": false,\n      \"id\": \"SLDezLygbRJ2VvbEepnBf\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 434.2855099184445,\n      \"y\": 2183.3194789694026,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 186.3429588607594,\n      \"height\": 141.38647151898752,\n      \"seed\": 367278288,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"jlL5CJ4DT5zCd_RjrZb1x\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"type\": \"text\",\n          \"id\": \"QA47B66xhfSSWURPoI2Wj\"\n        },\n        {\n          \"id\": \"I3Wcv2heCBjVL8wJ7cxju\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723755096802,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 525,\n      \"versionNonce\": 778602544,\n      \"index\": \"b11\",\n      \"isDeleted\": false,\n      \"id\": \"QA47B66xhfSSWURPoI2Wj\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 446.87704548328657,\n      \"y\": 2229.0127147288963,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 161.1598877310753,\n      \"height\": 50,\n      \"seed\": 344235728,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723755919085,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Run the task, \\nwith task runner\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"SLDezLygbRJ2VvbEepnBf\",\n      \"originalText\": \"Run the task, with task runner\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 339,\n      \"versionNonce\": 1846783696,\n      \"index\": \"b12\",\n      \"isDeleted\": false,\n      \"id\": \"kcV9WES9r3Hgum1vq3lSr\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.49425269310474107,\n      \"x\": 399.1123171761782,\n      \"y\": 2116.967520578905,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 40.7856642036145,\n      \"height\": 31.587420437145056,\n      \"seed\": 1348176592,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754505234,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 25.26993634971605,\n      \"fontFamily\": 5,\n      \"text\": \"Yes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Yes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 398,\n      \"versionNonce\": 915588656,\n      \"index\": \"b13\",\n      \"isDeleted\": false,\n      \"id\": \"mqiZadO7gUjCdwEG95rN-\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 120.53834260665997,\n      \"y\": 2302.415874311729,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 157.7848984637119,\n      \"height\": 127.39363519702648,\n      \"seed\": 699762384,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"Wn6LI4a9xruvOuYApEe8l\"\n        },\n        {\n          \"id\": \"8-EVaSqf6ZR64L11bet06\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"I3Wcv2heCBjVL8wJ7cxju\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723754621541,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 472,\n      \"versionNonce\": 1023728848,\n      \"index\": \"b14\",\n      \"isDeleted\": false,\n      \"id\": \"Wn6LI4a9xruvOuYApEe8l\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 178.48080251966826,\n      \"y\": 2353.6126919102426,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 41.89997863769531,\n      \"height\": 25,\n      \"seed\": 2096901328,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754619242,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Exit\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"mqiZadO7gUjCdwEG95rN-\",\n      \"originalText\": \"Exit\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 399,\n      \"versionNonce\": 257604144,\n      \"index\": \"b15\",\n      \"isDeleted\": false,\n      \"id\": \"I3Wcv2heCBjVL8wJ7cxju\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 520.6298569442847,\n      \"y\": 2334.4068649622322,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 228.38910390384757,\n      \"height\": 45.70347845481592,\n      \"seed\": 1391728848,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723755096802,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"SLDezLygbRJ2VvbEepnBf\",\n        \"focus\": -0.8845951098115866,\n        \"gap\": 9.700914473842204,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"mqiZadO7gUjCdwEG95rN-\",\n        \"focus\": 0.4096596647723168,\n        \"gap\": 13.917511970065277,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -228.38910390384757,\n          45.70347845481592\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 841,\n      \"versionNonce\": 89012432,\n      \"index\": \"b16\",\n      \"isDeleted\": false,\n      \"id\": \"UP2QFcgsa8fPXFOeQWORR\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.056032201216738,\n      \"x\": 324.87050612396354,\n      \"y\": 2388.028964675776,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 204.45667309363057,\n      \"height\": 24.853187128468736,\n      \"seed\": 1687715536,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754533104,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.88254970277498,\n      \"fontFamily\": 5,\n      \"text\": \"Eventually it finishes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Eventually it finishes\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 225,\n      \"versionNonce\": 705319984,\n      \"index\": \"b17\",\n      \"isDeleted\": false,\n      \"id\": \"AdDzyQ-BQk5kpMri0E72R\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.3899896189126908,\n      \"x\": 389.28497787797653,\n      \"y\": 1072.2854853849283,\n      \"strokeColor\": \"#e03131\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 238.16266248983007,\n      \"height\": 29.68216517673118,\n      \"seed\": 684030672,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723754643543,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 23.74573214138495,\n      \"fontFamily\": 5,\n      \"text\": \"await app.serve()\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"await app.serve()\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 832,\n      \"versionNonce\": 1154620112,\n      \"index\": \"b1X\",\n      \"isDeleted\": false,\n      \"id\": \"IQuQeqT5jHaGToLEJeScY\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1698.9602512692113,\n      \"y\": 871.1782058755693,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 349.58787444021567,\n      \"height\": 241.12794293132154,\n      \"seed\": 737447472,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"-GFO_l2VPtxG5Bc-I2kuy\",\n          \"type\": \"text\"\n        }\n      ],\n      \"updated\": 1723756979113,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 890,\n      \"versionNonce\": 795230416,\n      \"index\": \"b1XV\",\n      \"isDeleted\": false,\n      \"id\": \"-GFO_l2VPtxG5Bc-I2kuy\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1816.0542373174442,\n      \"y\": 879.2421773412301,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 115.39990234375,\n      \"height\": 225,\n      \"seed\": 1029764816,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756979113,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Task runner\\n\\n\\n\\n\\n\\n\\n\\n\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"IQuQeqT5jHaGToLEJeScY\",\n      \"originalText\": \"Task runner\\n\\n\\n\\n\\n\\n\\n\\n\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 1207,\n      \"versionNonce\": 209652432,\n      \"index\": \"b1XZ\",\n      \"isDeleted\": false,\n      \"id\": \"CqiCDYYzdeFbt7hIPh6Ej\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1705.7889969339021,\n      \"y\": 918.7187504179244,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 263.3592677540476,\n      \"height\": 184,\n      \"seed\": 1630399024,\n      \"groupIds\": [\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"fDd6ctG9MLdC4BAe3UjnL\"\n        },\n        {\n          \"id\": \"fXI-ZUZHdOLNfBJ2k9IaU\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756982309,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1228,\n      \"versionNonce\": 80235728,\n      \"index\": \"b1Xd\",\n      \"isDeleted\": false,\n      \"id\": \"fDd6ctG9MLdC4BAe3UjnL\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1813.535235425184,\n      \"y\": 923.7484501700878,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 47.866790771484375,\n      \"height\": 173.940600495673,\n      \"seed\": 1530374864,\n      \"groupIds\": [\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756979114,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.878925770934057,\n      \"fontFamily\": 5,\n      \"text\": \"Task\\n\\n\\n\\n\\n\\n\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"CqiCDYYzdeFbt7hIPh6Ej\",\n      \"originalText\": \"Task\\n\\n\\n\\n\\n\\n\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 2108,\n      \"versionNonce\": 902181584,\n      \"index\": \"b1Xh\",\n      \"isDeleted\": false,\n      \"id\": \"y2kyTs8L2fDyChRo01Siv\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.0790450627558883,\n      \"x\": 1791.3734901428243,\n      \"y\": 974.7988184591122,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 161.5060937244159,\n      \"height\": 49,\n      \"seed\": 641814224,\n      \"groupIds\": [\n        \"N_rYhw0CcYfJ8hWSOuPFa\",\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"7Q0nkPX6LxcrTKt8mS743\"\n        }\n      ],\n      \"updated\": 1723756979114,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 2137,\n      \"versionNonce\": 437737680,\n      \"index\": \"b1Xl\",\n      \"isDeleted\": false,\n      \"id\": \"7Q0nkPX6LxcrTKt8mS743\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.0790450627558883,\n      \"x\": 1836.7662050861936,\n      \"y\": 987.050373713208,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#12b886\",\n      \"width\": 70.51860976219177,\n      \"height\": 24.848657213667572,\n      \"seed\": 80671952,\n      \"groupIds\": [\n        \"N_rYhw0CcYfJ8hWSOuPFa\",\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756979114,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.878925770934057,\n      \"fontFamily\": 5,\n      \"text\": \"Callable\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"y2kyTs8L2fDyChRo01Siv\",\n      \"originalText\": \"Callable\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 2022,\n      \"versionNonce\": 1699987152,\n      \"index\": \"b1Xp\",\n      \"isDeleted\": false,\n      \"id\": \"XfSzjMLNwUPk9AVXRq5-c\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.039362398350647254,\n      \"x\": 1710.740412101144,\n      \"y\": 1026.350749168779,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 161.5060937244159,\n      \"height\": 49,\n      \"seed\": 79611952,\n      \"groupIds\": [\n        \"6zcuGwXIAAuVEqpx03rbO\",\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"lt43g2NolrgpCin3XiLSR\"\n        }\n      ],\n      \"updated\": 1723756979114,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 2045,\n      \"versionNonce\": 403924176,\n      \"index\": \"b1Xt\",\n      \"isDeleted\": false,\n      \"id\": \"lt43g2NolrgpCin3XiLSR\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.039362398350647254,\n      \"x\": 1756.5007388103759,\n      \"y\": 1038.6023044228748,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#a5d8ff\",\n      \"width\": 69.78338623046875,\n      \"height\": 24.848657213667572,\n      \"seed\": 471220784,\n      \"groupIds\": [\n        \"6zcuGwXIAAuVEqpx03rbO\",\n        \"c6BWuvFGG24TyGmFHy9p8\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756979114,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 19.878925770934057,\n      \"fontFamily\": 5,\n      \"text\": \"Trigger\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"XfSzjMLNwUPk9AVXRq5-c\",\n      \"originalText\": \"Trigger\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 879,\n      \"versionNonce\": 1474880560,\n      \"index\": \"b1i\",\n      \"isDeleted\": false,\n      \"id\": \"HyP7BZJVo6tJy8wPmNzbl\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1554.5497932146895,\n      \"y\": 1158.5511408762293,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 239.06313174761198,\n      \"height\": 126.8196202531644,\n      \"seed\": 1474292272,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"LbyFqCoQTmvTdC5VlJe9P\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"iOCvXYA2L16R_vT2aAdFu\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"fXI-ZUZHdOLNfBJ2k9IaU\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757164556,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 993,\n      \"versionNonce\": 1000136240,\n      \"index\": \"b1j\",\n      \"isDeleted\": false,\n      \"id\": \"iOCvXYA2L16R_vT2aAdFu\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1585.3614189029486,\n      \"y\": 1196.9609510028115,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 177.43988037109375,\n      \"height\": 50,\n      \"seed\": 1925391408,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757164556,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Get a task as an \\nargument\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"HyP7BZJVo6tJy8wPmNzbl\",\n      \"originalText\": \"Get a task as an argument\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 849,\n      \"versionNonce\": 985765936,\n      \"index\": \"b1m\",\n      \"isDeleted\": false,\n      \"id\": \"LbyFqCoQTmvTdC5VlJe9P\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1696.6044829064658,\n      \"y\": 1288.841905217944,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 0.8193286928735688,\n      \"height\": 53.678002679886276,\n      \"seed\": 1633266224,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757164556,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"HyP7BZJVo6tJy8wPmNzbl\",\n        \"focus\": -0.17844289194268018,\n        \"gap\": 3.471144088550318,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n        \"focus\": -0.009745033877159017,\n        \"gap\": 3.402997661688971,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          0.8193286928735688,\n          53.678002679886276\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"diamond\",\n      \"version\": 701,\n      \"versionNonce\": 463820848,\n      \"index\": \"b1s\",\n      \"isDeleted\": false,\n      \"id\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1536.3487797743226,\n      \"y\": 1345.0150380286043,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 327.0918259068212,\n      \"height\": 192.18989248354546,\n      \"seed\": 490909392,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"XIj9uOXTaxVQuFWHAh8t2\"\n        },\n        {\n          \"id\": \"STikmhs29bpStOM3nvBoD\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"3rHRwfJtVAuBBE1kyGmdt\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"2hRCzj2mZlHhWGt_WPdv1\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"LbyFqCoQTmvTdC5VlJe9P\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756891654,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 449,\n      \"versionNonce\": 308836560,\n      \"index\": \"b1t\",\n      \"isDeleted\": false,\n      \"id\": \"XIj9uOXTaxVQuFWHAh8t2\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1630.9617936240747,\n      \"y\": 1403.5625111494905,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 138.31988525390625,\n      \"height\": 75,\n      \"seed\": 1420055760,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756890860,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Check if task \\nstill can be \\ntriggered\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n      \"originalText\": \"Check if task still can be triggered\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 1103,\n      \"versionNonce\": 1760627760,\n      \"index\": \"b1u\",\n      \"isDeleted\": false,\n      \"id\": \"3rHRwfJtVAuBBE1kyGmdt\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1850.808229807619,\n      \"y\": 1461.3902677755318,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 282.17700712382043,\n      \"height\": 305.359935601738,\n      \"seed\": 1682829008,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756891655,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n        \"focus\": -0.12884470113660396,\n        \"gap\": 11.085840774530027,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"w7B3jgdlWOsnxlvPngEQD\",\n        \"focus\": 0.25339453076017165,\n        \"gap\": 9.103550868112734,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          282.17700712382043,\n          61.07043151696416\n        ],\n        [\n          273.33977612188505,\n          305.359935601738\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 517,\n      \"versionNonce\": 263527120,\n      \"index\": \"b1y\",\n      \"isDeleted\": false,\n      \"id\": \"jkUc63EEwi0tX_KG4pnmP\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.06053892542880668,\n      \"x\": 1987.9618247165897,\n      \"y\": 1449.2329700567975,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 24.720000326633453,\n      \"height\": 25,\n      \"seed\": 1910569520,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"No\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"No\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 811,\n      \"versionNonce\": 1171962416,\n      \"index\": \"b1z\",\n      \"isDeleted\": false,\n      \"id\": \"FFibWCRbM0ac15VquIccI\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0.13743315829613412,\n      \"x\": 1856.1019957293356,\n      \"y\": 1506.6127252537526,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 294.84916554571714,\n      \"height\": 66.19013114735047,\n      \"seed\": 77767216,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [\n        {\n          \"id\": \"3rHRwfJtVAuBBE1kyGmdt\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 17.650701639293455,\n      \"fontFamily\": 5,\n      \"text\": \"1. graceful shutdown\\n2. Sometimes task are n time run \\nonly (like startup task run once)\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"1. graceful shutdown\\n2. Sometimes task are n time run only (like startup task run once)\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 913,\n      \"versionNonce\": 1087998512,\n      \"index\": \"b21\",\n      \"isDeleted\": false,\n      \"id\": \"w7B3jgdlWOsnxlvPngEQD\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1996.7259811735935,\n      \"y\": 1775.8537542453826,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 198.5683942456583,\n      \"height\": 116.9088698549408,\n      \"seed\": 438287920,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"7XMxNA8RXkBPcSKKEXWIO\"\n        },\n        {\n          \"id\": \"3rHRwfJtVAuBBE1kyGmdt\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1045,\n      \"versionNonce\": 1529802448,\n      \"index\": \"b22\",\n      \"isDeleted\": false,\n      \"id\": \"7XMxNA8RXkBPcSKKEXWIO\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 2004.1902527497766,\n      \"y\": 1809.308189172853,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 183.63985109329224,\n      \"height\": 50,\n      \"seed\": 99121200,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756836519,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Return in function.\\nTask is finished\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"w7B3jgdlWOsnxlvPngEQD\",\n      \"originalText\": \"Return in function.\\nTask is finished\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 136,\n      \"versionNonce\": 1856546512,\n      \"index\": \"b23\",\n      \"isDeleted\": false,\n      \"id\": \"Uq1j2EwKkxp5mcs2g9yQf\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1629.8841019714973,\n      \"y\": 1563.2230944946575,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 49.21613603094943,\n      \"height\": 25,\n      \"seed\": 1579427536,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Yes\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"Yes\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 804,\n      \"versionNonce\": 2007851728,\n      \"index\": \"b26\",\n      \"isDeleted\": false,\n      \"id\": \"eYJWFucNr-z4ZnArvQ-Zr\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1571.2363939435845,\n      \"y\": 1626.8059576040923,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 273.58068072975556,\n      \"height\": 160,\n      \"seed\": 886513712,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"irv12xCSy44n3nsd-C1hX\"\n        },\n        {\n          \"id\": \"2hRCzj2mZlHhWGt_WPdv1\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"yd_IMWqbdG2nufqbzBVma\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1028,\n      \"versionNonce\": 1988840656,\n      \"index\": \"b27\",\n      \"isDeleted\": false,\n      \"id\": \"irv12xCSy44n3nsd-C1hX\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1579.7768258611966,\n      \"y\": 1644.3059576040923,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 256.49981689453125,\n      \"height\": 125,\n      \"seed\": 1466145328,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Wait until the task get \\ntriggered \\n(e.x if cron job, then \\nshould sleep for next cron\\ninterval)\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"eYJWFucNr-z4ZnArvQ-Zr\",\n      \"originalText\": \"Wait until the task get triggered \\n(e.x if cron job, then should sleep for next cron interval)\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 897,\n      \"versionNonce\": 1387080752,\n      \"index\": \"b2A\",\n      \"isDeleted\": false,\n      \"id\": \"JxDqrhu671kLDTdcOGeyZ\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1598.090109389605,\n      \"y\": 1892.713229888806,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 222.06468710355526,\n      \"height\": 101.80408496883595,\n      \"seed\": 2117406928,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"fGfM8hH-3f8NYSSbkKzuB\"\n        },\n        {\n          \"id\": \"yd_IMWqbdG2nufqbzBVma\",\n          \"type\": \"arrow\"\n        },\n        {\n          \"id\": \"STikmhs29bpStOM3nvBoD\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1171,\n      \"versionNonce\": 1999188688,\n      \"index\": \"b2B\",\n      \"isDeleted\": false,\n      \"id\": \"fGfM8hH-3f8NYSSbkKzuB\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1643.99249168917,\n      \"y\": 1931.115272373224,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#fff9db\",\n      \"width\": 130.25992250442505,\n      \"height\": 25,\n      \"seed\": 1828353744,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723756836518,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Run the task\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"JxDqrhu671kLDTdcOGeyZ\",\n      \"originalText\": \"Run the task\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 210,\n      \"versionNonce\": 1231029808,\n      \"index\": \"b2C\",\n      \"isDeleted\": false,\n      \"id\": \"2hRCzj2mZlHhWGt_WPdv1\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1695.1683092267951,\n      \"y\": 1535.5876864571458,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 2.505653222510091,\n      \"height\": 86.9826664940208,\n      \"seed\": 1489365040,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756891655,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n        \"focus\": 0.04554036978314176,\n        \"gap\": 1.0000000000001137,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"eYJWFucNr-z4ZnArvQ-Zr\",\n        \"focus\": -0.05698450771445858,\n        \"gap\": 4.235604652925758,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          2.505653222510091,\n          86.9826664940208\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 309,\n      \"versionNonce\": 274568240,\n      \"index\": \"b2D\",\n      \"isDeleted\": false,\n      \"id\": \"yd_IMWqbdG2nufqbzBVma\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1693.8171318957795,\n      \"y\": 1797.3645905520277,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 3.90076845842691,\n      \"height\": 84.28241401267951,\n      \"seed\": 1069469744,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756836520,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"eYJWFucNr-z4ZnArvQ-Zr\",\n        \"focus\": 0.1380173553300253,\n        \"gap\": 10.558632947935394,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"JxDqrhu671kLDTdcOGeyZ\",\n        \"focus\": -0.07528587805114698,\n        \"gap\": 11.066225324098781,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          3.90076845842691,\n          84.28241401267951\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 403,\n      \"versionNonce\": 933941808,\n      \"index\": \"b2E\",\n      \"isDeleted\": false,\n      \"id\": \"STikmhs29bpStOM3nvBoD\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1587.0238840655074,\n      \"y\": 1944.041078311416,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 134.6236982149312,\n      \"height\": 499.71671706185407,\n      \"seed\": 1382304976,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723756891655,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"JxDqrhu671kLDTdcOGeyZ\",\n        \"focus\": -0.8775469865039849,\n        \"gap\": 11.06622532409752,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"FCwcIAfeLbeJg6Zi8BVKM\",\n        \"focus\": 0.989821931698346,\n        \"gap\": 2.461394250283675,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -134.6236982149312,\n          -241.50757913609573\n        ],\n        [\n          -50.063194075934234,\n          -499.71671706185407\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 532,\n      \"versionNonce\": 1101911760,\n      \"index\": \"b2G\",\n      \"isDeleted\": false,\n      \"id\": \"Fo90EBjtmjCdnFISTCbzQ\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.278177951354555,\n      \"x\": 1258.6235230693567,\n      \"y\": 1663.7011971998286,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 212.59902644016609,\n      \"height\": 28.165738862651683,\n      \"seed\": 886943792,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723755863197,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 22.532591090121358,\n      \"fontFamily\": 5,\n      \"text\": \"In a while loop....\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"In a while loop....\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 72,\n      \"versionNonce\": 691999280,\n      \"index\": \"b2I\",\n      \"isDeleted\": false,\n      \"id\": \"fXI-ZUZHdOLNfBJ2k9IaU\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 1765.7332540847406,\n      \"y\": 1117.859883036091,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#d0bfff\",\n      \"width\": 8.997444931522296,\n      \"height\": 38.654428294499894,\n      \"seed\": 2004644048,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757164556,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"CqiCDYYzdeFbt7hIPh6Ej\",\n        \"focus\": 0.42645184962372484,\n        \"gap\": 15.141132618166694,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"HyP7BZJVo6tJy8wPmNzbl\",\n        \"focus\": 0.5020483085399844,\n        \"gap\": 2.036829545638284,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          -8.997444931522296,\n          38.654428294499894\n        ]\n      ],\n      \"elbowed\": false\n    },\n    {\n      \"type\": \"rectangle\",\n      \"version\": 96,\n      \"versionNonce\": 995565616,\n      \"index\": \"b2IG\",\n      \"isDeleted\": false,\n      \"id\": \"URbMlcmauXmJNE4154eWu\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 251.14182229334358,\n      \"y\": -74.41583597634695,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 370.7116656519728,\n      \"height\": 263.40256087233087,\n      \"seed\": 1862224944,\n      \"groupIds\": [\n        \"5K2ah22q0bV8H2rPle8Rd\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 3\n      },\n      \"boundElements\": [\n        {\n          \"type\": \"text\",\n          \"id\": \"Zv7ejmqs54GHJIXOGOmWE\"\n        },\n        {\n          \"id\": \"umIUdT2Pg_dV6Yj3OxRkn\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757378149,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 125,\n      \"versionNonce\": 1580595760,\n      \"index\": \"b2IV\",\n      \"isDeleted\": false,\n      \"id\": \"Zv7ejmqs54GHJIXOGOmWE\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 341.4177295822206,\n      \"y\": -30.21455554018152,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#ffc9c9\",\n      \"width\": 190.15985107421875,\n      \"height\": 175,\n      \"seed\": 1425153232,\n      \"groupIds\": [\n        \"5K2ah22q0bV8H2rPle8Rd\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757375833,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Aioclock Application\\n\\n\\n\\n\\n\\n\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"URbMlcmauXmJNE4154eWu\",\n      \"originalText\": \"Aioclock Application\\n\\n\\n\\n\\n\\n\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"ellipse\",\n      \"version\": 1064,\n      \"versionNonce\": 1690098896,\n      \"index\": \"b2J\",\n      \"isDeleted\": false,\n      \"id\": \"r8Q1v_XO3gRI7vmMfsEXw\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 325.2229077704774,\n      \"y\": 50.56508754088625,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 213.32236457653954,\n      \"height\": 120,\n      \"seed\": 1818493488,\n      \"groupIds\": [\n        \"5K2ah22q0bV8H2rPle8Rd\"\n      ],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [\n        {\n          \"id\": \"N6Qx_9If8fvD9nNkz3hcc\",\n          \"type\": \"text\"\n        },\n        {\n          \"id\": \"HRt4IaHAZ1RGroBskO7Y4\",\n          \"type\": \"arrow\"\n        }\n      ],\n      \"updated\": 1723757384370,\n      \"link\": null,\n      \"locked\": false\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 954,\n      \"versionNonce\": 1047887920,\n      \"index\": \"b2K\",\n      \"isDeleted\": false,\n      \"id\": \"N6Qx_9If8fvD9nNkz3hcc\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 375.60329101223635,\n      \"y\": 73.1386806696934,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 112.71990752220154,\n      \"height\": 75,\n      \"seed\": 2131910352,\n      \"groupIds\": [\n        \"5K2ah22q0bV8H2rPle8Rd\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757375832,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 20,\n      \"fontFamily\": 5,\n      \"text\": \"Dependency\\nInject\\nSystem\",\n      \"textAlign\": \"center\",\n      \"verticalAlign\": \"middle\",\n      \"containerId\": \"r8Q1v_XO3gRI7vmMfsEXw\",\n      \"originalText\": \"Dependency\\nInject\\nSystem\",\n      \"autoResize\": true,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"text\",\n      \"version\": 1687,\n      \"versionNonce\": 473918000,\n      \"index\": \"b2L\",\n      \"isDeleted\": false,\n      \"id\": \"HC4jNhimd9IUoFr2xipV5\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"solid\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 6.240271949811893,\n      \"x\": 441.847757819976,\n      \"y\": 380.6251438274418,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 182.67791703977647,\n      \"height\": 22.515338081998202,\n      \"seed\": 2112564944,\n      \"groupIds\": [\n        \"t7l2d8FW9kPWixWqQCSlC\"\n      ],\n      \"frameId\": null,\n      \"roundness\": null,\n      \"boundElements\": [],\n      \"updated\": 1723757435723,\n      \"link\": null,\n      \"locked\": false,\n      \"fontSize\": 18.01227046559856,\n      \"fontFamily\": 5,\n      \"text\": \"@aioclock.task\",\n      \"textAlign\": \"left\",\n      \"verticalAlign\": \"top\",\n      \"containerId\": null,\n      \"originalText\": \"@aioclock.task\",\n      \"autoResize\": false,\n      \"lineHeight\": 1.25\n    },\n    {\n      \"type\": \"arrow\",\n      \"version\": 2177,\n      \"versionNonce\": 812487728,\n      \"index\": \"b2M\",\n      \"isDeleted\": false,\n      \"id\": \"HRt4IaHAZ1RGroBskO7Y4\",\n      \"fillStyle\": \"hachure\",\n      \"strokeWidth\": 1,\n      \"strokeStyle\": \"dashed\",\n      \"roughness\": 1,\n      \"opacity\": 100,\n      \"angle\": 0,\n      \"x\": 414.225741641744,\n      \"y\": 196.96627523669537,\n      \"strokeColor\": \"#1e1e1e\",\n      \"backgroundColor\": \"#3bc9db\",\n      \"width\": 233.81396450213708,\n      \"height\": 212.0236995742859,\n      \"seed\": 1735212080,\n      \"groupIds\": [],\n      \"frameId\": null,\n      \"roundness\": {\n        \"type\": 2\n      },\n      \"boundElements\": [],\n      \"updated\": 1723757428340,\n      \"link\": null,\n      \"locked\": false,\n      \"startBinding\": {\n        \"elementId\": \"r8Q1v_XO3gRI7vmMfsEXw\",\n        \"focus\": 0.23301788409604482,\n        \"gap\": 27.124023203196707,\n        \"fixedPoint\": null\n      },\n      \"endBinding\": {\n        \"elementId\": \"mp5cf4rOe7-TBZhYPXY93\",\n        \"focus\": -0.31217152478063914,\n        \"gap\": 7.871960812977093,\n        \"fixedPoint\": null\n      },\n      \"lastCommittedPoint\": null,\n      \"startArrowhead\": null,\n      \"endArrowhead\": \"arrow\",\n      \"points\": [\n        [\n          0,\n          0\n        ],\n        [\n          17.144056910559357,\n          205.07407135371665\n        ],\n        [\n          233.81396450213708,\n          212.0236995742859\n        ]\n      ],\n      \"elbowed\": false\n    }\n  ],\n  \"appState\": {\n    \"gridSize\": 20,\n    \"gridStep\": 5,\n    \"gridModeEnabled\": false,\n    \"viewBackgroundColor\": \"#ffffff\"\n  },\n  \"files\": {}\n}"
  },
  {
    "path": "docs/examples/brokers.md",
    "content": "You can basically run any tasks on aioclock, it could be your redis broker or other kind of brokers listening to a queue. The benefit of doing so, is that you don't need to worry about dependency injection, shutdown or startup event.\n\nAioClock offer you a unique easy way to spin up new services, without any overhead or perfomance issue!\n\n```python\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\nfrom aioclock import AioClock, Forever, Depends\nfrom functools import lru_cache\nfrom typing import NewType\n\nBrokerType = NewType(\"BrokerType\", ...) # your broker type ...\n\n# your singleton redis instance\n@lru_cache\ndef get_redis():\n    ...\n\n@asynccontextmanager\nasync def lifespan(aio_clock: AioClock, redis: BrokerType = Depends(get_redis)) -> AsyncGenerator[AioClock]:\n    yield aio_clock\n    await redis.disconnect()\n\n\napp = AioClock(lifespan=lifespan)\n\n\n@app.task(trigger=Forever())\nasync def read_message_queue(redis: BrokerType = Depends(get_redis)):\n    async for message in redis.listen(\"...\"):\n        ...\n\n```\n\nOne other way to do this, is to implement a trigger that automatically execute the function.\nBut to do so, I basically need to wrap redis in my own library, and that's not good for some reasons:\n\n1. Complexity of framework increases.\n2. Is not realy flexible, because native library and client are always way more flexible. I end up writing something like `Celery`.\n3. The architecture I choose to handle interactions with broker may not satisfy your requirement.\n\n[This repository is an example how you can write a message queue in aioclock.](https://github.com/ManiMozaffar/typed-redis)\n"
  },
  {
    "path": "docs/examples/fastapi.md",
    "content": "To run AioClock with FastAPI, you can run it in the background with FastAPI lifespan, next to your asgi.\n\n```python\nfrom aioclock import AioClock\nfrom fastapi import FastAPI\nimport asyncio\nfrom contextlib import asynccontextmanager\n\nclock_app = AioClock()\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    task = asyncio.create_task(clock_app.serve())\n    yield\n\n    try:\n        task.cancel()\n        await task\n    except asyncio.CancelledError:\n        ...\n\napp = FastAPI(lifespan=lifespan)\n# now serve this with uvicorn or anything else\n```\n\n!!! danger \"This setup is not recommended at all\"\n\n    Running AioClock with FastAPI is not a good practice in General, because:\n    FastAPI is a framework to write stateless API, but aioclock is still stateful component in your architecture.\n    In simpler terms, it means if you have 5 instances of aioclock running, they produce 5x tasks than you intended.\n    So you cannot easily scale up horizontally by adding more aioclock power!\n\n    Even in this case, if you serve FastAPI with multiple processes, you end up having one aioclock per process!\n\n    What I suggest doing is to spin one new service, that is responsible for processing the periodic tasks.\n    Try to avoid periodic tasks in general, but sometimes it's not easy to do so.\n"
  },
  {
    "path": "docs/extra/tweaks.css",
    "content": "/* Revert hue value to that of pre mkdocs-material v9.4.0 */\n[data-md-color-scheme='slate'] {\n  --md-hue: 230;\n  --md-default-bg-color: hsla(230, 15%, 21%, 1);\n}\n"
  },
  {
    "path": "docs/images/README.md",
    "content": "Please check th diagrams folder if you're wondering where diagrams come from.\nYou can load them in [excalidraw](https://excalidraw.com/) website.\n"
  },
  {
    "path": "docs/index.md",
    "content": "# AioClock\n\n## The Principle\n\nScheduling is annoying, stateful and hard to scale. But not anymore! AioClock is here as an asyncio-based scheduling framework designed for execution of periodic task with integrated support for dependency injection, enabling efficient and flexiable task management.\n\nAioclock offers:\n\n- Async: 100% Async, very light, fast and resource friendly\n- Scheduling: Keep scheduling tasks for you\n- Group: Group your task, for better code maintainability\n- Trigger: Already defined and easily extendable triggers, to trigger your scheduler to be started\n- Easy syntax: Library's syntax is very easy and enjoyable, no confusing hierarchy\n- Pydantic v2 validation: Validate all your trigger on startup using pydantic 2. Fastest to fail possible!\n- **Soon**: Running the task dispatcher (scheduler) on different process by default, so CPU intensive stuff on task won't delay the scheduling\n- **Soon**: Backend support, to allow horizontal scalling, by synchronizing, maybe using Redis\n\n## Getting started\n\nTo Install aioclock, simply do\n\n```\npip install aioclock\n```\n\nAioClock is very user friendly and easy to use, it's type stated library to use easily.\nAioClock always have a trigger, that trigger the events.\n\n```python\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\nimport asyncio\n\nfrom aioclock import AioClock, At, Depends, Every, Forever, Once\nfrom aioclock.group import Group\n\n# groups.py\ngroup = Group()\n\n\ndef more_useless_than_me():\n    return \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Every(seconds=10))\nasync def every():\n    print(\"Every 10 seconds, I make a quantum leap. Where will I land next?\")\n\n\n@group.task(trigger=Every(seconds=5))\ndef even_sync_works():\n    print(\"I'm a synchronous task. I work even in async world.\")\n\n\n@group.task(trigger=At(tz=\"UTC\", hour=0, minute=0, second=0))\nasync def at():\n    print(\n        \"When the clock strikes midnight... I turn into a pumpkin. Just kidding, I run this task!\"\n    )\n\n\n@group.task(trigger=Forever())\nasync def forever(val: str = Depends(more_useless_than_me)):\n    await asyncio.sleep(2)\n    print(\"Heartbeat detected. Still not a zombie. Will check again in a bit.\")\n    assert val == \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Once())\nasync def once():\n    print(\"Just once, I get to say something. Here it goes... I love lamp.\")\n\n\n\n@asynccontextmanager\nasync def lifespan(aio_clock: AioClock) -> AsyncGenerator[AioClock]:\n    print(\n        \"Welcome to the Async Chronicles! Did you know a group of unicorns is called a blessing? Well, now you do!\"\n    )\n    yield aio_clock\n    print(\"Going offline. Remember, if your code is running, you better go catch it!\")\n\n\napp = AioClock(lifespan=lifespan)\napp.include_group(group)\n\n# main.py\nif __name__ == \"__main__\":\n    asyncio.run(app.serve())\n```\n"
  },
  {
    "path": "docs/overview.md",
    "content": "# AioClock: Overview\n\n## Introduction\n\nAioClock is a lightweight, asynchronous task scheduling framework designed for Python applications. It provides a structured approach to managing, scheduling, and executing tasks with integrated dependency injection, making it highly modular and testable.\n\n## Key Components\n\n### 1. AioClock Application\n\nThe central orchestrator for all tasks. It initializes, manages, and executes tasks based on their defined triggers.\n\n### 2. Dependency Injection System\n\nA built-in system for injecting dependencies into tasks, enabling loose coupling and improving testability. It uses [FastDepends](https://lancetnik.github.io/FastDepends/) internally, which is very fimiliar with [FastAPI Dependency Injection System](https://fastapi.tiangolo.com/tutorial/dependencies/)\n\n### 3. Task\n\nA unit of work that can be scheduled and executed asynchronously. Tasks are defined using specific triggers that determine their execution conditions.\n\n### 4. Trigger\n\nConditions or events that initiate task execution. It includes\n\n- **Every**: Repeats a task at regular intervals.\n- **At**: Executes a task at a specified time.\n- **Once**: Runs a task a single time.\n- **OnStartUp**: Runs when the application starts. (DEPRECATED in favor of lifespan)\n- **OnShutDown**: Executes during application shutdown. (DEPRECATED in favor of lifespan)\n- **Forever**: Continuously runs a task in an infinite loop.\n- **Cron**: Uses cron syntax for scheduling.\n- **OrTrigger**: Initiate with a list of triggers, and executes when at least one of the included triggers activate.\n\n### 5. Group\n\nA logical collection of tasks. Groups allow related tasks to be bundled and managed together, simplifying task organization and execution. Group allow you to code with aioclock in modular way.\n\n### 6. Task Runner\n\nThe engine that monitors and executes tasks according to their triggers, ensuring tasks run in the correct order and at the right time.\n\n### 7. Serve\n\nThe entry point that starts the AioClock application. It initiates the task runner, which monitors and executes tasks based on their triggers.\n\n### 8. Lifespan\n\nA context manager that will be used to handle the startup and shutdown of the application. It is used inside AioClock application.\n\n### 9. Callable\n\nA function or method associated with a task, executed when the task's trigger condition is met.\n\n## Diagrams\n\n### Ownership\n\n![Ownership Diagram](images/ownership-diagram.png)\n\nIn AioClock, tasks are managed through clear ownership within groups, using dependency injection. Groups encapsulate related tasks, each with specific triggers and callables. The include_group() function integrates these groups into the AioClock application, while standalone tasks are managed with decorators like @aioclock.task. This structure ensures that tasks are organized, maintainable, and easy to scale, with each component having a defined responsibility within the application.\nThe dependency injection system in AioClock allows you to override a callable with another through the application interface, facilitating testing. For example, instead of returning a session from a PostgreSQL database with get_session, you can override it to use get_sqlite_session, which provides a SQLite session instead. This flexibility makes it easier to swap out components for testing or other purposes without changing the core logic.\n\n### Aioclock LifeCycle\n\n![Aioclock LifeCycle](images/lifecycle-diagram.png)\n\nThis diagram shows the lifecycle of an AioClock application. It starts with the app.serve() call, which gathers all tasks and groups. The application then checks for startup tasks, runs them, and proceeds to other tasks. If a shutdown task is detected, it's executed before the application gracefully exits. If no shutdown tasks are present, the application simply exits. The diagram ensures a clear, step-by-step process for task management within the AioClock framework, ensuring tasks are executed in the proper order.\n\nP.S: Since startup and shutdown task are deprcated, lifespan has same side effect as them, with extra benefit of having them with a shared memory state. Please reffer to lifespan API Documentation to understand it better with examples.\n\n### Task Runner Execution Flow\n\n![Task Runner Execution Flow](images/task-runner-diagram.png)\n\nThis diagram breaks down how a task is handled in the AioClock system. The task runner first receives the task, checks if it’s still valid to run, and then either waits for the appropriate trigger (like a scheduled time) or finishes if it’s no longer needed. If valid, the task is executed; otherwise, the system gracefully exits. This loop continues until all tasks are either completed or stopped, ensuring everything runs smoothly and on time.\n"
  },
  {
    "path": "docs/plugins.py",
    "content": "import os\nimport re\nfrom typing import Match\n\nfrom mkdocs.config import Config\nfrom mkdocs.structure.files import Files\nfrom mkdocs.structure.pages import Page\n\ntry:\n    import pytest\nexcept ImportError:\n    pytest = None\n\n\ndef on_pre_build(config: Config):\n    pass\n\n\ndef on_files(files: Files, config: Config) -> Files:\n    return remove_files(files)\n\n\ndef remove_files(files: Files) -> Files:\n    to_remove = []\n    for file in files:\n        if file.src_path in {\"plugins.py\"}:\n            to_remove.append(file)\n        elif file.src_path.startswith(\"__pycache__/\"):\n            to_remove.append(file)\n\n    for f in to_remove:\n        files.remove(f)\n\n    return files\n\n\ndef on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str:\n    markdown = remove_code_fence_attributes(markdown)\n    return add_version(markdown, page)\n\n\ndef add_version(markdown: str, page: Page) -> str:\n    if page.file.src_uri == \"index.md\":\n        version_ref = os.getenv(\"GITHUB_REF\")\n        if version_ref and version_ref.startswith(\"refs/tags/\"):\n            version = re.sub(\"^refs/tags/\", \"\", version_ref.lower())\n            url = f\"https://ManiMozaffar.github.io/aioclock/releases/tag/{version}\"\n            version_str = f\"Documentation for version: [{version}]({url})\"\n        elif sha := os.getenv(\"GITHUB_SHA\"):\n            sha = sha[:7]\n            url = f\"https://ManiMozaffar.github.io/aioclock/commit/{sha}\"\n            version_str = f\"Documentation for development version: [{sha}]({url})\"\n        else:\n            version_str = \"Documentation for development version\"\n        markdown = re.sub(r\"{{ *version *}}\", version_str, markdown)\n    return markdown\n\n\ndef remove_code_fence_attributes(markdown: str) -> str:\n    \"\"\"\n    There's no way to add attributes to code fences that works with both pycharm and mkdocs, hence we use\n    `py key=\"value\"` to provide attributes to pytest-examples, then remove those attributes here.\n\n    https://youtrack.jetbrains.com/issue/IDEA-297873 & https://python-markdown.github.io/extensions/fenced_code_blocks/\n    \"\"\"\n\n    def remove_attrs(match: Match[str]) -> str:\n        suffix = re.sub(\n            r' (?:test|lint|upgrade|group|requires|output|rewrite_assert)=\".+?\"',\n            \"\",\n            match.group(2),\n            flags=re.M,\n        )\n        return f\"{match.group(1)}{suffix}\"\n\n    return re.sub(r\"^( *``` *py)(.*)\", remove_attrs, markdown, flags=re.M)\n"
  },
  {
    "path": "examples/app.py",
    "content": "import asyncio\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\nimport threading\nfrom time import sleep\nfrom typing import Annotated\n\nfrom aioclock import AioClock, Depends, Every, Group\n\n# service1.py\ngroup = Group()\n\n\ndef dependency():\n    return \"Hello from thread: \"\n\n\n@group.task(trigger=Every(seconds=2))\ndef sync_task_1(val: str = Depends(dependency)):\n    print(f\"{val} `sync_task_1` {threading.current_thread().ident}\")\n    sleep(1)  # some blocking operation\n\n\n@group.task(trigger=Every(seconds=2.01))\ndef sync_task_2(val: Annotated[str, Depends(dependency)]):\n    print(f\"{val} `sync_task_2` {threading.current_thread().ident}\")\n    sleep(1)  # some blocking operation\n    return \"3\"\n\n\nprint(sync_task_2(\"Aioclock won't color your functions!\"))\n\n\n@group.task(trigger=Every(seconds=2))\nasync def async_task(val: str = Depends(dependency)):\n    print(f\"{val} `async_task` {threading.current_thread().ident}\")\n\n\n@asynccontextmanager\nasync def lifespan(aio_clock: AioClock) -> AsyncGenerator[AioClock]:\n    print(\"Welcome!\")\n    yield aio_clock\n    print(\"Bye!\")\n\n\n# app.py\napp = AioClock(lifespan=lifespan)\napp.include_group(group)\n\n\nif __name__ == \"__main__\":\n    asyncio.run(app.serve())\n"
  },
  {
    "path": "examples/awesome_triggers.py",
    "content": "import asyncio\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\n\nfrom aioclock import AioClock, At, Depends, Every, Forever, Once\nfrom aioclock.group import Group\n\n# groups.py\ngroup = Group()\n\n\ndef more_useless_than_me():\n    return \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Every(seconds=10))\nasync def every():\n    print(\"Every 10 seconds, I make a quantum leap. Where will I land next?\")\n\n\n@group.task(trigger=Every(seconds=5))\ndef even_sync_works():\n    print(\"I'm a synchronous task. I work even in async world.\")\n\n\n@group.task(trigger=At(tz=\"UTC\", hour=0, minute=0, second=0))\nasync def at():\n    print(\n        \"When the clock strikes midnight... I turn into a pumpkin. Just kidding, I run this task!\"\n    )\n\n\n@group.task(trigger=Forever())\nasync def forever(val: str = Depends(more_useless_than_me)):\n    await asyncio.sleep(2)\n    print(\"Heartbeat detected. Still not a zombie. Will check again in a bit.\")\n    assert val == \"I'm a dependency. I'm more useless than a screen door on a submarine.\"\n\n\n@group.task(trigger=Once())\nasync def once():\n    print(\"Just once, I get to say something. Here it goes... I love lamp.\")\n\n\n@asynccontextmanager\nasync def lifespan(aio_clock: AioClock) -> AsyncGenerator[AioClock]:\n    print(\n        \"Welcome to the Async Chronicles! Did you know a group of unicorns is called a blessing? Well, now you do!\"\n    )\n    yield aio_clock\n    print(\"Going offline. Remember, if your code is running, you better go catch it!\")\n\n\n# app.py\napp = AioClock(lifespan=lifespan)\napp.include_group(group)\n\n\n# main.py\nif __name__ == \"__main__\":\n    asyncio.run(app.serve())\n"
  },
  {
    "path": "examples/dependency_injection.py",
    "content": "import asyncio\n\nfrom aioclock import AioClock, Depends, Every, Group\n\n# service1.py\ngroup = Group()\n\n\ndef dependency():\n    return \"Hello, world!\"\n\n\ndef overwritten_dependency():\n    return \"Goodbye, world!\"\n\n\n@group.task(trigger=Every(seconds=1))\nasync def my_task(val: str = Depends(dependency)):\n    print(val)\n\n\n# app.py\napp = AioClock()\napp.include_group(group)\n\n\napp.override_dependencies(dependency, overwritten_dependency)\n\nif __name__ == \"__main__\":\n    asyncio.run(app.serve())\n"
  },
  {
    "path": "examples/with_fast_api.py",
    "content": "import asyncio\nfrom collections.abc import AsyncGenerator\nfrom contextlib import asynccontextmanager\n\nfrom fastapi import FastAPI\n\nfrom aioclock import AioClock\nfrom aioclock.ext.fast import make_fastapi_router\nfrom aioclock.triggers import Every\n\n\n@asynccontextmanager\nasync def aioclock_lifespan(aio_clock: AioClock) -> AsyncGenerator[AioClock]:\n    print(\"Starting aiolcok app...\")\n    yield aio_clock\n    print(\"Closing aiolcok app...\")\n\n\nclock_app = AioClock(lifespan=aioclock_lifespan)\n\n\n@clock_app.task(trigger=Every(seconds=3600))\nasync def foo():\n    print(\"Foo is processing...\")\n\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    task = asyncio.create_task(clock_app.serve())\n    yield\n\n    try:\n        task.cancel()\n        await task\n    except asyncio.CancelledError:\n        ...\n\n\napp = FastAPI(lifespan=lifespan)\napp.include_router(make_fastapi_router(clock_app))\n\nif __name__ == \"__main__\":\n    import uvicorn\n\n    uvicorn.run(app)\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: AioClock\nsite_description: An asyncio-based scheduling framework designed for execution of periodic task with integrated support for dependency injection, enabling efficient and flexiable task management\nrepo_url: https://github.com/ManiMozaffar/aioclock\nsite_url: https://ManiMozaffar.github.io/aioclock\nsite_author: Mani Mozaffar\nrepo_name: ManiMozaffar/aioclock\ncopyright: Maintained by <a href=\"https://ManiMozaffar.com\">Mani Mozaffar</a>.\n\ntheme:\n  name: \"material\"\n  palette:\n    - media: \"(prefers-color-scheme)\"\n      toggle:\n        icon: material/link\n        name: Switch to light mode\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      primary: indigo\n      accent: indigo\n      toggle:\n        icon: material/toggle-switch\n        name: Switch to dark mode\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      primary: black\n      accent: indigo\n      toggle:\n        icon: material/toggle-switch-off\n        name: Switch to system preference\n\n  features:\n    - navigation.tabs\n    - navigation.instant\n    - content.code.annotate\n    - content.tabs.link\n    - content.code.copy\n    - announce.dismiss\n    - search.suggest\n    - search.highlight\n  # logo: assets/logo-white.svg\n  # favicon: assets/favicon.png\n\nedit_uri: \"\"\n\n# https://www.mkdocs.org/user-guide/configuration/#validation\nvalidation:\n  omitted_files: warn\n  absolute_links: warn\n  unrecognized_links: warn\n\nextra:\n  navigation:\n    next: true\n    previous: true\n\nextra_css:\n  - \"extra/tweaks.css\"\n\nmarkdown_extensions:\n  - toc:\n      permalink: true\n  - admonition\n  - pymdownx.details\n  - pymdownx.extra\n  - pymdownx.superfences\n  - pymdownx.highlight:\n      anchor_linenums: true\n  - pymdownx.inlinehilite\n  - pymdownx.snippets\n  - attr_list\n  - md_in_html\n\nwatch:\n  - aioclock\nplugins:\n  - mike:\n      alias_type: symlink\n      canonical_version: latest\n  - search:\n  - mkdocstrings:\n      handlers:\n        python:\n          paths:\n            - aioclock\n          options:\n            members_order: source\n            separate_signature: true\n            docstring_options:\n              ignore_init_summary: true\n            merge_init_into_class: true\n            show_signature_annotations: true\n            signature_crossrefs: true\n\n  - mkdocs-simple-hooks:\n      hooks:\n        on_pre_build: \"docs.plugins:on_pre_build\"\n        on_files: \"docs.plugins:on_files\"\n        on_page_markdown: \"docs.plugins:on_page_markdown\"\n\nnav:\n  - Introduction: index.md\n\n  - Documentation:\n      - Overview: overview.md\n\n      - Basic Usage:\n          - AioClock Application: api/getting_started.md\n          - Triggers: api/triggers.md\n\n      - Advance Usage:\n          - Task: api/task.md\n          - External API: api/external_api.md\n          - Plugins (FastAPI included): api/plugin.md\n          - Using beside FastAPI: examples/fastapi.md\n          - Using beside Message Brokers: examples/brokers.md\n\n      - Alternatives: alternative.md\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[project]\nname = \"aioclock\"\nversion = \"0.3.1\"\ndescription = \"An asyncio-based scheduling framework designed for execution of periodic task with integrated support for dependency injection, enabling efficient and flexible task management\"\nauthors = [{ name = \"Mani Mozaffar\", email = \"mani.mozaffar@gmail.com\" }]\nreadme = \"README.md\"\nrequires-python = \">= 3.8\"\ndependencies = [\n    \"pydantic[timezone]>=2.9.0\",\n    \"fast-depends>=2.4.0\",\n    \"asyncer>=0.0.7\",\n    \"croniter>=2.0.5\",\n]\nlicense = 'MIT'\n\n\n[project.urls]\nrepository = \"https://github.com/ManiMozaffar/aioclock\"\nHomepage = 'https://github.com/ManiMozaffar/aioclock'\nDocumentation = 'https://github.com/ManiMozaffar/aioclock'\nSource = 'https://github.com/ManiMozaffar/aioclock'\n\n\n[project.optional-dependencies]\nfastapi = [\"fastapi\"]\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n\n[tool.rye]\ndev-dependencies = [\n    \"pytest>=7.2.0\",\n    \"pytest-asyncio>=0.23.6\",\n    \"tox>=4.11.1\",\n    \"rich>=13.7.1\",\n    \"pyright>=1.1.350\",\n    \"mike==2.0.0\",\n    \"pytest-asyncio>=0.23.7\",\n    \"mkdocs>=1.4.2\",\n    \"mkdocs-material>=9.2.7\",\n    \"mkdocstrings[python]>=0.25.1\",\n    \"mkdocs-simple-hooks>=0.1.5\",\n    \"black>=24.4.2\",\n    \"fastapi>=0.90\",\n    \"ruff>=0.3.5\",\n    \"mkdocs-material-extensions>=1.3.1\",\n\n]\n\n[tool.behavior]\nuse-uv = true\n\n[tool.hatch.metadata]\nallow-direct-references = true\n\n[tool.hatch.build.targets.wheel]\npackages = [\"aioclock\"]\n\n[tool.setuptools.package-data]\n\"aioclock\" = [\"py.typed\"]\n\n[tool.ruff]\n\nlint.fixable = [\n    \"A\",\n    \"B\",\n    \"C\",\n    \"D\",\n    \"E\",\n    \"F\",\n    \"G\",\n    \"I\",\n    \"N\",\n    \"Q\",\n    \"S\",\n    \"T\",\n    \"W\",\n    \"ANN\",\n    \"ARG\",\n    \"BLE\",\n    \"COM\",\n    \"DJ\",\n    \"DTZ\",\n    \"EM\",\n    \"ERA\",\n    \"EXE\",\n    \"FBT\",\n    \"ICN\",\n    \"INP\",\n    \"ISC\",\n    \"NPY\",\n    \"PD\",\n    \"PGH\",\n    \"PIE\",\n    \"PL\",\n    \"PT\",\n    \"PTH\",\n    \"PYI\",\n    \"RET\",\n    \"RSE\",\n    \"RUF\",\n    \"SIM\",\n    \"SLF\",\n    \"TCH\",\n    \"TID\",\n    \"TRY\",\n    \"UP\",\n    \"YTT\",\n]\nlint.unfixable = []\n\nlint.exclude = [\n    \".bzr\",\n    \".direnv\",\n    \".eggs\",\n    \".git\",\n    \".git-rewrite\",\n    \".hg\",\n    \".mypy_cache\",\n    \".nox\",\n    \".pants.d\",\n    \".pytype\",\n    \".ruff_cache\",\n    \".svn\",\n    \".tox\",\n    \".venv\",\n    \"__pypackages__\",\n    \"_build\",\n    \"buck-out\",\n    \"build\",\n    \"dist\",\n    \"node_modules\",\n    \"venv\",\n]\n\nline-length = 100\n\n[tool.ruff.format]\npreview = true\n\n[tool.ruff.lint.per-file-ignores]\n\"tests/*\" = [\"S101\"]\n\n[tool.pyright]\ntypeCheckingMode = \"basic\"\n\n[tool.pytest.ini_options]\ntestpaths = [\"tests\"]\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/test_di.py",
    "content": "import pytest\n\nfrom aioclock import AioClock, Depends, Once\nfrom aioclock.api import run_with_injected_deps\n\napp = AioClock()\n\n\ndef some_dependency():\n    return 1\n\n\n@app.task(trigger=Once())\nasync def main(bar: int = Depends(some_dependency)):\n    print(\"Hello World\")\n    return bar\n\n\n@pytest.mark.asyncio\nasync def test_run_manual():\n    foo = await run_with_injected_deps(main)\n    assert foo == 1\n"
  },
  {
    "path": "tests/test_examples.py",
    "content": "import importlib.util\nimport inspect\nimport logging\nimport os\nimport sys\nimport textwrap\n\nlogger = logging.getLogger(__name__)\n\n\ndef extract_code_blocks(docstring: str) -> list[str]:\n    \"\"\"Extract code blocks from a docstring.\"\"\"\n    code_blocks = []\n    in_code_block = False\n    code_block = []\n\n    for line in docstring.split(\"\\n\"):\n        if line.strip().startswith(\"```python\"):\n            in_code_block = True\n            continue\n        elif line.strip().startswith(\"```\"):\n            in_code_block = False\n            if code_block:\n                code_blocks.append(\"\\n\".join(code_block))\n                code_block = []\n            continue\n\n        if in_code_block:\n            code_block.append(line)\n\n    return code_blocks\n\n\ndef exec_example(example: str) -> None:\n    \"\"\"Execute a code example.\"\"\"\n    example = textwrap.dedent(example)\n    namespace = {\"__name__\": \"__not_main__\"}\n    try:\n        code = compile(example, \"<string>\", \"exec\")\n        exec(code, namespace)\n    except Exception:\n        raise Exception(f\"Failed to execute example:\\n\\n{example}\")\n\n\ndef process_object(name: str, obj: object, module_name: str, library_name: str) -> None:\n    \"\"\"Process a single object, extracting and executing code examples.\"\"\"\n    docstring = inspect.getdoc(obj)\n    if docstring:\n        examples = extract_code_blocks(docstring)\n        for example in examples:\n            exec_example(example)\n\n    # Recursively process class methods only if they belong to the same library\n    if inspect.isclass(obj):\n        for method_name, method_obj in inspect.getmembers(obj, inspect.isfunction):\n            if method_obj.__module__ and method_obj.__module__.startswith(library_name):\n                process_object(f\"{name}.{method_name}\", method_obj, module_name, library_name)\n\n\ndef process_module(module_path: str) -> None:\n    \"\"\"Process a single Python module.\"\"\"\n    module_name = os.path.splitext(os.path.basename(module_path))[0]\n    spec = importlib.util.spec_from_file_location(module_name, module_path)\n    if spec is None or spec.loader is None:\n        raise Exception(f\"Could not load module: {module_path}\")\n\n    module = importlib.util.module_from_spec(spec)\n    sys.modules[module_name] = module\n    spec.loader.exec_module(module)\n\n    library_name = \"aioclock\"\n\n    for name, obj in inspect.getmembers(\n        module, lambda o: inspect.isfunction(o) or inspect.isclass(o)\n    ):\n        if obj.__module__ and obj.__module__.startswith(library_name):\n            process_object(name, obj, module_name, library_name)\n\n\ndef process_markdown(markdown_path: str) -> None:\n    \"\"\"Process a markdown file and execute code blocks.\"\"\"\n    with open(markdown_path, \"r\") as file:\n        content = file.read()\n\n    code_blocks = extract_code_blocks(content)\n    for example in code_blocks:\n        exec_example(example)\n\n\ndef traverse_library(library_path: str) -> None:\n    \"\"\"Traverse the library directory and process each Python module.\"\"\"\n    original_sys_path = sys.path.copy()\n    sys.path.insert(0, os.path.abspath(library_path))  # Prioritize the library path\n\n    for root, _, files in os.walk(library_path):\n        for file in files:\n            if file.endswith(\".py\"):\n                module_path = os.path.join(root, file)\n                process_module(module_path)\n\n    sys.path = original_sys_path  # Restore the original sys.path\n\n\ndef traverse_docs(docs_path: str) -> None:\n    \"\"\"Traverse the docs directory and process each markdown file.\"\"\"\n    for root, _, files in os.walk(docs_path):\n        for file in files:\n            if file.endswith(\".md\"):\n                markdown_path = os.path.join(root, file)\n                process_markdown(markdown_path)\n\n\ndef test_examples():\n    docs_path = \"../aioclock/docs\"\n    traverse_docs(docs_path)\n\n    library_path = \"../aioclock/aioclock\"\n    traverse_library(library_path)\n"
  },
  {
    "path": "tests/test_lifespan.py",
    "content": "from contextlib import asynccontextmanager, contextmanager\n\nimport pytest\n\nfrom aioclock import AioClock\nfrom aioclock.triggers import Once\n\nML_MODEL_ASYNC = []\nRAN_ONCE_TASK_ASYNC = False\n\n\n@asynccontextmanager\nasync def lifespan(app: AioClock):\n    ML_MODEL_ASYNC.append(2)\n    yield app\n    ML_MODEL_ASYNC.clear()\n\n\napp = AioClock(lifespan=lifespan)\n\n\n@app.task(trigger=Once())\nasync def main():\n    assert len(ML_MODEL_ASYNC) == 1\n    global RAN_ONCE_TASK_ASYNC\n    RAN_ONCE_TASK_ASYNC = True\n\n\n@pytest.mark.asyncio\nasync def test_lifespan_e2e_async():\n    assert len(ML_MODEL_ASYNC) == 0\n    assert RAN_ONCE_TASK_ASYNC is False\n    await app.serve()  # asserts are in the task\n    assert len(ML_MODEL_ASYNC) == 0  # clean up done\n    assert RAN_ONCE_TASK_ASYNC is True  # task ran\n\n\nML_MODEL_SYNC = []  # just some imaginary component that needs to be started and stopped\nRAN_ONCE_TASK_SYNC = False\n\n\n@contextmanager\ndef lifespan_sync(sync_app: AioClock):\n    ML_MODEL_SYNC.append(2)\n    yield sync_app\n    ML_MODEL_SYNC.clear()\n\n\nsync_app = AioClock(lifespan=lifespan_sync)\n\n\n@sync_app.task(trigger=Once())\ndef sync_main():\n    assert len(ML_MODEL_SYNC) == 1\n    global RAN_ONCE_TASK_SYNC\n    RAN_ONCE_TASK_SYNC = True\n\n\n@pytest.mark.asyncio\nasync def test_lifespan_e2e_sync():\n    assert len(ML_MODEL_SYNC) == 0\n    assert RAN_ONCE_TASK_SYNC is False\n    await sync_app.serve()  # asserts are in the task\n    assert len(ML_MODEL_SYNC) == 0  # clean up done\n    assert RAN_ONCE_TASK_SYNC is True  # task ran\n"
  },
  {
    "path": "tests/test_timeout.py",
    "content": "import asyncio\nfrom contextlib import contextmanager\nfrom datetime import datetime\n\nimport pytest\n\nfrom aioclock import AioClock, Once\n\napp = AioClock()\n\n\n@contextmanager\ndef assert_execution_time_below(target: float):\n    start_time = datetime.now()\n    yield\n    assert (datetime.now() - start_time).total_seconds() < target\n\n\n@app.task(trigger=Once(), timeout=0.1)\nasync def main():\n    await asyncio.sleep(10)\n\n\n@pytest.mark.asyncio\nasync def test_run_manual():\n    task_with_timeout = app._get_tasks()[0]\n    with assert_execution_time_below(0.5):\n        await task_with_timeout.run()\n"
  },
  {
    "path": "tests/test_triggers.py",
    "content": "from datetime import datetime, timedelta\n\nimport pytest\nimport zoneinfo\n\nfrom aioclock.triggers import At, Cron, Every, Forever, LoopController, Once, OrTrigger\n\n\ndef test_at_trigger():\n    # test this sunday\n    trigger = At(at=\"every sunday\", hour=14, minute=1, second=0, tz=\"Europe/Istanbul\")\n\n    val = trigger._get_next_ts(\n        datetime(\n            year=2024,\n            month=3,\n            day=31,\n            hour=14,\n            minute=00,\n            second=0,\n            tzinfo=zoneinfo.ZoneInfo(\"Europe/Istanbul\"),\n        )\n    )\n    assert val == 60\n\n    # test next week\n    trigger = At(at=\"every sunday\", hour=14, second=59, tz=\"Europe/Istanbul\")\n\n    val = trigger._get_next_ts(\n        datetime(\n            year=2024,\n            month=3,\n            day=31,\n            hour=14,\n            minute=0,\n            second=0,\n            tzinfo=zoneinfo.ZoneInfo(\"Europe/Istanbul\"),\n        )\n    )\n    assert val == 59\n\n    # test every day\n    trigger = At(at=\"every day\", hour=14, second=59, tz=\"Europe/Istanbul\")\n    this_sunday = datetime(\n        year=2024,\n        month=3,\n        day=31,\n        hour=14,\n        minute=0,\n        second=0,\n        tzinfo=zoneinfo.ZoneInfo(\"Europe/Istanbul\"),\n    )\n    val = trigger._get_next_ts(this_sunday)\n    assert val == 59\n\n    # test next week\n    trigger = At(at=\"every saturday\", hour=14, second=0, tz=\"Europe/Istanbul\")\n    val = trigger._get_next_ts(this_sunday)\n    assert val == 86400\n\n    # right NOW\n    trigger = At(at=\"every sunday\", hour=14, tz=\"Europe/Istanbul\")\n    val = trigger._get_next_ts(this_sunday)\n    assert val == 0\n\n    # next week but 1h before than now\n    trigger = At(at=\"every sunday\", hour=13, tz=\"Europe/Istanbul\")\n    val = trigger._get_next_ts(this_sunday)\n    assert val == timedelta(days=7).total_seconds() - timedelta(hours=1).total_seconds()\n\n\ndef test_cross_timezone():\n    # `NOW` is same day as the trigger, but trigger should be next week with different timezone\n    trigger = At(hour=3, tz=\"Europe/Berlin\", at=\"every saturday\")\n    assert (\n        trigger._get_next_ts(\n            datetime.fromtimestamp(1720296232, tz=zoneinfo.ZoneInfo(\"Europe/Istanbul\"))\n        )\n        == 532568\n    )\n\n\n@pytest.mark.asyncio\nasync def test_loop_controller():\n    # since once trigger is triggered, it should not trigger again.\n    trigger = Once()\n    assert trigger.should_trigger() is True\n    await trigger.trigger_next()\n    assert trigger.should_trigger() is False\n\n    class IterateFiveTime(LoopController):\n        type_: str = \"foo\"\n\n        async def trigger_next(self) -> None:\n            self._increment_loop_counter()\n            return None\n\n    trigger = IterateFiveTime(max_loop_count=5)\n    for _ in range(5):\n        assert trigger.should_trigger() is True\n        await trigger.trigger_next()\n\n    assert trigger.should_trigger() is False\n\n\n@pytest.mark.asyncio\nasync def test_forever():\n    trigger = Forever()\n    assert trigger.should_trigger() is True\n    await trigger.trigger_next()\n    assert trigger.should_trigger() is True\n    await trigger.trigger_next()\n    assert trigger.should_trigger() is True\n\n\n@pytest.mark.asyncio\nasync def test_every():\n    # wait should always wait for the period on first run\n    trigger = Every(seconds=1, first_run_strategy=\"wait\")\n    assert await trigger.get_waiting_time_till_next_trigger() == 1\n\n    # immediate should always execute immediately, but wait for the period from second run.\n    trigger = Every(seconds=1, first_run_strategy=\"immediate\")\n    assert await trigger.get_waiting_time_till_next_trigger() == 0\n    trigger._increment_loop_counter()\n    assert await trigger.get_waiting_time_till_next_trigger() == 1\n\n\n@pytest.mark.asyncio\nasync def test_cron():\n    # it's dumb idea to test library, but I don't trust it 100%, and it might drop it in the future.\n\n    trigger = Cron(cron=\"* * * * *\", tz=\"UTC\")\n    val = await trigger.get_waiting_time_till_next_trigger(\n        datetime(\n            year=2024,\n            month=3,\n            day=31,\n            hour=14,\n            minute=0,\n            second=0,\n            tzinfo=zoneinfo.ZoneInfo(\"UTC\"),\n        )\n    )\n    assert val == 60\n\n    trigger = Cron(cron=\"2-10 * * * *\", tz=\"UTC\")\n    assert (\n        await trigger.get_waiting_time_till_next_trigger(\n            datetime(\n                year=2024,\n                month=3,\n                day=31,\n                hour=11,\n                minute=48,\n                second=0,\n                tzinfo=zoneinfo.ZoneInfo(\"Europe/Berlin\"),\n            )\n        )\n        == 14 * 60\n    )\n\n    with pytest.raises(ValueError):\n        Cron(cron=\"* * * * 65\", tz=\"UTC\")\n\n\n@pytest.mark.asyncio\nasync def test_or_trigger_state():\n    trigger = OrTrigger(triggers=[Once(), Once()])\n    assert trigger.should_trigger() is True\n    await trigger.trigger_next()\n    assert trigger.should_trigger() is True\n    await trigger.trigger_next()\n    assert trigger.should_trigger() is False\n\n\n@pytest.mark.asyncio\nasync def test_or_trigger_next():\n    trigger = OrTrigger(\n        triggers=[Every(seconds=0, max_loop_count=2), Every(seconds=0, max_loop_count=2)]\n    )\n    for _ in range(4):\n        assert trigger.should_trigger() is True\n        assert (await trigger.get_waiting_time_till_next_trigger()) == 0\n        await trigger.trigger_next()\n\n    assert trigger.should_trigger() is False\n"
  },
  {
    "path": "tox.ini",
    "content": "[tox]\nskipsdist = true\nenvlist = py39, py310, py311\n\n[gh-actions]\npython =\n    3.9: py39\n    3.10: py310\n    3.11: py311\n\n[testenv:py39]\npassenv = PYTHON_VERSION\nallowlist_externals = rye,pytest,pyright\ncommands =\n    rye pin 3.9\n    rye sync\n    rye run pytest tests\n\n[testenv:py310]\npassenv = PYTHON_VERSION\nallowlist_externals = rye,pytest,pyright\ncommands =\n    rye pin 3.10\n    rye sync\n    rye run pytest tests\n\n[testenv:py311]\npassenv = PYTHON_VERSION\nallowlist_externals = rye,pytest,pyright\ncommands =\n    rye pin 3.11\n    rye sync\n    rye run pytest tests\n"
  }
]