reflex-junction 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. reflex_junction-0.1.0/.github/actions/basic-checks/action.yml +13 -0
  2. reflex_junction-0.1.0/.github/actions/full-checks/action.yml +47 -0
  3. reflex_junction-0.1.0/.github/actions/setup-python-env/action.yml +28 -0
  4. reflex_junction-0.1.0/.github/workflows/_reusable-ci.yml +54 -0
  5. reflex_junction-0.1.0/.github/workflows/ci-forks.yml +20 -0
  6. reflex_junction-0.1.0/.github/workflows/ci.yml +21 -0
  7. reflex_junction-0.1.0/.github/workflows/publish.yml +57 -0
  8. reflex_junction-0.1.0/.gitignore +42 -0
  9. reflex_junction-0.1.0/.pre-commit-config.yaml +26 -0
  10. reflex_junction-0.1.0/LICENSE +21 -0
  11. reflex_junction-0.1.0/PKG-INFO +193 -0
  12. reflex_junction-0.1.0/README.md +170 -0
  13. reflex_junction-0.1.0/Taskfile.yml +99 -0
  14. reflex_junction-0.1.0/custom_components/reflex_junction/__init__.py +33 -0
  15. reflex_junction-0.1.0/custom_components/reflex_junction/base.py +7 -0
  16. reflex_junction-0.1.0/custom_components/reflex_junction/fastapi_helpers.py +83 -0
  17. reflex_junction-0.1.0/custom_components/reflex_junction/junction_provider.py +375 -0
  18. reflex_junction-0.1.0/custom_components/reflex_junction/models.py +39 -0
  19. reflex_junction-0.1.0/docs/getting_started.md +78 -0
  20. reflex_junction-0.1.0/docs/index.md +27 -0
  21. reflex_junction-0.1.0/junction_demo/junction_demo/__init__.py +0 -0
  22. reflex_junction-0.1.0/junction_demo/junction_demo/junction_demo.py +50 -0
  23. reflex_junction-0.1.0/junction_demo/rxconfig.py +5 -0
  24. reflex_junction-0.1.0/mkdocs.yml +44 -0
  25. reflex_junction-0.1.0/pyproject.toml +81 -0
  26. reflex_junction-0.1.0/tests/__init__.py +0 -0
  27. reflex_junction-0.1.0/tests/test_models.py +75 -0
  28. reflex_junction-0.1.0/tests/test_state.py +210 -0
  29. reflex_junction-0.1.0/uv.lock +2236 -0
