TypeComp is a TypeScript library for managing World Cube Association competition scheduling and group assignments. It is heavily inspired by CompScript which is a far more tried and tested utility.
You will need Bun and Python installed.
bun install
pip install ortoolsBefore using TypeComp, you need to set up OAuth authentication with the WCA API.
- Go to WCA OAuth Applications
- Create a new OAuth application
- Set the redirect URI to:
http://localhost:3030/auth/oauth_response(or adjust based on yourPORT,SCHEME, andHOSTsettings) - Note your Client ID and Client Secret
Create a .env file in the project root with:
WCA_CLIENT=your_client_id_here
WCA_SECRET=your_client_secret_hereOptional environment variables (with defaults):
WCA_API- WCA API base URL (default:https://www.worldcubeassociation.org)PORT- OAuth callback port (default:3030)SCHEME- OAuth callback scheme (default:http)HOST- OAuth callback host (default:localhost)
Run the login command:
bun run loginThis will open the WCA's OAuth page in a browser. After authorizing, the tokens will be saved in the .typecomp folder.
TypeComp uses the competition's WCIF. This will be downloaded and cached locally (including changes). When running TypeComp scripts, you can use the following command-line arguments:
--commitor-c- Commits changes to the WCA. By default, TypeComp runs in dry-run mode and only saves changes locally. Use this flag to push changes to the WCA API.--no-local-cache- Bypasses the local cache and fetches fresh WCIF data from the WCA API. Useful when you need to ensure you're working with the latest/clean data.--verboseor-v- Enables verbose logging, providing additional information about what TypeComp is doing (e.g., which cache file is being used).
Here is an example of how to run a script
bun run scripts/examples/basic.ts --no-local-cacheTypically scripts are put in a competition specific subfolder of the scripts directory.
import { createTypeComp } from '@/lib/api';
const tc = await createTypeComp('CompetitionId');The core API uses a builder pattern, most of which can be chained together:
const round = tc
.round('333-r1') // Event and round ID
.createGroups(4, { // Number of groups
room: 'Ballroom',
from: '2026-01-17T14:15:00',
to: '2026-01-17T15:00:00',
})
.competitors(competingIn('333')) // Filter competitors
.maxGroupSize(25);
// Configure grouping preferences
round.groupBy.sameCountry(4); // Prefer grouping by country
round.groupBy.differentNames(-5); // Penalize same names
// Assign competitors to groups
const result = round.assign();For round grouping, scoring functions are used to find the assignments that maximise the total score across all competitors and groups. Multiple scoring functions are combined by summing their scores together.
You can create custom scoring functions to create additional items to be factored into assignments:
round.groupBy.custom({
getScore(person: Person, group: Group, otherPeople: Person[]): number {
// Positive scores makes it more likely to be assigned
// Negative scores try to prevent it being assigned
return someScore;
}
});Assign stations within groups (e.g., by speed):
round.stations.by(
(person) => person.personalBests?.['333']?.best || Infinity,
'ascending' // Fastest first
);
round.stations.bySpeed('333', 'best', 'ascending'); // essentially the sameAssign staff to groups:
const staffResult = tc
.staff('333-r1')
.judges(18)
.scramblers(4)
.runners(2)
.assign();After making assignments, commit them to the WCA:
await tc.commit();TypeComp provides a rich set of filters for selecting competitors and groups:
competingIn(eventId)- Competitors registered for an eventfromCountry(country)- Competitors from a specific countryhasPB(eventId)- Competitors with a personal bestpbFasterThan(eventId, time)- Competitors faster than a timenewcomer()- First-time competitorsisDelegate(),isOrganizer()- Staff roles
Filters can be combined with and(), or(), and not().
For common scenarios, TypeComp provides preset functions:
quickAssign()- Quick assignment for small competitionsassignRound()- Standard round assignment with balanced groupsassignLaterRound()- Assignment for subsequent rounds based on resultsassignBlindfolded()- Special handling for blindfolded eventsassignWaveStaff()- Staff assignment for parallel wave scheduling
For competitions with multiple events running simultaneously:
// For this just look at the DublinPickNMix2026 code# Type checking
bun run typecheck
# Linting
bun run lint
bun run lint:fix
# Formatting
bun run formatMIT