fit-file-parser 5.2.1 → 6.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -191
- package/PROFILE.md +43 -0
- package/README.md +126 -82
- package/dist/binary.d.ts +2 -0
- package/dist/binary.js +23 -6
- package/dist/cjs/binary.d.ts +2 -0
- package/dist/cjs/binary.js +23 -6
- package/dist/cjs/fit-parser.d.ts +5 -0
- package/dist/cjs/fit-parser.js +42 -4
- package/dist/cjs/fit.d.ts +0 -6
- package/dist/cjs/fit.js +4 -114
- package/dist/cjs/fit_types.d.ts +2 -1
- package/dist/cjs/profile-lookup.d.ts +19 -0
- package/dist/cjs/profile-lookup.js +150 -0
- package/dist/cjs/profile.d.ts +9 -0
- package/dist/{garmin_profile.generated.js → cjs/profile.js} +287 -8
- package/dist/cjs/raw-message-reader.d.ts +60 -0
- package/dist/cjs/raw-message-reader.js +377 -0
- package/dist/cjs/type_generator.js +1 -0
- package/dist/fit-parser.d.ts +5 -0
- package/dist/fit-parser.js +27 -3
- package/dist/fit.d.ts +0 -6
- package/dist/fit.js +3 -113
- package/dist/fit_types.d.ts +2 -1
- package/dist/profile-lookup.d.ts +19 -0
- package/dist/profile-lookup.js +140 -0
- package/dist/profile.d.ts +9 -0
- package/dist/{cjs/garmin_profile.generated.js → profile.js} +284 -11
- package/dist/raw-message-reader.d.ts +60 -0
- package/dist/raw-message-reader.js +368 -0
- package/dist/type_generator.js +1 -0
- package/package.json +38 -9
- package/dist/cjs/garmin_profile.generated.d.ts +0 -9
- package/dist/garmin_profile.generated.d.ts +0 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,205 +1,49 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
## 5.2.1
|
|
6
|
-
|
|
7
|
-
### Fixed
|
|
8
|
-
|
|
9
|
-
- Preserve unusual vendor field definitions in lossless raw-message output so
|
|
10
|
-
domain consumers can validate only the messages they understand.
|
|
11
|
-
- Add corpus checks for raw-message-only and combined raw/decoded parsing.
|
|
12
|
-
|
|
13
|
-
## 5.2.0
|
|
14
|
-
|
|
15
|
-
### Added
|
|
16
|
-
|
|
17
|
-
- Add opt-in, message-filterable `raw_messages` output with exact native and
|
|
18
|
-
developer-field bytes, wire base types, endianness, message occurrence, and
|
|
19
|
-
reconstructed compressed timestamps. Existing decoded output remains
|
|
20
|
-
unchanged when the option is disabled.
|
|
21
|
-
- Add `rawMessagesOnly` for bounded metadata consumers that do not need decoded
|
|
22
|
-
activity collections alongside retained messages.
|
|
23
|
-
|
|
24
|
-
### Fixed
|
|
25
|
-
|
|
26
|
-
- Decode compressed timestamp records without consuming bytes for the omitted
|
|
27
|
-
timestamp field.
|
|
28
|
-
|
|
29
|
-
## 5.1.0
|
|
3
|
+
## 6.1.1 - 2026-09-21
|
|
30
4
|
|
|
31
5
|
### Added
|
|
32
6
|
|
|
33
|
-
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
7
|
+
- Export lookup helpers for FIT manufacturer, Garmin product, sport, and
|
|
8
|
+
sub-sport identifiers from the package root.
|
|
9
|
+
- Export the same helpers from the lightweight `fit-file-parser/profile`
|
|
10
|
+
entry point for lookup-only consumers.
|
|
11
|
+
- Add reverse sport, sub-sport, and course-point identifier lookups.
|
|
12
|
+
- Add lightweight `fit-file-parser/raw` and `fit-file-parser/encoder` entry
|
|
13
|
+
points for profile-independent message scanning and encoding.
|
|
14
|
+
- Add a strict raw-message reader with CRC, definition, developer-field, and
|
|
15
|
+
compressed-timestamp validation.
|
|
16
|
+
- Export an opt-in Garmin product display-name helper without changing parsed
|
|
17
|
+
`product` or `product_name` fields.
|
|
18
|
+
- Decode mountain enduro and mountain downhill sub-sport identifiers.
|
|
41
19
|
|
|
42
|
-
|
|
43
|
-
`metabolic_calories` rather than the old duplicate `resting_calories` label.
|
|
44
|
-
- Clarify that the removed guessed `recovery_advisor` label was standard
|
|
45
|
-
session field 140, which the SDK defines as `avg_depth` in meters.
|
|
20
|
+
### Compatibility
|
|
46
21
|
|
|
47
|
-
|
|
22
|
+
- Existing parsed output remains unchanged except that sub-sport IDs 153 and
|
|
23
|
+
154 now resolve to `mountain_enduro` and `mountain_downhill`.
|
|
24
|
+
- Existing Garmin product display names ported from SportsLib are preserved.
|
|
48
25
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
- Run CI and version-driven npm publishing from the repository's `main`
|
|
52
|
-
branch, with a guarded manual recovery path for a missed release.
|
|
53
|
-
|
|
54
|
-
## 5.0.0
|
|
26
|
+
## 6.0.2 - 2026-09-21
|
|
55
27
|
|
|
56
28
|
### Changed
|
|
57
29
|
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
- Restrict non-SDK support to a collision-checked allowlist of Garmin fields
|
|
63
|
-
and private messages observed in the external FIT corpus.
|
|
64
|
-
- Emit only parsed FIT values: Garmin product names are no longer inferred,
|
|
65
|
-
and record elapsed/timer values are generated only when
|
|
66
|
-
`elapsedRecordField: true` is requested.
|
|
67
|
-
|
|
68
|
-
### Breaking
|
|
69
|
-
|
|
70
|
-
- Remove compatibility aliases such as `resting_calories`,
|
|
71
|
-
`recovery_advisor`, `repeat_dive_time`, and deprecated time-in-zone names.
|
|
72
|
-
- Remove guessed stress fields, handwritten enum/type additions, placeholder
|
|
73
|
-
zero-mask names, and the misspelled `hip_swing_excercise_name` type.
|
|
74
|
-
- Use the pinned SDK's `cadence_zone_high_bondary` spelling and generated
|
|
75
|
-
alphanumeric field/type spellings instead of manual corrections.
|
|
76
|
-
- Correct generated declarations to expose runtime `Date` values, numeric FIT
|
|
77
|
-
`bool` fields and unknown enum IDs, decoded mask objects, nullable array
|
|
78
|
-
entries, and optional profile fields.
|
|
79
|
-
- Keep undated cascade rows in file order and avoid duplicating them when a lap
|
|
80
|
-
or session boundary timestamp is absent.
|
|
81
|
-
|
|
82
|
-
## 4.1.1
|
|
83
|
-
|
|
84
|
-
### Fixed
|
|
85
|
-
|
|
86
|
-
- Apply Garmin FIT profile scale and offset to primitive numeric fields, while
|
|
87
|
-
retaining semicircle conversion only for fields declared in semicircles.
|
|
88
|
-
- Apply FIT developer-field offsets with the parser's equivalent signed form,
|
|
89
|
-
including scaled signed developer values.
|
|
90
|
-
- Decode native diving ascent-rate fields as meters per second instead of
|
|
91
|
-
geographic coordinates.
|
|
92
|
-
- Decode native dive depth and bottom-time fields with their Garmin FIT SDK
|
|
93
|
-
scale instead of exposing their encoded integer representation.
|
|
94
|
-
- Keep the pinned Garmin SDK authoritative for standard field types, arrays,
|
|
95
|
-
scales, offsets, and units while retaining compatible public field names.
|
|
96
|
-
|
|
97
|
-
## 4.1.0
|
|
30
|
+
- Consolidate the complete message, field, wire-metadata, type, enum, and
|
|
31
|
+
product contract into one static maintained profile.
|
|
32
|
+
- Maintain the FIT interoperability profile directly as static project source.
|
|
33
|
+
- Preserve decoded output and the public TypeScript surface.
|
|
98
34
|
|
|
99
35
|
### Added
|
|
100
36
|
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
- Reconstruct compressed timestamps and keep timestamp state isolated between
|
|
115
|
-
parser instances.
|
|
116
|
-
- Accept omitted header CRCs, validate file CRCs across the complete FIT header
|
|
117
|
-
and data section, and report strict header and file CRC failures explicitly.
|
|
118
|
-
- Reject structurally unsafe FIT inputs consistently in callback and Promise
|
|
119
|
-
APIs while retaining force-mode recovery for CRC corruption.
|
|
120
|
-
- Correct Garmin profile mappings for OHR settings, monitoring HR, sleep,
|
|
121
|
-
time-in-zone, altitude offsets, and lap/segment flow and grit summaries.
|
|
122
|
-
- Correct Celsius-to-Kelvin conversion, add `celsius` as the canonical
|
|
123
|
-
temperature unit, and retain `°C` as a supported alias.
|
|
124
|
-
|
|
125
|
-
### Compatibility and documentation
|
|
126
|
-
|
|
127
|
-
- Preserve compatible legacy field names, scales, value shapes, parser
|
|
128
|
-
signatures, output modes, and date behavior while adding canonical profile
|
|
129
|
-
names.
|
|
130
|
-
- Add regression coverage for temperature, pressure, validation, compressed
|
|
131
|
-
timestamps, generated profile messages, repeated messages, and MTB
|
|
132
|
-
flow/grit data.
|
|
133
|
-
- Refresh the README with current runtime, API, units, output modes, developer
|
|
134
|
-
fields, encoder behavior, and repository commands.
|
|
135
|
-
|
|
136
|
-
## 4.0.2
|
|
137
|
-
|
|
138
|
-
- Preserve record alignment when developer-field descriptions are missing or
|
|
139
|
-
appear after their message definitions.
|
|
140
|
-
- Decode subsequent developer-field values once their descriptions become
|
|
141
|
-
available, in both strict and force modes.
|
|
142
|
-
|
|
143
|
-
## 4.0.0
|
|
144
|
-
|
|
145
|
-
### FIT decoder performance
|
|
146
|
-
|
|
147
|
-
- Parse `ArrayBuffer` inputs and exact Node.js `Buffer` views directly, avoiding a full copy of the source file.
|
|
148
|
-
- Reuse one parse-local `DataView` instead of allocating temporary views for each supported endian field.
|
|
149
|
-
- Cache standard field metadata and enum/mask lookups on reusable message definitions.
|
|
150
|
-
- Reuse raw field storage for records that share a local message definition.
|
|
151
|
-
- Generate elapsed and timer fields once per record instead of once per decoded field.
|
|
152
|
-
- Skip header and file CRC scans in `force: true` mode, where CRC mismatches are intentionally ignored; strict mode continues to validate both CRCs.
|
|
153
|
-
- Preserve legacy malformed-field zero-padding, field-boundary, developer-field, invalid-value, and offset-buffer behavior.
|
|
154
|
-
|
|
155
|
-
### Measured benefit
|
|
156
|
-
|
|
157
|
-
- Suunto 93-hour / 7.5 MB FIT decoding: 1.057 s to 0.375 s, a 64.5% reduction.
|
|
158
|
-
- Garmin 110-hour / 28.6 MB FIT decoding: 3.643 s to 1.241 s, a 65.9% reduction.
|
|
159
|
-
- Input-related array-buffer memory is approximately halved by removing the full source copy:
|
|
160
|
-
- Suunto: 15.0 MB to 7.5 MB.
|
|
161
|
-
- Garmin: 57.2 MB to 28.6 MB.
|
|
162
|
-
|
|
163
|
-
There are no intentional parsed-output or public API changes in this release. Output parity was verified across 162 checked-in fixture/mode combinations, 8,320 generated malformed endian-definition cases, and both private long-duration benchmark files.
|
|
164
|
-
|
|
165
|
-
## 3.1.0
|
|
166
|
-
|
|
167
|
-
- Add the public `FitEncoder` API for writing FIT headers, definitions, data messages, and CRCs.
|
|
168
|
-
- Preserve the `course.sub_sport` field while parsing FIT course files.
|
|
169
|
-
|
|
170
|
-
<a name="1.5.4"></a>
|
|
171
|
-
|
|
172
|
-
## [1.5.4](https://github.com/jimmykane/fit-parser/compare/v1.0.0...v1.5.3) (2019-03-01)
|
|
173
|
-
|
|
174
|
-
- **Features**: HRV, Developer fields, devices and much more
|
|
175
|
-
- **Miscellaneous**: Fix most of the issues parsing fit files
|
|
176
|
-
|
|
177
|
-
<a name="1.0.1"></a>
|
|
178
|
-
|
|
179
|
-
## [1.0.1](https://github.com/pierremtb/easy-fit/compare/v1.0.0...v1.0.1) (2018-09-18)
|
|
180
|
-
|
|
181
|
-
### 😭 Unclassified (not [following convention](https://github.com/sportheroes/bk-conventional-changelog#types-of-commits))
|
|
182
|
-
|
|
183
|
-
- **Miscellaneous**: fix: Applied to src/fit offset adjustments from commit 9ed802 ([70b3eb6](https://github.com/pierremtb/easy-fit/commit/70b3eb6) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
184
|
-
|
|
185
|
-
<a name="1.0.0"></a>
|
|
186
|
-
|
|
187
|
-
# [1.0.0](https://github.com/pierremtb/easy-fit/compare/0.0.7...1.0.0) (2018-09-17)
|
|
188
|
-
|
|
189
|
-
### 😭 Unclassified (not [following convention](https://github.com/sportheroes/bk-conventional-changelog#types-of-commits))
|
|
190
|
-
|
|
191
|
-
- **Miscellaneous**: Fix readme typo ([2282c06](https://github.com/pierremtb/easy-fit/commit/2282c06)))
|
|
192
|
-
- **Miscellaneous**: chore: preparing to release fork internally ([3eef5f7](https://github.com/pierremtb/easy-fit/commit/3eef5f7) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
193
|
-
- **Miscellaneous**: fix: Typo leading to uint16 to never be invalidated ([9ed802c](https://github.com/pierremtb/easy-fit/commit/9ed802c) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
194
|
-
- **Miscellaneous**: Merge remote-tracking branch 'jenglert/master' ([819d78e](https://github.com/pierremtb/easy-fit/commit/819d78e)))
|
|
195
|
-
- **Miscellaneous**: fix missing buffer dependency and compile dst ([a4b237a](https://github.com/pierremtb/easy-fit/commit/a4b237a)))
|
|
196
|
-
- **Miscellaneous**: Merge remote-tracking branch 'FrostDigital/master' ([6fa9258](https://github.com/pierremtb/easy-fit/commit/6fa9258)))
|
|
197
|
-
- **Miscellaneous**: Makes parsing of fit files with developer defined fields possible (#1) ([9aea666](https://github.com/pierremtb/easy-fit/commit/9aea666))), closes [#1](https://github.com/pierremtb/easy-fit/issues/1)
|
|
198
|
-
- **Miscellaneous**: fix: Adjusted offset for all altitude-related fields ([1ff0fa4](https://github.com/pierremtb/easy-fit/commit/1ff0fa4) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
199
|
-
- **Miscellaneous**: chore(devices): add forerunner 735 xt ([aacaa04](https://github.com/pierremtb/easy-fit/commit/aacaa04)))
|
|
200
|
-
- **Miscellaneous**: Compile src ([ee4aa00](https://github.com/pierremtb/easy-fit/commit/ee4aa00)))
|
|
201
|
-
- **Miscellaneous**: Add support for other lap types ([dcf42a1](https://github.com/pierremtb/easy-fit/commit/dcf42a1)))
|
|
202
|
-
- **Miscellaneous**: Compile src ([50b9b21](https://github.com/pierremtb/easy-fit/commit/50b9b21)))
|
|
203
|
-
- **Miscellaneous**: Merge remote-tracking branch 'pierremtb/master' ([f7f5ff0](https://github.com/pierremtb/easy-fit/commit/f7f5ff0)))
|
|
204
|
-
- **Miscellaneous**: Add support for more manufacturers ([2d122d2](https://github.com/pierremtb/easy-fit/commit/2d122d2)))
|
|
205
|
-
- **Miscellaneous**: Really compiled last changes on binary.js this time, small details on package.json file -> bumped to 0.0.8 ([e5dc98f](https://github.com/pierremtb/easy-fit/commit/e5dc98f)))
|
|
37
|
+
- Add opt-in `unmapped_messages` preservation, including exact bytes and
|
|
38
|
+
definition metadata, without changing default parsed output.
|
|
39
|
+
- Add regression coverage for unknown messages, unknown fields inside known
|
|
40
|
+
messages, the immutable compatibility profile, and default-output stability.
|
|
41
|
+
- Add an optional privacy-safe corpus summary for unmapped message/field IDs,
|
|
42
|
+
wire base types, sizes, occurrences, and file counts.
|
|
43
|
+
- Document the manual update requirements and corpus verification workflow in
|
|
44
|
+
`PROFILE.md`.
|
|
45
|
+
|
|
46
|
+
There are no intentional parsed-output or public API changes in this release.
|
|
47
|
+
Output parity was verified across 162 checked-in fixture/mode combinations,
|
|
48
|
+
8,320 generated malformed endian-definition cases, and both private
|
|
49
|
+
long-duration benchmark files.
|
package/PROFILE.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# FIT Profile Maintenance
|
|
2
|
+
|
|
3
|
+
The parser uses one static interoperability table in `src/profile.ts` for
|
|
4
|
+
normal installs, builds, tests, and releases. It contains message and field
|
|
5
|
+
identifiers, names, wire types, array behavior, scales, offsets, units, enum
|
|
6
|
+
labels, and product identifiers. `npm run profile:check` pins full-table
|
|
7
|
+
fingerprints and the resulting message, field, type, enum-value, and product
|
|
8
|
+
counts so accidental drift fails CI.
|
|
9
|
+
|
|
10
|
+
## Unknown and newer fields
|
|
11
|
+
|
|
12
|
+
A FIT definition contains enough wire metadata to retain a field even when the
|
|
13
|
+
maintained table cannot assign it a semantic name. Pass
|
|
14
|
+
`includeUnmappedMessages: true` to expose unmapped native or unresolved
|
|
15
|
+
developer fields in `unmapped_messages`, including:
|
|
16
|
+
|
|
17
|
+
- global message number and occurrence index;
|
|
18
|
+
- endianness and reconstructed compressed timestamp, when applicable;
|
|
19
|
+
- field-definition number and FIT base-type byte;
|
|
20
|
+
- an exact copy of the field's wire bytes.
|
|
21
|
+
|
|
22
|
+
The opt-in keeps default decoded results unchanged. Consumers that need a
|
|
23
|
+
complete raw representation of selected messages can continue to use
|
|
24
|
+
`includeRawMessages`; `unmapped_messages` stores only the fields the maintained
|
|
25
|
+
table cannot decode.
|
|
26
|
+
|
|
27
|
+
## Updating the table
|
|
28
|
+
|
|
29
|
+
Every mapping change requires focused regression coverage. Do not guess
|
|
30
|
+
adjacent identifiers or add a mapping solely to make `unmapped_messages`
|
|
31
|
+
disappear. Update `src/profile.ts`, the relevant tests, and the public types,
|
|
32
|
+
then verify the complete compatibility contract:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm run codegen
|
|
36
|
+
npm run profile:check
|
|
37
|
+
npm run check
|
|
38
|
+
npm run compatibility:check -- /path/to/reference-package /path/to/fit-corpus
|
|
39
|
+
npm run corpus:check -- ../FIT-test-files --allow-force-recovery
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`src/fit_types.ts` is generated from the maintained table and must not be
|
|
43
|
+
edited manually.
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ including developer-defined data.
|
|
|
15
15
|
- Convert speed, length, temperature, and pressure fields to preferred units.
|
|
16
16
|
- Decode developer fields while preserving record alignment when descriptions
|
|
17
17
|
arrive after their definitions.
|
|
18
|
+
- Optionally preserve every unmapped native or unresolved developer field in
|
|
19
|
+
`unmapped_messages` with its exact wire bytes.
|
|
18
20
|
- Encode profile-agnostic FIT messages with validated field definitions and
|
|
19
21
|
CRCs.
|
|
20
22
|
- Use ESM or CommonJS with bundled TypeScript declarations.
|
|
@@ -29,56 +31,12 @@ including developer-defined data.
|
|
|
29
31
|
npm install fit-file-parser
|
|
30
32
|
```
|
|
31
33
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Update field access, destructuring, persisted schemas, and snapshots to use the
|
|
39
|
-
SDK-backed names. Common migrations include:
|
|
40
|
-
|
|
41
|
-
| 4.x name | 5.0 name |
|
|
42
|
-
| ------------------------------ | ---------------------------- |
|
|
43
|
-
| `speed_1s` | `speed1s` |
|
|
44
|
-
| `start_n_2`, `end_n_2` | `start_n2`, `end_n2` |
|
|
45
|
-
| `o_2_toxicity` | `o2_toxicity` |
|
|
46
|
-
| `avg_spo_2`, `reading_spo_2` | `avg_spo2`, `reading_spo2` |
|
|
47
|
-
| `po_2` | `po2` |
|
|
48
|
-
| `cycle_length_16` | `cycle_length16` |
|
|
49
|
-
| `map_3_sample_mean` | `map3_sample_mean` |
|
|
50
|
-
| `time_256` | `time256` |
|
|
51
|
-
| `last_night_5_min_high` | `last_night5_min_high` |
|
|
52
|
-
| `average_7_day_deviation` | `average7_day_deviation` |
|
|
53
|
-
| `spo_2_data`, `hsa_spo_2_data` | `spo2_data`, `hsa_spo2_data` |
|
|
54
|
-
| `repeat_dive_time` | `repeat_dive_interval` |
|
|
55
|
-
| `cadence_zone_high_boundary` | `cadence_zone_high_bondary` |
|
|
56
|
-
| `HipSwingExcerciseName` | `HipSwingExerciseName` |
|
|
57
|
-
| `resting_calories` | `metabolic_calories` |
|
|
58
|
-
|
|
59
|
-
The same alphanumeric-token rule applies to enum strings, for example
|
|
60
|
-
`camera_orientation_90` becomes `camera_orientation90`, `po_2_warn` becomes
|
|
61
|
-
`po2_warn`, and `power_3s` becomes `power3s`. The generated TypeScript
|
|
62
|
-
declarations are the exhaustive name and value reference for the pinned SDK.
|
|
63
|
-
|
|
64
|
-
Parser 4 exposed standard session field 196 (`metabolic_calories`) a second
|
|
65
|
-
time as `resting_calories`; use the canonical `metabolic_calories` name in 5.0.
|
|
66
|
-
`recovery_advisor` was a guessed label for standard session field 140, whose
|
|
67
|
-
canonical SDK field is `avg_depth` in meters. Other behavior to account for
|
|
68
|
-
during migration:
|
|
69
|
-
|
|
70
|
-
- `product_name` is emitted only when it exists in the FIT input. It is no
|
|
71
|
-
longer inferred from `manufacturer` and `product`.
|
|
72
|
-
- Record `elapsed_time` and `timer_time` require
|
|
73
|
-
`elapsedRecordField: true`.
|
|
74
|
-
- FIT timestamps are typed as `Date`, FIT `bool` values remain numeric, mask
|
|
75
|
-
fields are `{ value, ...flags }` objects, unknown enum values remain numbers,
|
|
76
|
-
and array entries may be `null` when the FIT invalid sentinel is retained.
|
|
77
|
-
- Every profile field is optional because individual FIT message definitions
|
|
78
|
-
determine which fields are present.
|
|
79
|
-
- When upgrading from 4.1.0 or earlier, remove application-side scale or offset
|
|
80
|
-
corrections for parsed numeric values, including dive depth, bottom time,
|
|
81
|
-
ascent rate, and developer fields. The parser now applies the SDK metadata.
|
|
34
|
+
## Profile coverage
|
|
35
|
+
|
|
36
|
+
The static profile contains 126 messages, 1,444 fields, 200 types, and 4,403
|
|
37
|
+
mapped values. Pass `includeUnmappedMessages: true` to retain fields outside
|
|
38
|
+
that maintained surface by number and exact bytes; applications that need all
|
|
39
|
+
bytes for recognized messages can also opt into `raw_messages`.
|
|
82
40
|
|
|
83
41
|
## Quick start
|
|
84
42
|
|
|
@@ -186,6 +144,31 @@ provider-specific semantics. Pass `true` to retain developer fields from every
|
|
|
186
144
|
global message, or an array such as `[18]` to bound collection to specific FIT
|
|
187
145
|
message numbers.
|
|
188
146
|
|
|
147
|
+
### Opt-in preservation of unmapped fields
|
|
148
|
+
|
|
149
|
+
Set `includeUnmappedMessages: true` to return fields that are absent from the
|
|
150
|
+
maintained profile or belong to an unknown global message in
|
|
151
|
+
`unmapped_messages`. Only unmapped fields are retained, so normal known fields
|
|
152
|
+
are not duplicated:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
const data = await new FitParser({ includeUnmappedMessages: true })
|
|
156
|
+
.parseAsync(content)
|
|
157
|
+
|
|
158
|
+
for (const message of data.unmapped_messages ?? []) {
|
|
159
|
+
console.log({
|
|
160
|
+
globalMessageNumber: message.global_message_number,
|
|
161
|
+
messageIndex: message.message_index,
|
|
162
|
+
fields: message.fields,
|
|
163
|
+
developerFields: message.developer_fields,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Each entry uses the same wire-level field representation as `raw_messages`.
|
|
169
|
+
This opt-in ensures a newer FIT field cannot disappear merely because it
|
|
170
|
+
does not yet have a reviewed semantic mapping.
|
|
171
|
+
|
|
189
172
|
### Lossless selected messages
|
|
190
173
|
|
|
191
174
|
Set `includeRawMessages` when a consumer needs native FIT numeric codes rather
|
|
@@ -256,16 +239,14 @@ unchanged.
|
|
|
256
239
|
|
|
257
240
|
## Profile-backed output
|
|
258
241
|
|
|
259
|
-
|
|
260
|
-
arrays, and units come from the
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
compatibility aliases for alternate field spellings.
|
|
242
|
+
Recognized message names, field names, enum values, wire types, scales,
|
|
243
|
+
offsets, arrays, and units come from the community-maintained table in
|
|
244
|
+
`src/profile.ts`. Its maintenance and verification contract is documented in
|
|
245
|
+
[`PROFILE.md`](./PROFILE.md).
|
|
264
246
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
unregistered additions.
|
|
247
|
+
Public names use `snake_case` while preserving established alphanumeric tokens
|
|
248
|
+
such as `n2`, `po2`, and `time128`. Unmapped fields remain available by number
|
|
249
|
+
and exact bytes in `unmapped_messages` rather than receiving guessed names.
|
|
269
250
|
|
|
270
251
|
Only values present in the FIT input are emitted. In particular,
|
|
271
252
|
`product_name` is not inferred from `manufacturer` and `product`, and record
|
|
@@ -276,6 +257,57 @@ unknown enum IDs remain numbers, and invalid entries retained inside FIT arrays
|
|
|
276
257
|
are `null`. All profile fields are optional because each FIT message definition
|
|
277
258
|
chooses which fields are present.
|
|
278
259
|
|
|
260
|
+
Applications that need profile lookups independently of parsing can use the
|
|
261
|
+
exported manufacturer, Garmin product, sport, sub-sport, and course-point helpers:
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
import {
|
|
265
|
+
getFitCoursePointId,
|
|
266
|
+
getFitGarminProductDisplayName,
|
|
267
|
+
getFitManufacturerName,
|
|
268
|
+
getFitSportId,
|
|
269
|
+
getFitSportName,
|
|
270
|
+
getFitSubSportId,
|
|
271
|
+
getFitSubSportName,
|
|
272
|
+
} from 'fit-file-parser'
|
|
273
|
+
|
|
274
|
+
getFitManufacturerName(1) // "garmin"
|
|
275
|
+
getFitGarminProductDisplayName(4655) // "Edge MTB"
|
|
276
|
+
getFitSportName(2) // "cycling"
|
|
277
|
+
getFitSubSportName(153) // "mountain_enduro"
|
|
278
|
+
getFitSportId('cycling') // 2
|
|
279
|
+
getFitSubSportId('indoor_cycling') // 6
|
|
280
|
+
getFitCoursePointId('rest_area') // 29
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
These helpers read the same maintained profile used by the decoder. The product
|
|
284
|
+
display helper is opt-in and does not synthesize or overwrite parsed
|
|
285
|
+
`product_name` values.
|
|
286
|
+
|
|
287
|
+
Lookup-only consumers can import the same helpers from
|
|
288
|
+
`fit-file-parser/profile` without loading the parser entry point.
|
|
289
|
+
|
|
290
|
+
### Lightweight raw messages
|
|
291
|
+
|
|
292
|
+
Consumers that need strict FIT framing, CRC validation, compressed timestamps,
|
|
293
|
+
and exact fields without loading the semantic profile can use the lightweight
|
|
294
|
+
raw entry point:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
import { readFitMessages } from 'fit-file-parser/raw'
|
|
298
|
+
|
|
299
|
+
const { messages } = readFitMessages(content, {
|
|
300
|
+
messageNumbers: [18, 26, 72],
|
|
301
|
+
maxInputBytes: 64 * 1024 * 1024,
|
|
302
|
+
})
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The result retains native and developer fields as defensive `Uint8Array`
|
|
306
|
+
copies. It does not apply names, enum formatting, scales, units, or
|
|
307
|
+
provider-specific interpretation. Invalid input throws `FitMessageReaderError`
|
|
308
|
+
with a stable `code` such as `invalid_header`, `invalid_crc`, or
|
|
309
|
+
`invalid_structure`.
|
|
310
|
+
|
|
279
311
|
## Inputs
|
|
280
312
|
|
|
281
313
|
Both parser methods accept:
|
|
@@ -302,7 +334,7 @@ base types, sizes, and values in their raw FIT representation. Applying FIT
|
|
|
302
334
|
scales and offsets is the caller's responsibility.
|
|
303
335
|
|
|
304
336
|
```javascript
|
|
305
|
-
import { FitBaseType, FitEncoder } from 'fit-file-parser'
|
|
337
|
+
import { FitBaseType, FitEncoder } from 'fit-file-parser/encoder'
|
|
306
338
|
|
|
307
339
|
const encoder = new FitEncoder()
|
|
308
340
|
encoder.writeMessage(0, [
|
|
@@ -338,6 +370,9 @@ Scalar 64-bit values use `bigint`. Strings, numeric arrays, and other
|
|
|
338
370
|
variable-length values use exact-size `Uint8Array` values. Invalid field
|
|
339
371
|
definitions or numeric ranges throw before a partial message is written.
|
|
340
372
|
|
|
373
|
+
The encoder remains available from the package root; the `/encoder` entry
|
|
374
|
+
point avoids loading the decoder and semantic profile.
|
|
375
|
+
|
|
341
376
|
## TypeScript and module formats
|
|
342
377
|
|
|
343
378
|
The package includes TypeScript declarations and exports
|
|
@@ -363,21 +398,22 @@ const {
|
|
|
363
398
|
|
|
364
399
|
Run commands from the repository root.
|
|
365
400
|
|
|
366
|
-
| Command | Purpose
|
|
367
|
-
| ---------------------------------- |
|
|
368
|
-
| `npm ci` | Install locked dependencies.
|
|
369
|
-
| `npm run build` | Build ESM and CommonJS output.
|
|
370
|
-
| `npm
|
|
371
|
-
| `npm test -- --run
|
|
372
|
-
| `npm run
|
|
373
|
-
| `npm run codegen
|
|
374
|
-
| `npm run
|
|
375
|
-
| `npm run
|
|
376
|
-
| `npm run
|
|
377
|
-
| `npm run
|
|
378
|
-
| `npm run
|
|
379
|
-
| `npm run
|
|
380
|
-
| `npm run
|
|
401
|
+
| Command | Purpose |
|
|
402
|
+
| ---------------------------------- | --------------------------------------------------- |
|
|
403
|
+
| `npm ci` | Install locked dependencies. |
|
|
404
|
+
| `npm run build` | Build ESM and CommonJS output. |
|
|
405
|
+
| `npm run clean` | Remove the generated build output. |
|
|
406
|
+
| `npm test -- --run` | Run the complete test suite once. |
|
|
407
|
+
| `npm test -- --run test/<file>.ts` | Run a focused test file. |
|
|
408
|
+
| `npm run codegen` | Regenerate public types from the static profile. |
|
|
409
|
+
| `npm run codegen:check` | Verify generated public types are current. |
|
|
410
|
+
| `npm run profile:check` | Audit profile counts, structure, and fingerprints. |
|
|
411
|
+
| `npm run corpus:check -- <path>` | Validate an external FIT corpus without file data. |
|
|
412
|
+
| `npm run lint` | Check lint and formatting rules. |
|
|
413
|
+
| `npm run fmt` | Apply the configured formatting rules. |
|
|
414
|
+
| `npm run type-check` | Run TypeScript without emitting files. |
|
|
415
|
+
| `npm run examples` | Build and regenerate checked-in example outputs. |
|
|
416
|
+
| `npm run check` | Run profile checks, lint, types, tests, and builds. |
|
|
381
417
|
|
|
382
418
|
### External FIT corpus (optional)
|
|
383
419
|
|
|
@@ -390,18 +426,26 @@ git clone https://github.com/ThomasKuehne/FIT-test-files.git ../FIT-test-files
|
|
|
390
426
|
npm run corpus:check -- ../FIT-test-files --allow-force-recovery
|
|
391
427
|
npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages
|
|
392
428
|
npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages-with-decoded-output
|
|
429
|
+
npm run compatibility:check -- /path/to/reference-package ../FIT-test-files
|
|
393
430
|
```
|
|
394
431
|
|
|
395
432
|
The command accepts any corpus path; the sibling location is only a convenient
|
|
396
433
|
convention. Add `--raw-messages` to exercise lossless raw-message-only parsing
|
|
397
434
|
for every message in each file, or `--raw-messages-with-decoded-output` to also
|
|
398
|
-
verify the ordinary decoded output path.
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
`
|
|
435
|
+
verify the ordinary decoded output path. Add `--unmapped-summary` for a
|
|
436
|
+
privacy-safe aggregate of unknown message/field identifiers, base types, sizes,
|
|
437
|
+
occurrences, and file counts. The corpus contains a known header-CRC failure
|
|
438
|
+
that is expected to recover only in force mode. Reports never print file names
|
|
439
|
+
or parsed activity values.
|
|
440
|
+
|
|
441
|
+
`compatibility:check` parses every corpus file with both the current build and
|
|
442
|
+
a reference package, compares their complete default outputs, and reports only
|
|
443
|
+
aggregate counts. Files rejected by both strict parsers are retried in force
|
|
444
|
+
mode.
|
|
445
|
+
|
|
446
|
+
Edit `src/profile.ts` only through reviewed profile changes with focused
|
|
447
|
+
regression coverage and a documented source. Do not edit `src/fit_types.ts`
|
|
448
|
+
manually; run `npm run codegen` after changing the maintained profile.
|
|
405
449
|
|
|
406
450
|
Repository-specific automation guidance is tracked in
|
|
407
451
|
[`.agent/README.md`](./.agent/README.md). More examples are available in the
|
package/dist/binary.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDe
|
|
|
40
40
|
compressedTimestamp?: number;
|
|
41
41
|
rawFields?: RawFieldValue[];
|
|
42
42
|
rawDeveloperFields?: RawDeveloperFieldValue[];
|
|
43
|
+
unmappedFields?: RawFieldValue[];
|
|
44
|
+
unmappedDeveloperFields?: RawDeveloperFieldValue[];
|
|
43
45
|
};
|
|
44
46
|
export declare function getArrayBuffer(buffer: ArrayBuffer | Buffer): ArrayBuffer;
|
|
45
47
|
export declare function calculateCRC(blob: Uint8Array, start: number, end: number): number;
|
package/dist/binary.js
CHANGED
|
@@ -522,6 +522,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
522
522
|
&& options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
|
|
523
523
|
const rawFields = includeRawMessage ? [] : undefined;
|
|
524
524
|
const rawDeveloperFields = includeRawDeveloperFields || includeRawMessage ? [] : undefined;
|
|
525
|
+
const unmappedFields = options.includeUnmappedMessages ? [] : undefined;
|
|
526
|
+
const unmappedDeveloperFields = options.includeUnmappedMessages ? [] : undefined;
|
|
525
527
|
if (retainsRawMessages(options)) {
|
|
526
528
|
const nativeSize = messageType.fieldDefs.reduce((total, field, index) => (total + (isCompressedTimestamp && index === 0 && field.fDefNo === 253 ? 0 : field.size)), 0);
|
|
527
529
|
const developerSize = developerFieldDefs.reduce((total, field) => total + field.size, 0);
|
|
@@ -537,12 +539,17 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
537
539
|
rawData[i] = InvalidFieldData;
|
|
538
540
|
continue;
|
|
539
541
|
}
|
|
540
|
-
if (rawFields
|
|
541
|
-
|
|
542
|
+
if ((rawFields || (unmappedFields && !isOutputFieldName(fDef.name)))
|
|
543
|
+
&& readDataFromIndex + fDef.size <= dataEnd) {
|
|
544
|
+
const rawField = {
|
|
542
545
|
fieldDefinitionNumber: fDef.fDefNo,
|
|
543
546
|
baseType: fDef.baseTypeNo,
|
|
544
547
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + fDef.size)),
|
|
545
|
-
}
|
|
548
|
+
};
|
|
549
|
+
rawFields === null || rawFields === void 0 ? void 0 : rawFields.push(rawField);
|
|
550
|
+
if (!isOutputFieldName(fDef.name)) {
|
|
551
|
+
unmappedFields === null || unmappedFields === void 0 ? void 0 : unmappedFields.push(rawField);
|
|
552
|
+
}
|
|
546
553
|
}
|
|
547
554
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
548
555
|
if (data !== InvalidFieldData
|
|
@@ -566,15 +573,21 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
566
573
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
567
574
|
const developerFieldDef = developerFieldDefs[i];
|
|
568
575
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
569
|
-
|
|
576
|
+
let rawDeveloperField;
|
|
577
|
+
if ((rawDeveloperFields
|
|
578
|
+
|| (unmappedDeveloperFields && developerFieldDef.resolvedFieldDef === undefined))
|
|
570
579
|
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
571
|
-
|
|
580
|
+
rawDeveloperField = {
|
|
572
581
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
573
582
|
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
574
583
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
575
|
-
}
|
|
584
|
+
};
|
|
585
|
+
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push(rawDeveloperField);
|
|
576
586
|
}
|
|
577
587
|
const fDef = resolveDeveloperFieldDefinition(developerFieldDef, messageType.littleEndian, developerFields, options);
|
|
588
|
+
if (!fDef && rawDeveloperField) {
|
|
589
|
+
unmappedDeveloperFields === null || unmappedDeveloperFields === void 0 ? void 0 : unmappedDeveloperFields.push(rawDeveloperField);
|
|
590
|
+
}
|
|
578
591
|
if (fDef) {
|
|
579
592
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
580
593
|
if (data !== InvalidFieldData
|
|
@@ -692,6 +705,10 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
692
705
|
message: fields,
|
|
693
706
|
rawFields,
|
|
694
707
|
rawDeveloperFields,
|
|
708
|
+
unmappedFields: unmappedFields && unmappedFields.length > 0 ? unmappedFields : undefined,
|
|
709
|
+
unmappedDeveloperFields: unmappedDeveloperFields && unmappedDeveloperFields.length > 0
|
|
710
|
+
? unmappedDeveloperFields
|
|
711
|
+
: undefined,
|
|
695
712
|
};
|
|
696
713
|
}
|
|
697
714
|
export function getArrayBuffer(buffer) {
|
package/dist/cjs/binary.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDe
|
|
|
40
40
|
compressedTimestamp?: number;
|
|
41
41
|
rawFields?: RawFieldValue[];
|
|
42
42
|
rawDeveloperFields?: RawDeveloperFieldValue[];
|
|
43
|
+
unmappedFields?: RawFieldValue[];
|
|
44
|
+
unmappedDeveloperFields?: RawDeveloperFieldValue[];
|
|
43
45
|
};
|
|
44
46
|
export declare function getArrayBuffer(buffer: ArrayBuffer | Buffer): ArrayBuffer;
|
|
45
47
|
export declare function calculateCRC(blob: Uint8Array, start: number, end: number): number;
|