@@ -0,0 +1,13 @@
1
+ name: 'Basic CI Checks'
2
+ description: 'Runs basic CI checks (lint and typecheck) without secrets'
3
+
4
+ runs:
5
+ using: 'composite'
6
+ steps:
7
+ - name: Run lint
8
+ run: task lint
9
+ shell: bash
10
+
11
+ - name: Run typecheck
12
+ run: task typecheck
13
+ shell: bash
@@ -0,0 +1,47 @@
1
+ name: 'Full CI Checks'
2
+ description: 'Runs full CI including integration tests with secrets'
3
+ inputs:
4
+ junction-api-key:
5
+ description: 'Junction (Vital) API key'
6
+ required: true
7
+
8
+ runs:
9
+ using: 'composite'
10
+ steps:
11
+ - name: Check env vars set
12
+ run: |
13
+ if [ -z "${{ inputs.junction-api-key }}" ]; then
14
+ echo "JUNCTION_API_KEY is not set"
15
+ exit 1
16
+ fi
17
+ shell: bash
18
+
19
+ - name: Create .env
20
+ run: |
21
+ echo "JUNCTION_API_KEY=${{ inputs.junction-api-key }}" >> .env
22
+ shell: bash
23
+
24
+ - name: DEBUG - check python version
25
+ run: uv sync && uv run python --version
26
+ shell: bash
27
+
28
+ - name: Run lint
29
+ run: task lint
30
+ shell: bash
31
+
32
+ - name: Run typecheck
33
+ run: task typecheck
34
+ shell: bash
35
+
36
+ - name: Initialize Reflex
37
+ run: uv run reflex init
38
+ working-directory: junction_demo
39
+ shell: bash
40
+
41
+ - name: Install playwright
42
+ run: uv run playwright install chromium
43
+ shell: bash
44
+
45
+ - name: Run tests
46
+ run: task test
47
+ shell: bash
@@ -0,0 +1,28 @@
1
+ name: 'Setup Python Environment'
2
+ description: 'Sets up Python, uv, and Task for CI'
3
+ inputs:
4
+ python-version:
5
+ description: 'Python version to setup'
6
+ required: true
7
+ github-token:
8
+ description: 'GitHub token for Task installation'
9
+ required: true
10
+
11
+ runs:
12
+ using: 'composite'
13
+ steps:
14
+ - name: Set up Python
15
+ uses: actions/setup-python@v5
16
+ with:
17
+ python-version: ${{ inputs.python-version }}
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v5
21
+ with:
22
+ version: "0.6.5"
23
+
24
+ - name: Install Task
25
+ uses: arduino/setup-task@v2
26
+ with:
27
+ version: 3.x
28
+ repo-token: ${{ inputs.github-token }}
@@ -0,0 +1,54 @@
1
+ name: Reusable CI
2
+
3
+ on:
4
+ workflow_call:
5
+ inputs:
6
+ checkout_ref:
7
+ description: 'Git ref to checkout'
8
+ required: false
9
+ type: string
10
+ default: ''
11
+ checkout_repository:
12
+ description: 'Repository to checkout (for forks)'
13
+ required: false
14
+ type: string
15
+ default: ''
16
+ check_type:
17
+ description: 'Type of checks to run: basic or full'
18
+ required: true
19
+ type: string
20
+ environment:
21
+ description: 'Environment to use for secrets (only for full checks)'
22
+ required: false
23
+ type: string
24
+ default: ''
25
+
26
+ jobs:
27
+ ci:
28
+ runs-on: ubuntu-latest
29
+ strategy:
30
+ matrix:
31
+ python-versions: ["3.11", "3.12", "3.13"]
32
+ environment: ${{ inputs.environment || null }}
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ with:
37
+ ref: ${{ inputs.checkout_ref || github.sha }}
38
+ repository: ${{ inputs.checkout_repository || github.repository }}
39
+
40
+ - name: Setup Python Environment
41
+ uses: ./.github/actions/setup-python-env
42
+ with:
43
+ python-version: ${{ matrix.python-versions }}
44
+ github-token: ${{ secrets.GITHUB_TOKEN }}
45
+
46
+ - name: Run Basic Checks
47
+ if: inputs.check_type == 'basic'
48
+ uses: ./.github/actions/basic-checks
49
+
50
+ - name: Run Full Checks
51
+ if: inputs.check_type == 'full'
52
+ uses: ./.github/actions/full-checks
53
+ with:
54
+ junction-api-key: ${{ secrets.JUNCTION_API_KEY }}
@@ -0,0 +1,20 @@
1
+ name: CI for Fork PRs
2
+
3
+ on:
4
+ pull_request_target:
5
+ # Note: Repo must be set to require approval before running workflows from forks
6
+ # This only runs basic checks without secrets for safety
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ basic-checks:
15
+ uses: ./.github/workflows/_reusable-ci.yml
16
+ with:
17
+ checkout_ref: ${{ github.event.pull_request.head.sha }}
18
+ checkout_repository: ${{ github.event.pull_request.head.repo.full_name }}
19
+ check_type: 'basic'
20
+ secrets: inherit
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*.*.*"
7
+ # branches: [main]
8
+ pull_request:
9
+ branches: [main]
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ ci:
17
+ uses: ./.github/workflows/_reusable-ci.yml
18
+ with:
19
+ check_type: 'full'
20
+ environment: 'demo'
21
+ secrets: inherit
@@ -0,0 +1,57 @@
1
+ name: Publish
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["CI"]
6
+ types:
7
+ - completed
8
+ branches: [v*.*.*]
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ deploy:
16
+ runs-on: ubuntu-latest
17
+ if: ${{ github.event.workflow_run.conclusion == 'success' && github.repository == 'Syntropy-Health/reflex-junction' }}
18
+ environment: deploy
19
+ permissions:
20
+ contents: write
21
+ id-token: write # Required for PyPI trusted publishers
22
+
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.13"
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v5
33
+ with:
34
+ version: "0.6.5"
35
+
36
+ - name: Install Task
37
+ uses: arduino/setup-task@v2
38
+ with:
39
+ version: 3.x
40
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
41
+
42
+ - name: Build component
43
+ run: |
44
+ cd custom_components && uv run python -m reflex.utils.pyi_generator reflex_junction
45
+ cd .. && uv build
46
+
47
+ - name: Publish to PyPI
48
+ uses: pypa/gh-action-pypi-publish@release/v1
49
+ with:
50
+ # Uses trusted publishers (OIDC) — no token needed
51
+ # Falls back to PYPI_TOKEN if trusted publishers not configured
52
+ password: ${{ secrets.PYPI_TOKEN }}
53
+
54
+ - name: Publish docs
55
+ run: |
56
+ uv sync --dev
57
+ uv run mkdocs gh-deploy --force
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ *.egg
9
+
10
+ # Virtual environments
11
+ .venv/
12
+ venv/
13
+
14
+ # Reflex
15
+ .web/
16
+ *.db
17
+
18
+ # IDE
19
+ .idea/
20
+ .vscode/
21
+ *.swp
22
+ *.swo
23
+
24
+ # OS
25
+ .DS_Store
26
+ Thumbs.db
27
+
28
+ # Environment
29
+ .env
30
+ .env.*
31
+ !.env.example
32
+
33
+ # Testing
34
+ .pytest_cache/
35
+ htmlcov/
36
+ .coverage
37
+
38
+ # Build artifacts
39
+ *.pyi
40
+
41
+ # Node (from Reflex)
42
+ node_modules/
@@ -0,0 +1,26 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+
10
+ - repo: https://github.com/astral-sh/ruff-pre-commit
11
+ rev: v0.11.2
12
+ hooks:
13
+ - id: ruff
14
+ args: [--fix]
15
+ - id: ruff-format
16
+
17
+ - repo: https://github.com/RobertCraigie/pyright-python
18
+ rev: v1.1.397
19
+ hooks:
20
+ - id: pyright
21
+
22
+ - repo: https://github.com/astral-sh/uv-pre-commit
23
+ # uv version.
24
+ rev: 0.6.9
25
+ hooks:
26
+ - id: uv-lock
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Syntropy Health
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: reflex-junction
3
+ Version: 0.1.0
4
+ Summary: Reflex custom component wrapping @tryvital/vital-link and integrating the Junction (Vital) health data SDK
5
+ Project-URL: repository, https://github.com/Syntropy-Health/reflex-junction
6
+ Author: Syntropy Health
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: health-data,junction,reflex,reflex-custom-components,vital
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: fastapi>=0.115.0
17
+ Requires-Dist: reflex>=0.8.0
18
+ Requires-Dist: vital>=2.1.0
19
+ Provides-Extra: dev
20
+ Requires-Dist: build; extra == 'dev'
21
+ Requires-Dist: twine; extra == 'dev'
22
+ Description-Content-Type: text/markdown
23
+
24
+ [![CI](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml/badge.svg)](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml)
25
+ [![PyPI](https://img.shields.io/pypi/v/reflex-junction.svg)](https://pypi.org/project/reflex-junction/)
26
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-junction.svg)](https://pypi.org/project/reflex-junction/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
28
+ [![Docs](https://img.shields.io/badge/docs-mkdocs-blue)](https://syntropy-health.github.io/reflex-junction/)
29
+
30
+ # reflex-junction
31
+
32
+ A [Reflex](https://reflex.dev) custom component for integrating [Junction (Vital)](https://tryvital.io/) health data into your application. Connect wearables and health platforms (Oura, Fitbit, Apple Health, Garmin, etc.) with a few lines of Python.
33
+
34
+ ## Features
35
+
36
+ - **JunctionState** — Reflex state management for the Vital API (user creation, provider connections, data refresh)
37
+ - **JunctionUser** — Extended state with health data summaries (activity, sleep, body, meals, workouts)
38
+ - **wrap_app()** — One-line integration that configures your entire Reflex app
39
+ - **junction_provider()** — Component-level integration for wrapping specific pages
40
+ - **Webhook support** — FastAPI router for receiving real-time health data events
41
+ - **Link widget support** — Token generation for the Vital Link provider connection UI
42
+ - **Multi-region** — US and EU sandbox/production environments
43
+
44
+ ## Installation
45
+
46
+ ```bash
47
+ pip install reflex-junction
48
+ ```
49
+
50
+ Or with your preferred package manager:
51
+
52
+ ```bash
53
+ uv add reflex-junction
54
+ poetry add reflex-junction
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Get an API key
60
+
61
+ Sign up at [tryvital.io](https://tryvital.io/) and grab your API key from the dashboard.
62
+
63
+ ### 2. Wrap your app
64
+
65
+ ```python
66
+ import os
67
+ import reflex as rx
68
+ import reflex_junction as junction
69
+
70
+ app = rx.App()
71
+
72
+ junction.wrap_app(
73
+ app,
74
+ api_key=os.environ["JUNCTION_API_KEY"],
75
+ environment="sandbox", # or "production"
76
+ register_user_state=True,
77
+ )
78
+ ```
79
+
80
+ ### 3. Use Junction state in your pages
81
+
82
+ ```python
83
+ def health_dashboard() -> rx.Component:
84
+ return rx.container(
85
+ rx.heading("Health Dashboard"),
86
+ rx.text(f"Connected: {junction.JunctionState.has_connections}"),
87
+ rx.foreach(
88
+ junction.JunctionState.connected_sources,
89
+ lambda p: rx.badge(p["name"]),
90
+ ),
91
+ )
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ ### Using `junction_provider` directly
97
+
98
+ For page-level integration instead of app-wide:
99
+
100
+ ```python
101
+ import reflex_junction as junction
102
+
103
+ def health_page() -> rx.Component:
104
+ return junction.junction_provider(
105
+ rx.container(
106
+ rx.text("Connected providers: "),
107
+ rx.text(junction.JunctionState.connected_sources.length()),
108
+ ),
109
+ api_key=os.environ["JUNCTION_API_KEY"],
110
+ )
111
+ ```
112
+
113
+ ### Webhook support
114
+
115
+ Receive real-time events when health data updates:
116
+
117
+ ```python
118
+ junction.wrap_app(
119
+ app,
120
+ api_key=os.environ["JUNCTION_API_KEY"],
121
+ register_webhooks=True,
122
+ webhook_secret=os.environ["JUNCTION_WEBHOOK_SECRET"],
123
+ webhook_prefix="/junction", # POST /junction/webhooks
124
+ )
125
+ ```
126
+
127
+ ### Environment options
128
+
129
+ | Environment | Description |
130
+ |------------------|--------------------|
131
+ | `sandbox` | US sandbox (default) |
132
+ | `production` | US production |
133
+ | `sandbox_eu` | EU sandbox |
134
+ | `production_eu` | EU production |
135
+
136
+ ## API Reference
137
+
138
+ ### State Classes
139
+
140
+ | Class | Description |
141
+ |-------|-------------|
142
+ | `JunctionState` | Core state — user creation, provider management, Link tokens |
143
+ | `JunctionUser` | Extended state — health data summaries (activity, sleep, body, meals, workouts) |
144
+
145
+ ### Configuration Models
146
+
147
+ | Class | Description |
148
+ |-------|-------------|
149
+ | `JunctionConfig` | Environment and region settings |
150
+ | `LinkConfig` | Redirect URL and provider filter for the Link widget |
151
+ | `ProviderInfo` | Provider metadata (name, slug, logo, auth_type) |
152
+
153
+ ### Functions
154
+
155
+ | Function | Description |
156
+ |----------|-------------|
157
+ | `wrap_app(app, api_key, ...)` | One-line app integration with optional webhooks |
158
+ | `junction_provider(*children, api_key, ...)` | Component wrapper for page-level integration |
159
+ | `on_load(handlers)` | Wrap page on_load handlers to wait for Junction init |
160
+ | `register_on_auth_change_handler(handler)` | Register handler to fire after Junction initializes |
161
+ | `create_webhook_router(prefix, secret)` | Create a standalone FastAPI webhook router |
162
+ | `register_webhook_api(app, secret, prefix)` | Register webhook endpoint on a Reflex app |
163
+
164
+ ### JunctionState Events
165
+
166
+ | Event | Description |
167
+ |-------|-------------|
168
+ | `create_user(client_user_id)` | Create a Junction user mapped to your app's user |
169
+ | `get_connected_providers()` | Fetch connected health data providers |
170
+ | `disconnect_provider(provider)` | Disconnect a specific provider by slug |
171
+ | `refresh_data()` | Trigger data sync from all connected providers |
172
+ | `create_link_token(redirect_url)` | Generate a Link widget token |
173
+
174
+ See the [full documentation](https://syntropy-health.github.io/reflex-junction/) for detailed guides.
175
+
176
+ ## Contributing
177
+
178
+ Contributions welcome! We use [Taskfile](https://taskfile.dev/) for common tasks:
179
+
180
+ ```bash
181
+ task install # Install dev dependencies + pre-commit
182
+ task test # Run lint + typecheck + pytest
183
+ task run # Run the demo app
184
+ task run-docs # Serve docs locally at localhost:9000
185
+ task bump-patch # Bump patch version (bug fix)
186
+ task bump-minor # Bump minor version (new feature)
187
+ ```
188
+
189
+ Workflow: Fork → feature branch → add tests → submit PR.
190
+
191
+ ## License
192
+
193
+ [MIT](LICENSE) — Copyright (c) 2025 Syntropy Health
@@ -0,0 +1,170 @@
1
+ [![CI](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml/badge.svg)](https://github.com/Syntropy-Health/reflex-junction/actions/workflows/ci.yml)
2
+ [![PyPI](https://img.shields.io/pypi/v/reflex-junction.svg)](https://pypi.org/project/reflex-junction/)
3
+ [![Python](https://img.shields.io/pypi/pyversions/reflex-junction.svg)](https://pypi.org/project/reflex-junction/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Docs](https://img.shields.io/badge/docs-mkdocs-blue)](https://syntropy-health.github.io/reflex-junction/)
6
+
7
+ # reflex-junction
8
+
9
+ A [Reflex](https://reflex.dev) custom component for integrating [Junction (Vital)](https://tryvital.io/) health data into your application. Connect wearables and health platforms (Oura, Fitbit, Apple Health, Garmin, etc.) with a few lines of Python.
10
+
11
+ ## Features
12
+
13
+ - **JunctionState** — Reflex state management for the Vital API (user creation, provider connections, data refresh)
14
+ - **JunctionUser** — Extended state with health data summaries (activity, sleep, body, meals, workouts)
15
+ - **wrap_app()** — One-line integration that configures your entire Reflex app
16
+ - **junction_provider()** — Component-level integration for wrapping specific pages
17
+ - **Webhook support** — FastAPI router for receiving real-time health data events
18
+ - **Link widget support** — Token generation for the Vital Link provider connection UI
19
+ - **Multi-region** — US and EU sandbox/production environments
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ pip install reflex-junction
25
+ ```
26
+
27
+ Or with your preferred package manager:
28
+
29
+ ```bash
30
+ uv add reflex-junction
31
+ poetry add reflex-junction
32
+ ```
33
+
34
+ ## Quick Start
35
+
36
+ ### 1. Get an API key
37
+
38
+ Sign up at [tryvital.io](https://tryvital.io/) and grab your API key from the dashboard.
39
+
40
+ ### 2. Wrap your app
41
+
42
+ ```python
43
+ import os
44
+ import reflex as rx
45
+ import reflex_junction as junction
46
+
47
+ app = rx.App()
48
+
49
+ junction.wrap_app(
50
+ app,
51
+ api_key=os.environ["JUNCTION_API_KEY"],
52
+ environment="sandbox", # or "production"
53
+ register_user_state=True,
54
+ )
55
+ ```
56
+
57
+ ### 3. Use Junction state in your pages
58
+
59
+ ```python
60
+ def health_dashboard() -> rx.Component:
61
+ return rx.container(
62
+ rx.heading("Health Dashboard"),
63
+ rx.text(f"Connected: {junction.JunctionState.has_connections}"),
64
+ rx.foreach(
65
+ junction.JunctionState.connected_sources,
66
+ lambda p: rx.badge(p["name"]),
67
+ ),
68
+ )
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ### Using `junction_provider` directly
74
+
75
+ For page-level integration instead of app-wide:
76
+
77
+ ```python
78
+ import reflex_junction as junction
79
+
80
+ def health_page() -> rx.Component:
81
+ return junction.junction_provider(
82
+ rx.container(
83
+ rx.text("Connected providers: "),
84
+ rx.text(junction.JunctionState.connected_sources.length()),
85
+ ),
86
+ api_key=os.environ["JUNCTION_API_KEY"],
87
+ )
88
+ ```
89
+
90
+ ### Webhook support
91
+
92
+ Receive real-time events when health data updates:
93
+
94
+ ```python
95
+ junction.wrap_app(
96
+ app,
97
+ api_key=os.environ["JUNCTION_API_KEY"],
98
+ register_webhooks=True,
99
+ webhook_secret=os.environ["JUNCTION_WEBHOOK_SECRET"],
100
+ webhook_prefix="/junction", # POST /junction/webhooks
101
+ )
102
+ ```
103
+
104
+ ### Environment options
105
+
106
+ | Environment | Description |
107
+ |------------------|--------------------|
108
+ | `sandbox` | US sandbox (default) |
109
+ | `production` | US production |
110
+ | `sandbox_eu` | EU sandbox |
111
+ | `production_eu` | EU production |
112
+
113
+ ## API Reference
114
+
115
+ ### State Classes
116
+
117
+ | Class | Description |
118
+ |-------|-------------|
119
+ | `JunctionState` | Core state — user creation, provider management, Link tokens |
120
+ | `JunctionUser` | Extended state — health data summaries (activity, sleep, body, meals, workouts) |
121
+
122
+ ### Configuration Models
123
+
124
+ | Class | Description |
125
+ |-------|-------------|
126
+ | `JunctionConfig` | Environment and region settings |
127
+ | `LinkConfig` | Redirect URL and provider filter for the Link widget |
128
+ | `ProviderInfo` | Provider metadata (name, slug, logo, auth_type) |
129
+
130
+ ### Functions
131
+
132
+ | Function | Description |
133
+ |----------|-------------|
134
+ | `wrap_app(app, api_key, ...)` | One-line app integration with optional webhooks |
135
+ | `junction_provider(*children, api_key, ...)` | Component wrapper for page-level integration |
136
+ | `on_load(handlers)` | Wrap page on_load handlers to wait for Junction init |
137
+ | `register_on_auth_change_handler(handler)` | Register handler to fire after Junction initializes |
138
+ | `create_webhook_router(prefix, secret)` | Create a standalone FastAPI webhook router |
139
+ | `register_webhook_api(app, secret, prefix)` | Register webhook endpoint on a Reflex app |
140
+
141
+ ### JunctionState Events
142
+
143
+ | Event | Description |
144
+ |-------|-------------|
145
+ | `create_user(client_user_id)` | Create a Junction user mapped to your app's user |
146
+ | `get_connected_providers()` | Fetch connected health data providers |
147
+ | `disconnect_provider(provider)` | Disconnect a specific provider by slug |
148
+ | `refresh_data()` | Trigger data sync from all connected providers |
149
+ | `create_link_token(redirect_url)` | Generate a Link widget token |
150
+
151
+ See the [full documentation](https://syntropy-health.github.io/reflex-junction/) for detailed guides.
152
+
153
+ ## Contributing
154
+
155
+ Contributions welcome! We use [Taskfile](https://taskfile.dev/) for common tasks:
156
+
157
+ ```bash
158
+ task install # Install dev dependencies + pre-commit
159
+ task test # Run lint + typecheck + pytest
160
+ task run # Run the demo app
161
+ task run-docs # Serve docs locally at localhost:9000
162
+ task bump-patch # Bump patch version (bug fix)
163
+ task bump-minor # Bump minor version (new feature)
164
+ ```
165
+
166
+ Workflow: Fork → feature branch → add tests → submit PR.
167
+
168
+ ## License
169
+
170
+ [MIT](LICENSE) — Copyright (c) 2025 Syntropy Health