This repository contains the business logic of the Incyclist
indoor cycling app, published on npm as
incyclist-services.
Incyclist separates its user interfaces from its business logic. The front-ends,
web-ui (React, used by the desktop app) and the
mobile app (React Native), decide how things are rendered. This library decides what is
rendered: device pairing, rides, workouts, routes, activities, settings, third-party app
connections, and so on. A front-end should not contain business logic. If you're changing
behaviour, this is usually the right repo.
incyclist-devices ──► incyclist-services ──► web-ui ──► desktop app (Electron)
(trainers, sensors) (this repo) └─► mobile app (React Native)
Prerequisites: Node.js 24 or newer (CI tests against 24.x and 25.x) and npm.
git clone https://github.com/incyclist/services.git
cd services
npm install
npm run build
npm testIf the build and the unit tests pass, you're set up. You don't need any devices, accounts or credentials to work on this repo: unit tests mock everything external.
| Script | Purpose |
|---|---|
npm run build |
Build ESM (lib/esm) and CJS (lib/cjs) output |
npm run dev |
Watch mode: rebuilds lib/esm on every change (see below) |
npm test |
Run the unit tests. Scope to a file or folder with npm test -- <path> |
npm run coverage |
Unit tests with a coverage report |
npm run lint |
ESLint |
npm run typecheck |
TypeScript type check (tsc --noEmit), including test files |
npm run test:e2e |
E2E tests against real backends. Maintainers only, see below |
Don't run bare npx jest or npm run test:all: both include the E2E tests.
Files named *.e2e.test.ts talk to real services (Incyclist backend, Strava, Komoot,
intervals.icu, VeloHero, ...). They need credentials in a local .env file and can create or
change real data. Contributors don't need them. CI runs the unit tests, lint and type check
only.
To see your changes in the running app, clone web-ui
and devices next to this repo:
<your-folder>
├── services (this repo)
├── devices
└── web-ui
In dev mode, web-ui loads both incyclist-services and incyclist-devices from these
sibling folders (../services/lib/esm, ../devices/lib/esm) instead of node_modules. So:
- Run
npm installandnpm run buildonce indevicesand inservices.lib/esmmust exist in both. - Run
npm run devhere. It rebuilds on every change. - Run
npm run devinweb-ui. Every change you save here now shows up in the UI.
See the web-ui README for how to point the desktop app at your local UI.
Each folder under src/ is a domain module with its own index.ts, types.ts, service
classes and colocated *.unit.test.ts files.
| Module | What it covers |
|---|---|
activities |
Recording, saving, listing and uploading completed rides |
api |
Platform bindings (file system, settings, BLE/ANT+/serial, MQTT, ...) and REST helpers |
apps |
Connected third-party apps: Strava, intervals.icu, Komoot, ... |
appstate |
App state and feature toggles |
autoupdate |
UI bundle update checks |
avatars |
Rider avatars |
base |
Base classes and decorators shared by all services |
coaches |
Virtual riders to ride against |
devices |
Device configuration, access, pairing and in-ride device handling |
fileaccess |
Loading local and external files |
i18n |
Localization, countries, units |
maps, services |
Map areas and OpenStreetMap (Overpass) queries |
monitoring |
Online/offline status |
ride |
The live ride: display, controls, route/workout/video ride modes |
routes |
Route list, parsing, download and import |
settings |
User settings |
ui |
UI-facing helpers |
video |
Video handling for video routes |
workouts |
Workout list, import, parsing and execution |
Everything consumers may use is exported from src/index.ts:
import { useDeviceConfiguration, initUserSettings } from 'incyclist-services'useXXX() returns a service singleton, and initXXX() initializes one where needed.
The library never calls platform APIs directly. File access, settings storage, secrets,
Bluetooth, ANT+ and so on all go through bindings (src/api/bindings/) that the host app
must provide at startup. A fork or a new front-end has to supply its own implementations.
The existing front-ends (web-ui for browser/Electron, the mobile app for React Native)
are the reference for how this is done.
Contributions are welcome. Please read:
- CONTRIBUTING.md: workflow, branches, pull requests
- docs/CODEBASE_GUIDE.md: architecture, coding style and testing conventions. Please read this before writing code.