Skip to main content

migrateTournamentRecord

One-shot helper that walks an entire tournamentRecord and promotes every legacy extensions[] envelope and schedule timeItems[] entry the CODES initiative has promoted to first-class attributes. The output is a record shaped for the v5.0.0 default (schemaWriteMode: 'native').

import { migrateTournamentRecord } from 'tods-competition-factory';

const { tournamentRecord: legacy } = await loadLegacyRecord();
const { promoted, totalPromoted } = migrateTournamentRecord({ tournamentRecord: legacy });
// legacy is mutated in place — same reference, post-migration shape
// promoted: { events: 4, matchUpScheduleTimeItems: 124, ... }

When to call it

You only need this once per record, at the seam where a legacy v4 (or older) record enters a v5 codebase. After the call the record is native-shaped and reads through firstClassOrExtension / dedicated query methods will find values in their new home.

Common seams:

  • Import / ingestion paths — a legacy export, a federation feed, a backup restore. Run migration before handing the record to the engine.
  • Storage upgrade scripts — walk the historical record store once, write back the migrated shape.
  • Mode transitions — if a server flips schemaWriteMode from legacy to native, migrate previously-saved records before the new mode reads them.

What it promotes

The walker visits the tournament record, every event, every draw definition, every structure (with its positionAssignments), every matchUp, every venue, every court. At each element it lifts:

Source shapeNew homePhase
positionAssignment.extensions[{name: 'tally'}].valuepositionAssignment.tally1
positionAssignment.extensions[{name: 'subOrder'}].valuepositionAssignment.subOrder1
matchUp.timeItems[{itemType: SCHEDULED_DATE}].itemValuematchUp.schedule.scheduledDate2
matchUp.timeItems[{itemType: SCHEDULED_TIME}].itemValuematchUp.schedule.scheduledTime2
matchUp.timeItems[{itemType: ASSIGN_COURT}].itemValuematchUp.schedule.courtId2
matchUp.timeItems[{itemType: ASSIGN_VENUE}].itemValuematchUp.schedule.venueId2
matchUp.timeItems[{itemType: COURT_ORDER}].itemValuematchUp.schedule.courtOrder2
matchUp.timeItems[{itemType: COURT_ANNOTATION}].itemValuematchUp.schedule.courtAnnotation2
matchUp.timeItems[{itemType: ALLOCATE_COURTS}].itemValuematchUp.schedule.allocatedCourts2
matchUp.timeItems[{itemType: TIME_MODIFIERS}].itemValuematchUp.schedule.timeModifiers2
matchUp.timeItems[{itemType: HOME_PARTICIPANT_ID}].itemValuematchUp.schedule.homeParticipantId2
matchUp.timeItems[{itemType: ASSIGN_OFFICIAL}].itemValuematchUp.schedule.official2
event.extensions[{name: 'flightProfile'}].valueevent.flightProfile3
drawDefinition.extensions[{name: 'lineUps'}].valuedrawDefinition.lineUps3
8 promoted flat-scalar extensions (DRAFT_STATE, DISABLE_AUTO_CALC, etc.)matching first-class attribute4
tournamentRecord.extensions[{name: 'SCHEDULING_PROFILE'}].valuetournamentRecord.scheduling.profile5
tournamentRecord.extensions[{name: 'SCHEDULE_LIMITS'}].valuetournamentRecord.scheduling.dailyLimits5
tournamentRecord.extensions[{name: 'SCHEDULE_TIMING'}].valuetournamentRecord.scheduling.timing5
tournamentRecord.extensions[{name: 'LINKED_TOURNAMENTS'}]tournamentRecord.linkedTournamentIds (shape-translated)7

For lifecycle timeItems (START_TIME / STOP_TIME / RESUME_TIME / END_TIME) — these are not promoted; they remain an ordered history that matchUpDuration() walks.

clearLegacy flag

type MigrateTournamentRecordArgs = {
tournamentRecord: Tournament;
clearLegacy?: boolean; // default: true
};
ValueBehavior
true (default)After promoting an entry, the legacy extension / timeItem is removed. Output is purely native-shaped.
falsePromoted values are copied to their new home; legacy entries are kept. Useful when you want to migrate readers but defer dropping the legacy envelope (e.g., to keep an in-flight schemaWriteMode: 'dual' deployment safe to roll back).

Existing first-class values are never overwritten — the helper only fills element[attribute] === undefined. Running migration twice is a no-op on the second pass.

Result shape

type MigrationResult = {
promoted: {
tournament: number;
events: number;
entries: number;
drawDefinitions: number;
structures: number;
positionAssignments: number;
matchUps: number;
matchUpScheduleTimeItems: number;
venues: number;
courts: number;
};
totalPromoted: number; // sum across all categories
success: true;
// ResultType base — error?: ErrorType if input was invalid
};

The promoted counts are diagnostic — they tell you "the record actually had data in this shape". A migration that returns totalPromoted: 0 is a record that was already native (or had nothing to lift).

Errors

error.codeWhen
MISSING_TOURNAMENT_RECORDThe tournamentRecord argument was missing or wasn't an object

The walker is defensive: missing intermediate collections (events, extensions, timeItems) are treated as empty rather than throwing. Records with partial structure migrate as far as they can.

See also

  • The 4.x to 5.0.0 migration guide — full context on the CODES schema initiative and the typed engine default.
  • schemaWriteMode — the engine flag this helper coordinates with.
  • getTally — example of a mode-agnostic reader that works against both pre- and post-migration shapes.