EXFA Format is the versioned, engine-independent file format for EVE fitting data. It stores a fit's calculable content together with the references and user-facing information needed by the web app, desktop app, and future clients. It also turns a saved fit into the stateless FitRequest accepted by EXFA Engine.
The TypeScript package is @exfa/format (ES modules); the Rust crate is exfa-format. Both implement the same JSON wire shapes and consume shared fixtures. The TypeScript types are normative. Object field names are snake_case, IDs are opaque strings, and EVE items are identified by numeric type_id; display names are never authoritative.
- Fit document —
exfa/fit@1. One saved fit.fitis the calculable ship and its fitted items.refspoints to library characters, damage patterns, target profiles, and scenarios.linkspoints to booster and projected fits. Alternatives, named branches, bounded history, and app-privateuidata travel with the document. - Library —
exfa/library@1. A collection of fit documents plus shared characters, damage patterns, target profiles, scenarios, fleets, and explicit folders. References use IDs so records can be renamed without changing their identity. - Directory export.
library.exfa.jsonis the library index; each fit is a separate<folder>/<safe name>.<id>.exfa.jsondocument. The folder is represented by its directory and is omitted from the document file.toFilesandfromFilessupport the same layout for folder export, zip export, and desktop storage.
For example, a library with a fit in PvP/Frigates is stored as:
library.exfa.json
PvP/
Frigates/
Rifter.fit-rifter.exfa.json
Scenarios/
Command T3D.command-fit.exfa.json
schema/ JSON Schema draft 2020-12
common.schema.json
fit-document.schema.json
library.schema.json
library-index.schema.json
scenario.schema.json
ts/ @exfa/format
rust/ exfa-format
fixtures/ shared TypeScript and Rust vectors
engine/ vendored EXFA Engine v0.2.0 request schema
{
"format": "exfa/fit@1",
"id": "fit-rifter",
"name": "Rifter",
"fit": {
"ship": { "type_id": 587, "mode_type_id": null },
"modules": [],
"drones": [],
"fighters": [],
"implants": [],
"boosters": [],
"cargo": [],
"projected": [],
"fleet_buffs": [],
"environment": { "effect_type_ids": [], "system_security": null },
"options": { "factor_reload": false, "spool": 1, "rah": "adapt" }
},
"refs": {
"character_id": "all5",
"damage_pattern_id": "uniform",
"target_profile_id": "none",
"scenario_ids": []
},
"links": { "booster_fit_ids": [], "projected_fits": [] },
"alternatives": [],
"branches": [],
"history": []
}resolve(library, fitId, { branch }) produces the Engine contract 1.5 request for that fit. It follows character/profile/scenario references, resolves depth-one booster and projected fits, and applies a named branch without changing the library. Missing characters fall back to all5; dangling optional profile, scenario, booster, or projected-fit references are omitted. An unknown root fit, selected branch, or explicit scenario target is reported as a FormatError. Nested fits do not recursively include their own projected fits or scenarios.
import { fromFiles, resolve } from '@exfa/format';
const library = fromFiles(importedFiles);
const request = resolve(library, 'fit-rifter', { branch: 'budget' });
// Send request to the EXFA Engine v0.2.0 RPC/CLI.Scenario requests use the Engine's scenarios[] contract, including the attacker-speed and angle parameters. scenarioRequest(library, scenario) is also available for ad-hoc scenarios that are not saved in the library.
The package also exports applyBranch, captureBranch, addAlternative, removeAlternative, and branchDiverged; recordHistory and restoreHistory; migrate and migrateFitDocument; and the toFiles/fromFiles layout helpers. The Rust crate exposes typed serde models, resolve/resolve_with_options, migrate, and read_directory/write_directory.
- EXFA App stores
FitDocumentobjects and shared records as aLibrary;migrateimports its legacy library and backup JSON. The App callsresolvebefore calculating with the Engine. - EXFA Engine consumes the resolved
FitRequestdefined by contract 1.5. The format itself does not call the Engine or depend on an SDE. - Desktop and file-based clients use
toFiles/fromFilesfor directory or archive export and import. The same stable paths are suitable for syncing and version control. - Rust clients use the
exfa-formatcrate for serde types, resolution, and directory read/write.
The vendored request schema is the exact EXFA-Engine v0.2.0 schema. App toRequest emits explicit nulls for some optional fields and uses hisec/wspace; that schema rejects those nulls and expects highsec/wormhole. resolve preserves the App request shape rather than silently rewriting it. The validation suite checks raw requests against a narrowly widened compatibility schema and checks a normalized view against the unmodified Engine schema. See fixtures/engine/README.md for the affected fields and the precise deviation.
This is a schema mismatch only: this package does not alter the vendored contract or infer values for absent App fields. Consumers that require strict JSON Schema validation against the unmodified file should normalize the documented nulls and security aliases before validating.
import { fromFiles, resolve, toFiles } from '@exfa/format';
const library = fromFiles(importedFiles);
const engineRequest = resolve(library, 'fit-rifter');
const exportFiles = toFiles(library);use exfa_format::{read_directory, resolve, write_directory};
let library = read_directory("./fits")?;
let request = resolve(&library, "fit-rifter")?;
write_directory("./fits-export", &library)?;For an App git dependency, the repository-root npm manifest delegates its prepare step to the TypeScript package and exposes the generated declarations and ESM entry point:
{
"dependencies": {
"@exfa/format": "github:EX-CT/EXFA-Format#v1.0.0"
}
}The TypeScript source package can also be built and tested directly:
cd ts
npm ci
npm test
npm run build
npm run schema-validateThe Rust crate is standalone:
cd rust
cargo testexfa/fit@1, exfa/library@1, and exfa/library-index@1 identify wire-format versions independently of package and crate version 1.0.0. Patch releases preserve the v1 data model. A breaking persisted-shape change creates a new @2 format and an explicit migration path; a reader must reject an unknown future exfa/library@N with UNSUPPORTED_VERSION rather than silently reinterpret it.
migrate accepts v1 libraries, legacy EXFA web libraries, and eve-fit-web-library backup wrappers. Missing v1 maps/arrays are normalized to empty collections. Legacy fits are converted to fit documents; unrecognized legacy fit fields are preserved under doc.ui.legacy. Unknown fields in current documents and library records are retained during read/write. History snapshots contain prior fit cores only, are grouped within ten minutes, and retain at most the newest 50 entries.
Safe file names replace /\\:*?"<>| and control characters with _, trim whitespace, and are capped at 80 characters; empty names become fit. Folder segments follow the same rule. IDs remain opaque in data and are percent-encoded only when a file name needs path-safe escaping.
The library is distributed under LGPL-3.0-or-later. LICENSE contains the LGPL terms and LICENSE.GPL-3.0 contains the incorporated GPL terms.