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 { tournamentEngine } from 'tods-competition-factory';
const { migrateTournamentRecord } = tournamentEngine;

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

A flightProfile extension on a draw definition is not promoted (since 7.10.0): a flight profile belongs to the event, nothing ever wrote one on a draw, and a stray draw-level extension is left as an extension.

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.