TypeComp is a TypeScript library for managing World Cube Association (WCA) competition scheduling and group assignments. It provides a fluent builder API for creating groups, assigning competitors, and managing staff roles.
Inspired by CompScript. This project was largely implemented by AI Agents as it might only be used for a single competition (DublinPickNMix2026), so the quality of the library cannot be guarenteed
Requires Bun v1.0.0 or later.
bun installTypeComp uses OAuth to interact with the WCA API. You'll need to set up credentials before fetching or pushing competition data.
- Go to WCA OAuth Applications
- Create a new application with redirect URI:
http://localhost:3030/auth/oauth_response - Note your Client ID and Client Secret
Create a .env file in the project root:
WCA_CLIENT=your_client_id
WCA_SECRET=your_client_secretOptional settings:
WCA_API- API base URL (default:https://www.worldcubeassociation.org)PORT- OAuth callback port (default:3030)
bun run loginThis opens the WCA OAuth page. After authorizing, tokens are saved to .typecomp/.
import { createTypeComp, competingIn, canScramble } from 'typecomp';
const tc = await createTypeComp('YourCompetition2026');
// Create groups for a round
tc.round('333-r1')
.createGroups(4, {
room: 'Main Hall',
from: '2026-01-17T10:00:00',
to: '2026-01-17T11:00:00',
})
.competitors(competingIn('333'))
.maxGroupSize(20)
.groupBy.sameCountry(4)
.differentNames(-5)
.done()
.stations.bySpeed('333', 'average')
.done()
.assign();
// Assign staff
tc.staff('333-r1')
.from(competingIn('333'))
.judges(12)
.scramblers(4, canScramble('333'))
.runners(2)
.overwrite(true)
.assign();
// Save changes
await tc.commit();When running scripts, TypeComp supports these flags:
| Flag | Description |
|---|---|
--commit, -c |
Push changes to WCA (default: dry-run, saves locally only) |
--no-local-cache |
Fetch fresh WCIF from WCA API instead of using local cache |
--verbose, -v |
Enable verbose logging |
--clean |
Remove all assignments, groups (childActivities), and custom extensions from WCIF |
# Dry run (saves locally only)
bun run scripts/examples/basic.ts
# Push changes to WCA
bun run scripts/examples/basic.ts --commit
# Fetch fresh data
bun run scripts/examples/basic.ts --no-local-cacheconst tc = await createTypeComp('CompetitionId');
// Access competition data
tc.competition // Full WCIF competition object
tc.id // Competition ID
tc.dryRun // Whether running in dry-run mode
// Query people
tc.persons.registered() // All registered competitors
tc.persons.all() // All persons (including non-competing)
tc.persons.filter(fn) // Filter by predicate
tc.persons.byWcaId(id) // Find by WCA ID
tc.persons.count() // Count peopleCreate groups and assign competitors to a round:
tc.round('333-r1')
.createGroups(count, options) // Create N groups
.competitors(filter) // Who to assign
.maxGroupSize(size) // Cap per group
.groupBy // Configure grouping preferences
.stations // Configure station assignment
.assign() // Execute assignment.createGroups(4, {
room: 'Main Hall', // Room name (optional if round exists in schedule)
from: '2026-01-17T10:00:00', // Start time (optional)
to: '2026-01-17T11:00:00', // End time (optional)
})Scoring functions determine how competitors are grouped. Positive scores encourage assignment, negative scores discourage it.
.groupBy
.sameCountry(score, limit?) // Group people from same country
.differentNames(penalty) // Penalize same first names
.matching(valueFn, score) // Group by matching values
.when(personFilter, groupFilter, score) // Conditional scoring
.custom(scorer) // Custom scorer function
.done()Assign station numbers within groups:
.stations
.bySpeed(eventId, 'single' | 'average', 'ascending' | 'descending')
.by(scoreFn, order) // Custom ordering
.none() // Disable stations
.done()Assign staff to groups:
tc.staff('333-r1')
.from(filter) // Pool of eligible staff
.groups(groupFilter) // Which groups to staff
.judges(count, options?) // Number of judges
.scramblers(count, eligibility?) // Scramblers (with optional filter)
.runners(count) // Runners
.dataEntry(count) // Data entry
.job(name, count, options?) // Custom job type
.preferFastScramblers() // Prefer faster scramblers
.scorer(staffScorer) // Custom scoring
.overwrite(replace?) // Replace existing assignments
.avoidConflicts(avoid?) // Skip if person is competing
.assign()Filter functions for selecting people and groups:
// Competition-based
competingIn('333') // Registered for event
competingInAny('333', '222') // Registered for any
competingInAll('333', 'pyram') // Registered for all
notCompetingIn('333') // Not registered for event
// Personal bests
hasPB('333', 'average') // Has a PB
pbFasterThan('333', 1000) // PB under 10 seconds (in centiseconds)
pbSlowerThan('333', 3000) // PB over 30 seconds
canScramble('333') // Can scramble this event
// Demographics
fromCountry('IE') // From specific country
fromCountries('IE', 'GB') // From any of these countries
newcomer // No WCA ID
hasWcaId // Has WCA ID
gender('f') // Gender filter
// Roles
isDelegate // Is a delegate
isOrganizer // Is an organizer
hasRole('staff-judge') // Has specific role
// Combining filters
and(filter1, filter2) // Both must match
or(filter1, filter2) // Either must match
not(filter) // Invert filterCreate custom scoring functions for group assignment:
import type { Scorer, Person, Group } from 'typecomp';
const myScorer: Scorer = {
getScore(person: Person, group: Group, otherPeople: Person[]): number {
// Return positive to encourage assignment
// Return negative to discourage
// Return 0 for neutral
return someCalculation;
}
};
tc.round('333-r1')
.groupBy.custom(myScorer)
// ...// Save locally only (always happens)
await tc.save();
// Save locally and push to WCA if --commit flag was used
await tc.commit();See the scripts/examples/ directory:
basic.ts- Simple group and staff assignmentcustom-scorers.ts- Custom scoring functionswave-optimization.ts- Parallel event schedulingavailability-assignment.ts- Handling availability constraints
Run an example:
bun run scripts/examples/basic.ts# Type checking
bun run typecheck
# Linting
bun run lint
bun run lint:fix
# Formatting
bun run format
# Run tests
bun testtypecomp/
├── engine/ # Core WCIF loading/saving and authentication
├── lib/
│ ├── api/ # Main API (TypeComp, builders, filters, scorers)
│ ├── functions/ # Helper functions for WCIF manipulation
│ ├── solvers/ # Linear programming solvers for optimization
│ ├── types/ # TypeScript types
│ └── utils/ # Utilities
└── scripts/ # Example and competition-specific scripts
MIT