fit-file-parser 5.2.0 → 6.0.2
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 +18 -187
- package/PROFILE.md +43 -0
- package/README.md +75 -81
- package/dist/binary.d.ts +2 -0
- package/dist/binary.js +28 -58
- package/dist/cjs/binary.d.ts +2 -0
- package/dist/cjs/binary.js +28 -58
- package/dist/cjs/fit-parser.d.ts +2 -0
- package/dist/cjs/fit-parser.js +25 -3
- package/dist/cjs/fit.d.ts +0 -6
- package/dist/cjs/fit.js +4 -114
- package/dist/cjs/fit_types.d.ts +1 -0
- package/dist/cjs/profile.d.ts +9 -0
- package/dist/{garmin_profile.generated.js → cjs/profile.js} +285 -8
- package/dist/cjs/type_generator.js +1 -0
- package/dist/fit-parser.d.ts +2 -0
- package/dist/fit-parser.js +25 -3
- package/dist/fit.d.ts +0 -6
- package/dist/fit.js +3 -113
- package/dist/fit_types.d.ts +1 -0
- package/dist/profile.d.ts +9 -0
- package/dist/{cjs/garmin_profile.generated.js → profile.js} +282 -11
- package/dist/type_generator.js +1 -0
- package/package.json +10 -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,195 +1,26 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
|
|
5
|
-
### Added
|
|
6
|
-
|
|
7
|
-
- Add opt-in, message-filterable `raw_messages` output with exact native and
|
|
8
|
-
developer-field bytes, wire base types, endianness, message occurrence, and
|
|
9
|
-
reconstructed compressed timestamps. Existing decoded output remains
|
|
10
|
-
unchanged when the option is disabled.
|
|
11
|
-
- Add `rawMessagesOnly` for bounded metadata consumers that do not need decoded
|
|
12
|
-
activity collections alongside retained messages.
|
|
13
|
-
|
|
14
|
-
### Fixed
|
|
15
|
-
|
|
16
|
-
- Decode compressed timestamp records without consuming bytes for the omitted
|
|
17
|
-
timestamp field.
|
|
18
|
-
|
|
19
|
-
## 5.1.0
|
|
20
|
-
|
|
21
|
-
### Added
|
|
22
|
-
|
|
23
|
-
- Add opt-in, message-filterable `raw_developer_fields` output with exact
|
|
24
|
-
developer-field bytes, global-message occurrence, developer-data index, and
|
|
25
|
-
field-definition number. Existing decoded output remains unchanged when the
|
|
26
|
-
option is disabled.
|
|
27
|
-
|
|
28
|
-
## 5.0.2
|
|
29
|
-
|
|
30
|
-
### Fixed
|
|
31
|
-
|
|
32
|
-
- Clarify the 5.0 migration for standard session field 196: use canonical
|
|
33
|
-
`metabolic_calories` rather than the old duplicate `resting_calories` label.
|
|
34
|
-
- Clarify that the removed guessed `recovery_advisor` label was standard
|
|
35
|
-
session field 140, which the SDK defines as `avg_depth` in meters.
|
|
36
|
-
|
|
37
|
-
## 5.0.1
|
|
38
|
-
|
|
39
|
-
### Fixed
|
|
40
|
-
|
|
41
|
-
- Run CI and version-driven npm publishing from the repository's `main`
|
|
42
|
-
branch, with a guarded manual recovery path for a missed release.
|
|
43
|
-
|
|
44
|
-
## 5.0.0
|
|
3
|
+
## 6.0.2 - 2026-09-21
|
|
45
4
|
|
|
46
5
|
### Changed
|
|
47
6
|
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
|
|
52
|
-
- Restrict non-SDK support to a collision-checked allowlist of Garmin fields
|
|
53
|
-
and private messages observed in the external FIT corpus.
|
|
54
|
-
- Emit only parsed FIT values: Garmin product names are no longer inferred,
|
|
55
|
-
and record elapsed/timer values are generated only when
|
|
56
|
-
`elapsedRecordField: true` is requested.
|
|
57
|
-
|
|
58
|
-
### Breaking
|
|
59
|
-
|
|
60
|
-
- Remove compatibility aliases such as `resting_calories`,
|
|
61
|
-
`recovery_advisor`, `repeat_dive_time`, and deprecated time-in-zone names.
|
|
62
|
-
- Remove guessed stress fields, handwritten enum/type additions, placeholder
|
|
63
|
-
zero-mask names, and the misspelled `hip_swing_excercise_name` type.
|
|
64
|
-
- Use the pinned SDK's `cadence_zone_high_bondary` spelling and generated
|
|
65
|
-
alphanumeric field/type spellings instead of manual corrections.
|
|
66
|
-
- Correct generated declarations to expose runtime `Date` values, numeric FIT
|
|
67
|
-
`bool` fields and unknown enum IDs, decoded mask objects, nullable array
|
|
68
|
-
entries, and optional profile fields.
|
|
69
|
-
- Keep undated cascade rows in file order and avoid duplicating them when a lap
|
|
70
|
-
or session boundary timestamp is absent.
|
|
71
|
-
|
|
72
|
-
## 4.1.1
|
|
73
|
-
|
|
74
|
-
### Fixed
|
|
75
|
-
|
|
76
|
-
- Apply Garmin FIT profile scale and offset to primitive numeric fields, while
|
|
77
|
-
retaining semicircle conversion only for fields declared in semicircles.
|
|
78
|
-
- Apply FIT developer-field offsets with the parser's equivalent signed form,
|
|
79
|
-
including scaled signed developer values.
|
|
80
|
-
- Decode native diving ascent-rate fields as meters per second instead of
|
|
81
|
-
geographic coordinates.
|
|
82
|
-
- Decode native dive depth and bottom-time fields with their Garmin FIT SDK
|
|
83
|
-
scale instead of exposing their encoded integer representation.
|
|
84
|
-
- Keep the pinned Garmin SDK authoritative for standard field types, arrays,
|
|
85
|
-
scales, offsets, and units while retaining compatible public field names.
|
|
86
|
-
|
|
87
|
-
## 4.1.0
|
|
7
|
+
- Consolidate the complete message, field, wire-metadata, type, enum, and
|
|
8
|
+
product contract into one static maintained profile.
|
|
9
|
+
- Maintain the FIT interoperability profile directly as static project source.
|
|
10
|
+
- Preserve decoded output and the public TypeScript surface.
|
|
88
11
|
|
|
89
12
|
### Added
|
|
90
13
|
|
|
91
|
-
-
|
|
92
|
-
|
|
93
|
-
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
- Reconstruct compressed timestamps and keep timestamp state isolated between
|
|
105
|
-
parser instances.
|
|
106
|
-
- Accept omitted header CRCs, validate file CRCs across the complete FIT header
|
|
107
|
-
and data section, and report strict header and file CRC failures explicitly.
|
|
108
|
-
- Reject structurally unsafe FIT inputs consistently in callback and Promise
|
|
109
|
-
APIs while retaining force-mode recovery for CRC corruption.
|
|
110
|
-
- Correct Garmin profile mappings for OHR settings, monitoring HR, sleep,
|
|
111
|
-
time-in-zone, altitude offsets, and lap/segment flow and grit summaries.
|
|
112
|
-
- Correct Celsius-to-Kelvin conversion, add `celsius` as the canonical
|
|
113
|
-
temperature unit, and retain `°C` as a supported alias.
|
|
114
|
-
|
|
115
|
-
### Compatibility and documentation
|
|
116
|
-
|
|
117
|
-
- Preserve compatible legacy field names, scales, value shapes, parser
|
|
118
|
-
signatures, output modes, and date behavior while adding canonical profile
|
|
119
|
-
names.
|
|
120
|
-
- Add regression coverage for temperature, pressure, validation, compressed
|
|
121
|
-
timestamps, generated profile messages, repeated messages, and MTB
|
|
122
|
-
flow/grit data.
|
|
123
|
-
- Refresh the README with current runtime, API, units, output modes, developer
|
|
124
|
-
fields, encoder behavior, and repository commands.
|
|
125
|
-
|
|
126
|
-
## 4.0.2
|
|
127
|
-
|
|
128
|
-
- Preserve record alignment when developer-field descriptions are missing or
|
|
129
|
-
appear after their message definitions.
|
|
130
|
-
- Decode subsequent developer-field values once their descriptions become
|
|
131
|
-
available, in both strict and force modes.
|
|
132
|
-
|
|
133
|
-
## 4.0.0
|
|
134
|
-
|
|
135
|
-
### FIT decoder performance
|
|
136
|
-
|
|
137
|
-
- Parse `ArrayBuffer` inputs and exact Node.js `Buffer` views directly, avoiding a full copy of the source file.
|
|
138
|
-
- Reuse one parse-local `DataView` instead of allocating temporary views for each supported endian field.
|
|
139
|
-
- Cache standard field metadata and enum/mask lookups on reusable message definitions.
|
|
140
|
-
- Reuse raw field storage for records that share a local message definition.
|
|
141
|
-
- Generate elapsed and timer fields once per record instead of once per decoded field.
|
|
142
|
-
- Skip header and file CRC scans in `force: true` mode, where CRC mismatches are intentionally ignored; strict mode continues to validate both CRCs.
|
|
143
|
-
- Preserve legacy malformed-field zero-padding, field-boundary, developer-field, invalid-value, and offset-buffer behavior.
|
|
144
|
-
|
|
145
|
-
### Measured benefit
|
|
146
|
-
|
|
147
|
-
- Suunto 93-hour / 7.5 MB FIT decoding: 1.057 s to 0.375 s, a 64.5% reduction.
|
|
148
|
-
- Garmin 110-hour / 28.6 MB FIT decoding: 3.643 s to 1.241 s, a 65.9% reduction.
|
|
149
|
-
- Input-related array-buffer memory is approximately halved by removing the full source copy:
|
|
150
|
-
- Suunto: 15.0 MB to 7.5 MB.
|
|
151
|
-
- Garmin: 57.2 MB to 28.6 MB.
|
|
152
|
-
|
|
153
|
-
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.
|
|
154
|
-
|
|
155
|
-
## 3.1.0
|
|
156
|
-
|
|
157
|
-
- Add the public `FitEncoder` API for writing FIT headers, definitions, data messages, and CRCs.
|
|
158
|
-
- Preserve the `course.sub_sport` field while parsing FIT course files.
|
|
159
|
-
|
|
160
|
-
<a name="1.5.4"></a>
|
|
161
|
-
|
|
162
|
-
## [1.5.4](https://github.com/jimmykane/fit-parser/compare/v1.0.0...v1.5.3) (2019-03-01)
|
|
163
|
-
|
|
164
|
-
- **Features**: HRV, Developer fields, devices and much more
|
|
165
|
-
- **Miscellaneous**: Fix most of the issues parsing fit files
|
|
166
|
-
|
|
167
|
-
<a name="1.0.1"></a>
|
|
168
|
-
|
|
169
|
-
## [1.0.1](https://github.com/pierremtb/easy-fit/compare/v1.0.0...v1.0.1) (2018-09-18)
|
|
170
|
-
|
|
171
|
-
### 😭 Unclassified (not [following convention](https://github.com/sportheroes/bk-conventional-changelog#types-of-commits))
|
|
172
|
-
|
|
173
|
-
- **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))
|
|
174
|
-
|
|
175
|
-
<a name="1.0.0"></a>
|
|
176
|
-
|
|
177
|
-
# [1.0.0](https://github.com/pierremtb/easy-fit/compare/0.0.7...1.0.0) (2018-09-17)
|
|
178
|
-
|
|
179
|
-
### 😭 Unclassified (not [following convention](https://github.com/sportheroes/bk-conventional-changelog#types-of-commits))
|
|
180
|
-
|
|
181
|
-
- **Miscellaneous**: Fix readme typo ([2282c06](https://github.com/pierremtb/easy-fit/commit/2282c06)))
|
|
182
|
-
- **Miscellaneous**: chore: preparing to release fork internally ([3eef5f7](https://github.com/pierremtb/easy-fit/commit/3eef5f7) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
183
|
-
- **Miscellaneous**: fix: Typo leading to uint16 to never be invalidated ([9ed802c](https://github.com/pierremtb/easy-fit/commit/9ed802c) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
184
|
-
- **Miscellaneous**: Merge remote-tracking branch 'jenglert/master' ([819d78e](https://github.com/pierremtb/easy-fit/commit/819d78e)))
|
|
185
|
-
- **Miscellaneous**: fix missing buffer dependency and compile dst ([a4b237a](https://github.com/pierremtb/easy-fit/commit/a4b237a)))
|
|
186
|
-
- **Miscellaneous**: Merge remote-tracking branch 'FrostDigital/master' ([6fa9258](https://github.com/pierremtb/easy-fit/commit/6fa9258)))
|
|
187
|
-
- **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)
|
|
188
|
-
- **Miscellaneous**: fix: Adjusted offset for all altitude-related fields ([1ff0fa4](https://github.com/pierremtb/easy-fit/commit/1ff0fa4) - [JoeTheFkingFrypan](https://github.com/JoeTheFkingFrypan))
|
|
189
|
-
- **Miscellaneous**: chore(devices): add forerunner 735 xt ([aacaa04](https://github.com/pierremtb/easy-fit/commit/aacaa04)))
|
|
190
|
-
- **Miscellaneous**: Compile src ([ee4aa00](https://github.com/pierremtb/easy-fit/commit/ee4aa00)))
|
|
191
|
-
- **Miscellaneous**: Add support for other lap types ([dcf42a1](https://github.com/pierremtb/easy-fit/commit/dcf42a1)))
|
|
192
|
-
- **Miscellaneous**: Compile src ([50b9b21](https://github.com/pierremtb/easy-fit/commit/50b9b21)))
|
|
193
|
-
- **Miscellaneous**: Merge remote-tracking branch 'pierremtb/master' ([f7f5ff0](https://github.com/pierremtb/easy-fit/commit/f7f5ff0)))
|
|
194
|
-
- **Miscellaneous**: Add support for more manufacturers ([2d122d2](https://github.com/pierremtb/easy-fit/commit/2d122d2)))
|
|
195
|
-
- **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)))
|
|
14
|
+
- Add opt-in `unmapped_messages` preservation, including exact bytes and
|
|
15
|
+
definition metadata, without changing default parsed output.
|
|
16
|
+
- Add regression coverage for unknown messages, unknown fields inside known
|
|
17
|
+
messages, the immutable compatibility profile, and default-output stability.
|
|
18
|
+
- Add an optional privacy-safe corpus summary for unmapped message/field IDs,
|
|
19
|
+
wire base types, sizes, occurrences, and file counts.
|
|
20
|
+
- Document the manual update requirements and corpus verification workflow in
|
|
21
|
+
`PROFILE.md`.
|
|
22
|
+
|
|
23
|
+
There are no intentional parsed-output or public API changes in this release.
|
|
24
|
+
Output parity was verified across 162 checked-in fixture/mode combinations,
|
|
25
|
+
8,320 generated malformed endian-definition cases, and both private
|
|
26
|
+
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
|
|
@@ -363,21 +344,22 @@ const {
|
|
|
363
344
|
|
|
364
345
|
Run commands from the repository root.
|
|
365
346
|
|
|
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
|
|
347
|
+
| Command | Purpose |
|
|
348
|
+
| ---------------------------------- | --------------------------------------------------- |
|
|
349
|
+
| `npm ci` | Install locked dependencies. |
|
|
350
|
+
| `npm run build` | Build ESM and CommonJS output. |
|
|
351
|
+
| `npm run clean` | Remove the generated build output. |
|
|
352
|
+
| `npm test -- --run` | Run the complete test suite once. |
|
|
353
|
+
| `npm test -- --run test/<file>.ts` | Run a focused test file. |
|
|
354
|
+
| `npm run codegen` | Regenerate public types from the static profile. |
|
|
355
|
+
| `npm run codegen:check` | Verify generated public types are current. |
|
|
356
|
+
| `npm run profile:check` | Audit profile counts, structure, and fingerprints. |
|
|
357
|
+
| `npm run corpus:check -- <path>` | Validate an external FIT corpus without file data. |
|
|
358
|
+
| `npm run lint` | Check lint and formatting rules. |
|
|
359
|
+
| `npm run fmt` | Apply the configured formatting rules. |
|
|
360
|
+
| `npm run type-check` | Run TypeScript without emitting files. |
|
|
361
|
+
| `npm run examples` | Build and regenerate checked-in example outputs. |
|
|
362
|
+
| `npm run check` | Run profile checks, lint, types, tests, and builds. |
|
|
381
363
|
|
|
382
364
|
### External FIT corpus (optional)
|
|
383
365
|
|
|
@@ -388,16 +370,28 @@ aggregate-only validation command:
|
|
|
388
370
|
```sh
|
|
389
371
|
git clone https://github.com/ThomasKuehne/FIT-test-files.git ../FIT-test-files
|
|
390
372
|
npm run corpus:check -- ../FIT-test-files --allow-force-recovery
|
|
373
|
+
npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages
|
|
374
|
+
npm run corpus:check -- ../FIT-test-files --allow-force-recovery --raw-messages-with-decoded-output
|
|
375
|
+
npm run compatibility:check -- /path/to/reference-package ../FIT-test-files
|
|
391
376
|
```
|
|
392
377
|
|
|
393
378
|
The command accepts any corpus path; the sibling location is only a convenient
|
|
394
|
-
convention.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
379
|
+
convention. Add `--raw-messages` to exercise lossless raw-message-only parsing
|
|
380
|
+
for every message in each file, or `--raw-messages-with-decoded-output` to also
|
|
381
|
+
verify the ordinary decoded output path. Add `--unmapped-summary` for a
|
|
382
|
+
privacy-safe aggregate of unknown message/field identifiers, base types, sizes,
|
|
383
|
+
occurrences, and file counts. The corpus contains a known header-CRC failure
|
|
384
|
+
that is expected to recover only in force mode. Reports never print file names
|
|
385
|
+
or parsed activity values.
|
|
386
|
+
|
|
387
|
+
`compatibility:check` parses every corpus file with both the current build and
|
|
388
|
+
a reference package, compares their complete default outputs, and reports only
|
|
389
|
+
aggregate counts. Files rejected by both strict parsers are retried in force
|
|
390
|
+
mode.
|
|
391
|
+
|
|
392
|
+
Edit `src/profile.ts` only through reviewed profile changes with focused
|
|
393
|
+
regression coverage and a documented source. Do not edit `src/fit_types.ts`
|
|
394
|
+
manually; run `npm run codegen` after changing the maintained profile.
|
|
401
395
|
|
|
402
396
|
Repository-specific automation guidance is tracked in
|
|
403
397
|
[`.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
|
@@ -8,36 +8,14 @@ const GarminTimeOffset = 631065600000;
|
|
|
8
8
|
const InvalidFieldData = Symbol('invalid FIT field data');
|
|
9
9
|
const formatTypeMetadata = new Map();
|
|
10
10
|
const uint8CompatibleTypes = new Set(['enum', 'uint8', 'byte']);
|
|
11
|
-
const fitBaseTypeWidths = new Map([
|
|
12
|
-
[0, 1],
|
|
13
|
-
[1, 1],
|
|
14
|
-
[2, 1],
|
|
15
|
-
[3, 2],
|
|
16
|
-
[4, 2],
|
|
17
|
-
[5, 4],
|
|
18
|
-
[6, 4],
|
|
19
|
-
[7, 1],
|
|
20
|
-
[8, 4],
|
|
21
|
-
[9, 8],
|
|
22
|
-
[10, 1],
|
|
23
|
-
[11, 2],
|
|
24
|
-
[12, 4],
|
|
25
|
-
[13, 1],
|
|
26
|
-
[14, 8],
|
|
27
|
-
[15, 8],
|
|
28
|
-
[16, 8],
|
|
29
|
-
]);
|
|
30
11
|
function retainsRawMessages(options) {
|
|
31
12
|
return options.includeRawMessages === true
|
|
32
13
|
|| Array.isArray(options.includeRawMessages);
|
|
33
14
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const typeId = baseType & 0x1F;
|
|
39
|
-
const width = fitBaseTypeWidths.get(typeId);
|
|
40
|
-
return width !== undefined && (typeId === 7 || size % width === 0);
|
|
15
|
+
function retainsRawMessage(options, globalMessageNumber) {
|
|
16
|
+
return options.includeRawMessages === true
|
|
17
|
+
|| (Array.isArray(options.includeRawMessages)
|
|
18
|
+
&& options.includeRawMessages.includes(globalMessageNumber));
|
|
41
19
|
}
|
|
42
20
|
function baseTypeSize(type) {
|
|
43
21
|
switch (type) {
|
|
@@ -480,18 +458,11 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
480
458
|
rawData: [],
|
|
481
459
|
};
|
|
482
460
|
const message = getFitMessage(mTypeDef.globalMessageNumber);
|
|
483
|
-
const nativeFieldNumbers = new Set();
|
|
484
461
|
for (let i = 0; i < numberOfFields; i++) {
|
|
485
462
|
const fDefIndex = startIndex + 6 + i * 3;
|
|
486
463
|
const baseType = blob[fDefIndex + 2];
|
|
487
464
|
const fieldNumber = blob[fDefIndex];
|
|
488
465
|
const fieldSize = blob[fDefIndex + 1];
|
|
489
|
-
if (retainsRawMessages(options)
|
|
490
|
-
&& (nativeFieldNumbers.has(fieldNumber)
|
|
491
|
-
|| !isValidRawFieldDefinition(fieldSize, baseType))) {
|
|
492
|
-
throw new Error('Invalid FIT native field definition');
|
|
493
|
-
}
|
|
494
|
-
nativeFieldNumbers.add(fieldNumber);
|
|
495
466
|
const wireType = FIT.types.fit_base_type[baseType];
|
|
496
467
|
const { field, type, baseType: profileBaseType, array, scale, offset, units, } = message.getAttributes(blob[fDefIndex]);
|
|
497
468
|
const profileCompatible = areProfileBaseTypesCompatible(profileBaseType, wireType);
|
|
@@ -515,15 +486,8 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
515
486
|
};
|
|
516
487
|
mTypeDef.fieldDefs.push(fDef);
|
|
517
488
|
}
|
|
518
|
-
const developerFieldNumbers = new Set();
|
|
519
489
|
for (let i = 0; i < numberOfDeveloperDataFields; i++) {
|
|
520
490
|
const fDefIndex = startIndex + 6 + numberOfFields * 3 + 1 + i * 3;
|
|
521
|
-
const developerFieldKey = `${blob[fDefIndex + 2]}:${blob[fDefIndex]}`;
|
|
522
|
-
if (retainsRawMessages(options)
|
|
523
|
-
&& (blob[fDefIndex + 1] === 0 || developerFieldNumbers.has(developerFieldKey))) {
|
|
524
|
-
throw new Error('Invalid FIT developer field definition');
|
|
525
|
-
}
|
|
526
|
-
developerFieldNumbers.add(developerFieldKey);
|
|
527
491
|
(_a = mTypeDef.developerFieldDefs) === null || _a === void 0 ? void 0 : _a.push({
|
|
528
492
|
fieldDefinitionNumber: blob[fDefIndex],
|
|
529
493
|
size: blob[fDefIndex + 1],
|
|
@@ -546,29 +510,20 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
546
510
|
if (!messageType) {
|
|
547
511
|
throw new Error('FIT data record has no local definition');
|
|
548
512
|
}
|
|
549
|
-
if (isCompressedTimestamp && retainsRawMessages(options)) {
|
|
550
|
-
const timestampField = messageType.fieldDefs[0];
|
|
551
|
-
if (!timestampField
|
|
552
|
-
|| timestampField.fDefNo !== 253
|
|
553
|
-
|| timestampField.size !== 4
|
|
554
|
-
|| (timestampField.baseTypeNo & 0x1F) !== 6) {
|
|
555
|
-
throw new Error('Invalid FIT compressed timestamp definition');
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
513
|
let messageSize = 0;
|
|
559
514
|
let readDataFromIndex = startIndex + 1;
|
|
560
515
|
const fields = {};
|
|
561
516
|
const message = getFitMessage(messageType.globalMessageNumber);
|
|
562
517
|
const developerFieldDefs = (_b = messageType.developerFieldDefs) !== null && _b !== void 0 ? _b : [];
|
|
563
518
|
const totalFieldCount = messageType.fieldDefs.length + developerFieldDefs.length;
|
|
564
|
-
const includeRawMessage = options.
|
|
565
|
-
|| (Array.isArray(options.includeRawMessages)
|
|
566
|
-
&& options.includeRawMessages.includes(messageType.globalMessageNumber));
|
|
519
|
+
const includeRawMessage = retainsRawMessage(options, messageType.globalMessageNumber);
|
|
567
520
|
const includeRawDeveloperFields = options.includeRawDeveloperFields === true
|
|
568
521
|
|| (Array.isArray(options.includeRawDeveloperFields)
|
|
569
522
|
&& options.includeRawDeveloperFields.includes(messageType.globalMessageNumber));
|
|
570
523
|
const rawFields = includeRawMessage ? [] : undefined;
|
|
571
524
|
const rawDeveloperFields = includeRawDeveloperFields || includeRawMessage ? [] : undefined;
|
|
525
|
+
const unmappedFields = options.includeUnmappedMessages ? [] : undefined;
|
|
526
|
+
const unmappedDeveloperFields = options.includeUnmappedMessages ? [] : undefined;
|
|
572
527
|
if (retainsRawMessages(options)) {
|
|
573
528
|
const nativeSize = messageType.fieldDefs.reduce((total, field, index) => (total + (isCompressedTimestamp && index === 0 && field.fDefNo === 253 ? 0 : field.size)), 0);
|
|
574
529
|
const developerSize = developerFieldDefs.reduce((total, field) => total + field.size, 0);
|
|
@@ -584,12 +539,17 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
584
539
|
rawData[i] = InvalidFieldData;
|
|
585
540
|
continue;
|
|
586
541
|
}
|
|
587
|
-
if (rawFields
|
|
588
|
-
|
|
542
|
+
if ((rawFields || (unmappedFields && !isOutputFieldName(fDef.name)))
|
|
543
|
+
&& readDataFromIndex + fDef.size <= dataEnd) {
|
|
544
|
+
const rawField = {
|
|
589
545
|
fieldDefinitionNumber: fDef.fDefNo,
|
|
590
546
|
baseType: fDef.baseTypeNo,
|
|
591
547
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + fDef.size)),
|
|
592
|
-
}
|
|
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
|
+
}
|
|
593
553
|
}
|
|
594
554
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
595
555
|
if (data !== InvalidFieldData
|
|
@@ -613,15 +573,21 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
613
573
|
for (let i = 0; i < developerFieldDefs.length; i++) {
|
|
614
574
|
const developerFieldDef = developerFieldDefs[i];
|
|
615
575
|
const rawDataIndex = messageType.fieldDefs.length + i;
|
|
616
|
-
|
|
576
|
+
let rawDeveloperField;
|
|
577
|
+
if ((rawDeveloperFields
|
|
578
|
+
|| (unmappedDeveloperFields && developerFieldDef.resolvedFieldDef === undefined))
|
|
617
579
|
&& readDataFromIndex + developerFieldDef.size <= dataEnd) {
|
|
618
|
-
|
|
580
|
+
rawDeveloperField = {
|
|
619
581
|
developerDataIndex: developerFieldDef.developerDataIndex,
|
|
620
582
|
fieldDefinitionNumber: developerFieldDef.fieldDefinitionNumber,
|
|
621
583
|
rawValue: Array.from(blob.subarray(readDataFromIndex, readDataFromIndex + developerFieldDef.size)),
|
|
622
|
-
}
|
|
584
|
+
};
|
|
585
|
+
rawDeveloperFields === null || rawDeveloperFields === void 0 ? void 0 : rawDeveloperFields.push(rawDeveloperField);
|
|
623
586
|
}
|
|
624
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
|
+
}
|
|
625
591
|
if (fDef) {
|
|
626
592
|
const data = readData(blob, dataView, fDef, readDataFromIndex);
|
|
627
593
|
if (data !== InvalidFieldData
|
|
@@ -739,6 +705,10 @@ export function readRecord(blob, messageTypes, developerFields, startIndex, opti
|
|
|
739
705
|
message: fields,
|
|
740
706
|
rawFields,
|
|
741
707
|
rawDeveloperFields,
|
|
708
|
+
unmappedFields: unmappedFields && unmappedFields.length > 0 ? unmappedFields : undefined,
|
|
709
|
+
unmappedDeveloperFields: unmappedDeveloperFields && unmappedDeveloperFields.length > 0
|
|
710
|
+
? unmappedDeveloperFields
|
|
711
|
+
: undefined,
|
|
742
712
|
};
|
|
743
713
|
}
|
|
744
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;
|