npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@enagonathletics/flipturn

v0.4.0

Published

Read and write swim meet files (SDIF, Hy-Tek .hy3/.ev3/.hyv) in TypeScript.

Readme

flipturn

Read and write swim meet files (SDIF, Hy-Tek .hy3/.ev3/.hyv) in TypeScript.

MIT-licensed, zero-runtime-dependency, ESM. Runs on Node >=22, Bun, and browsers. Library code never touches the file system: pass it text or bytes.

Install

npm install @enagonathletics/flipturn

Status

Pre-1.0. The API is unstable and will change without a major version bump (see bump-minor-pre-major in release-please-config.json).

Formats

| Format | Extensions | Read | Write | | ------------------- | -------------- | ---------------------------------------------------- | ---------------------------- | | SDIF v3 | .sd3, .cl2 | Yes: meet, teams, swimmers, entries, results, splits | Yes: entries (standard SDIF) | | Hy-Tek meet results | .hy3 | Yes: entries, results per round, relays, DQ reasons | No | | Hy-Tek event files | .ev3, .hyv | Yes: events, sessions, fees, qualifying times | No | | Zip containers | .zip | Yes: detects and reads each meet file inside | No | | Lenex | .lef, .lxf | No | No |

Every reader returns the same format-neutral Meet model plus the file's raw records and any warnings. Known gaps: HY3 split records (G1) are not read, and Team Unify's "Extended" SD3 variant is not written until a real sample has been compared.

Entry limits and fees

Meet.entryLimits and Meet.fees are meet-wide; MeetEvent.entryFee is per event and IndividualEntry/RelayEntry.entryFee is per entry. A format only gets a field here when a byte position for it was confirmed against a documented field table or a real exported file — see porting policy for the sources.

| Format | entryLimits | Fee types | | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | EV3 | maxTotalEntries, maxIndividualEntries, maxRelayEntries — meet-wide, per swimmer except the total (header fields 19–21, 1-based) | MeetEvent.entryFee per event/relay (field 15), plus meet-wide teamSurcharge, athleteSurcharge, facilitySurcharge (header fields 7–9) | | HYV | None — confirmed absent. Team Manager's 11-field header and ~18-field event row have no unclaimed columns for one; cross-checked against swimparse and hytek-parser's independent HYV readers, neither of which decodes anything beyond the existing per-event entryFee | MeetEvent.entryFee per event/relay only | | HY3 | None — confirmed absent. A results/entries format has no reason to carry them and none of swimparse, swimlib or hytek-parser decode any | IndividualEntry.entryFee (E1, 1-based cols 33–38) and RelayEntry.entryFee (F1, same columns) only — no meet-wide fee field | | SDIF | None — confirmed absent from the spec text itself (no field in B1/B2/D0/D1) | None — confirmed absent, same basis |

Two candidates were investigated and deliberately not modeled:

  • EV3 event-row columns 27–29 (max_entries, max_individual_entries, max_relay_entries per swimlib's field table) sit right after the per-event session columns, which would fit a per-day/session limit — but the one EV3 sample available repeats the same three values on every event regardless of day, which is equally consistent with "an event-level cap" as with "a per-day limit stated redundantly." Absent a real file where they vary by day, this is not asserted as the per-day limit the task asked for.
  • HY3 B2's meetFee (swimlib's claimed 1-based cols 100–106) does not survive a cross-check: swimlib's own B1 date fields, in the same record family, land on garbage when checked against real Meet Manager results exports, while swimparse and hytek-parser — the two libraries whose B-record offsets do reproduce the real dates in those files — neither decode a B2 fee at all. Treated as unconfirmed, not modeled.

Usage

Read any meet file:

import { readSdif } from '@enagonathletics/flipturn/sdif';

const { meet, warnings } = readSdif(bytes); // Uint8Array (Windows-1252) or string
meet.swimmers; // name, birth date, gender, external ids
meet.entries; // seed times and, for results files, one result per round

Read every meet file in a Meet Manager zip:

import { readMeetArchive } from '@enagonathletics/flipturn/zip';

for (const file of await readMeetArchive(zipBytes)) {
	if (file.result) console.log(file.name, file.format, file.result.meet.events.length);
	else if (file.error) console.warn(file.name, file.error);
}

Event files keep the sex code as written (G/B age-group, W/M senior) next to the canonical gender:

import { readEv3 } from '@enagonathletics/flipturn/ev3';

const [event] = readEv3(text).meet.events;
event.sexCode; // 'G'
event.gender; // 'F'

Write an SDIF entries file. canadian mode makes the USA Swimming ID optional:

import { encodeWindows1252 } from '@enagonathletics/flipturn/core';
import { writeSdifEntries } from '@enagonathletics/flipturn/sdif';

const { text, warnings } = writeSdifEntries(meet, {
	mode: 'canadian',
	contact: { name: 'Registrar, Lee', phone: '2505550199' }
});
const bytes = encodeWindows1252(text); // 160-byte records, CRLF line endings

Meet.altitude (whole feet, 0-9999) goes in B1. Pass profile: 'team-unify' to write the layout of Team Unify's Standard SD3 export instead of the spec's where they differ: event numbers right-justified in columns 73-75 with an uppercase suffix letter in 76, TIME codes such as NT right-justified, no C2 for a team without a coach, an A0 description (description, default Meet Entries), B1 altitude 0 when unset, the team code's LSC in D0 columns 4-11, D3 participation flags F, Swimmer.middleName and N in column 147, and Z0 batch 1 with zero membership counts.

Swim times are integers in hundredths:

import { formatSwimTime, parseSwimTime } from '@enagonathletics/flipturn/core';

parseSwimTime('1:02.34'); // { kind: 'time', hundredths: 6234 }
formatSwimTime({ kind: 'nt' }); // 'NT'

Docs

License

MIT © Enagon Athletics