fit-file-parser 4.0.2 → 4.1.0
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 +41 -0
- package/README.md +190 -119
- package/dist/binary.d.ts +5 -1
- package/dist/binary.js +202 -141
- package/dist/cjs/binary.d.ts +5 -1
- package/dist/cjs/binary.js +202 -141
- package/dist/cjs/fit-parser.js +37 -19
- package/dist/cjs/fit.d.ts +6 -0
- package/dist/cjs/fit.js +167 -44
- package/dist/cjs/fit_types.d.ts +1297 -207
- package/dist/cjs/garmin_profile.generated.d.ts +9 -0
- package/dist/cjs/garmin_profile.generated.js +17838 -0
- package/dist/cjs/type_generator.d.ts +3 -0
- package/dist/cjs/type_generator.js +37 -9
- package/dist/fit-parser.js +37 -19
- package/dist/fit.d.ts +6 -0
- package/dist/fit.js +167 -44
- package/dist/fit_types.d.ts +1297 -207
- package/dist/garmin_profile.generated.d.ts +9 -0
- package/dist/garmin_profile.generated.js +17835 -0
- package/dist/type_generator.d.ts +3 -0
- package/dist/type_generator.js +36 -9
- package/package.json +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 4.1.0
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Generate all 124 standard messages, 1,406 fields, and 200 profile types from
|
|
10
|
+
the exactly pinned Garmin FIT SDK 21.208.0 profile.
|
|
11
|
+
- Retain every recognized message in file order under the typed
|
|
12
|
+
`ParsedFit.messages` index without changing existing list, cascade, or
|
|
13
|
+
singleton outputs.
|
|
14
|
+
- Decode Garmin strength-training `set` messages in list and cascade modes.
|
|
15
|
+
- Add reproducible generated-profile and privacy-safe external corpus audits,
|
|
16
|
+
and enforce profile freshness and coverage in CI.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Decode fields from their wire base types, including compatible developer
|
|
21
|
+
enum/uint8/byte definitions and correctly sized numeric arrays.
|
|
22
|
+
- Reconstruct compressed timestamps and keep timestamp state isolated between
|
|
23
|
+
parser instances.
|
|
24
|
+
- Accept omitted header CRCs, validate file CRCs across the complete FIT header
|
|
25
|
+
and data section, and report strict header and file CRC failures explicitly.
|
|
26
|
+
- Reject structurally unsafe FIT inputs consistently in callback and Promise
|
|
27
|
+
APIs while retaining force-mode recovery for CRC corruption.
|
|
28
|
+
- Correct Garmin profile mappings for OHR settings, monitoring HR, sleep,
|
|
29
|
+
time-in-zone, altitude offsets, and lap/segment flow and grit summaries.
|
|
30
|
+
- Correct Celsius-to-Kelvin conversion, add `celsius` as the canonical
|
|
31
|
+
temperature unit, and retain `°C` as a supported alias.
|
|
32
|
+
|
|
33
|
+
### Compatibility and documentation
|
|
34
|
+
|
|
35
|
+
- Preserve compatible legacy field names, scales, value shapes, parser
|
|
36
|
+
signatures, output modes, and date behavior while adding canonical profile
|
|
37
|
+
names.
|
|
38
|
+
- Add regression coverage for temperature, pressure, validation, compressed
|
|
39
|
+
timestamps, generated profile messages, repeated messages, and MTB
|
|
40
|
+
flow/grit data.
|
|
41
|
+
- Refresh the README with current runtime, API, units, output modes, developer
|
|
42
|
+
fields, encoder behavior, and repository commands.
|
|
43
|
+
|
|
3
44
|
## 4.0.2
|
|
4
45
|
|
|
5
46
|
- Preserve record alignment when developer-field descriptions are missing or
|
package/README.md
CHANGED
|
@@ -1,181 +1,252 @@
|
|
|
1
1
|
# fit-file-parser
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://github.com/jimmykane/fit-parser/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/fit-file-parser)
|
|
5
|
+
[](./LICENSE)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
Parse and encode FIT files in JavaScript and TypeScript. The parser supports
|
|
8
|
+
files produced by Garmin, Polar, Suunto, and other FIT-compatible devices,
|
|
9
|
+
including developer-defined data.
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- Parse Node.js `Buffer` and standard `ArrayBuffer` inputs.
|
|
14
|
+
- Choose flat lists, nested activity data, or both output shapes.
|
|
15
|
+
- Convert speed, length, temperature, and pressure fields to preferred units.
|
|
16
|
+
- Decode developer fields while preserving record alignment when descriptions
|
|
17
|
+
arrive after their definitions.
|
|
18
|
+
- Encode profile-agnostic FIT messages with validated field definitions and
|
|
19
|
+
CRCs.
|
|
20
|
+
- Use ESM or CommonJS with bundled TypeScript declarations.
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
11
23
|
|
|
12
|
-
|
|
24
|
+
- Node.js 20 or newer
|
|
13
25
|
|
|
14
|
-
|
|
26
|
+
## Installation
|
|
15
27
|
|
|
16
|
-
|
|
28
|
+
```sh
|
|
29
|
+
npm install fit-file-parser
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
The Promise API is the simplest way to parse a file:
|
|
17
35
|
|
|
18
36
|
```javascript
|
|
19
|
-
import
|
|
37
|
+
import { readFile } from 'node:fs/promises'
|
|
20
38
|
import FitParser from 'fit-file-parser'
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
force: true,
|
|
29
|
-
speedUnit: 'km/h',
|
|
30
|
-
lengthUnit: 'km',
|
|
31
|
-
temperatureUnit: 'kelvin',
|
|
32
|
-
pressureUnit: 'bar', // accept bar, cbar and psi (default is bar)
|
|
33
|
-
elapsedRecordField: true,
|
|
34
|
-
mode: 'cascade',
|
|
35
|
-
})
|
|
40
|
+
const content = await readFile('./activity.fit')
|
|
41
|
+
const parser = new FitParser({
|
|
42
|
+
mode: 'list',
|
|
43
|
+
speedUnit: 'km/h',
|
|
44
|
+
lengthUnit: 'km',
|
|
45
|
+
})
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
else {
|
|
44
|
-
console.log(JSON.stringify(data))
|
|
45
|
-
}
|
|
46
|
-
})
|
|
47
|
+
const data = await parser.parseAsync(content)
|
|
48
|
+
|
|
49
|
+
console.log({
|
|
50
|
+
sessions: data.sessions?.length ?? 0,
|
|
51
|
+
laps: data.laps?.length ?? 0,
|
|
52
|
+
records: data.records?.length ?? 0,
|
|
47
53
|
})
|
|
48
54
|
```
|
|
49
55
|
|
|
50
|
-
###
|
|
56
|
+
### Callback API
|
|
51
57
|
|
|
52
58
|
```javascript
|
|
53
|
-
import
|
|
59
|
+
import { readFile } from 'node:fs'
|
|
54
60
|
import FitParser from 'fit-file-parser'
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
readFile('./activity.fit', (readError, content) => {
|
|
63
|
+
if (readError) {
|
|
64
|
+
console.error(readError)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const parser = new FitParser()
|
|
69
|
+
parser.parse(content, (parseError, data) => {
|
|
70
|
+
if (parseError) {
|
|
71
|
+
console.error(parseError)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
console.log(data)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
58
78
|
```
|
|
59
79
|
|
|
60
|
-
|
|
80
|
+
Parser errors are strings. `parseAsync()` rejects with the same value that the
|
|
81
|
+
callback API receives as its first argument.
|
|
61
82
|
|
|
62
|
-
|
|
63
|
-
It is profile-agnostic: callers provide profile field identifiers and values in
|
|
64
|
-
their raw FIT representation (including any scale or offset). Scalar 64-bit
|
|
65
|
-
values use `bigint`; strings and numeric arrays use exact-size raw
|
|
66
|
-
`Uint8Array` values.
|
|
83
|
+
## Parser options
|
|
67
84
|
|
|
68
|
-
|
|
69
|
-
import { FitBaseType, FitEncoder } from 'fit-file-parser'
|
|
85
|
+
All options are optional.
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
87
|
+
| Option | Values | Default | Behavior |
|
|
88
|
+
| -------------------- | --------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `mode` | `list`, `cascade`, `both` | `list` | Controls whether primary activity collections are returned as root lists, nested data, or both. |
|
|
90
|
+
| `force` | `true`, `false` | `true` | Skips header and file CRC validation and enables supported best-effort field recovery. Structural header and data bounds are always validated. |
|
|
91
|
+
| `speedUnit` | `m/s`, `km/h`, `mph` | `m/s` | Converts speed-related fields. |
|
|
92
|
+
| `lengthUnit` | `m`, `km`, `mi` | `m` | Converts distance, altitude, and other length-related fields. |
|
|
93
|
+
| `temperatureUnit` | `celsius`, `°C`, `kelvin`, `fahrenheit` | `celsius` | Converts temperature fields. `°C` remains available as a legacy alias. |
|
|
94
|
+
| `pressureUnit` | `bar`, `cbar`, `psi` | `bar` | Converts pressure and tank-pressure fields. |
|
|
95
|
+
| `elapsedRecordField` | `true`, `false` | `false` | Adds `elapsed_time` and `timer_time`, in seconds, to records. |
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
97
|
+
`force: true` does not make arbitrary bytes a valid FIT file. Inputs that are
|
|
98
|
+
too short, have an invalid header size or signature, or declare data beyond the
|
|
99
|
+
available bytes are rejected in both modes.
|
|
79
100
|
|
|
80
|
-
|
|
81
|
-
argument) for each recurring message shape to avoid redundant definitions.
|
|
82
|
-
The encoder validates field definitions and numeric ranges before writing, so
|
|
83
|
-
an exception never leaves a partial message in the output.
|
|
101
|
+
## Output modes
|
|
84
102
|
|
|
85
|
-
|
|
103
|
+
The mode controls where sessions, laps, records, and related activity
|
|
104
|
+
collections are exposed.
|
|
86
105
|
|
|
87
|
-
|
|
106
|
+
| Mode | Root lists | Nested under `activity` | Default |
|
|
107
|
+
| --------- | ---------- | ----------------------- | ------- |
|
|
108
|
+
| `list` | Yes | No | Yes |
|
|
109
|
+
| `cascade` | No | Yes | No |
|
|
110
|
+
| `both` | Yes | Yes | No |
|
|
88
111
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
112
|
+
In cascade output, sessions contain their laps and laps contain their records
|
|
113
|
+
and lengths. Other parsed FIT message collections remain available where the
|
|
114
|
+
parser exposes them.
|
|
92
115
|
|
|
93
|
-
|
|
116
|
+
Every recognized message is also retained in file order in `data.messages`.
|
|
117
|
+
This additive index is useful for message kinds that historically exposed
|
|
118
|
+
only the last value at the root:
|
|
94
119
|
|
|
95
|
-
```
|
|
96
|
-
|
|
120
|
+
```javascript
|
|
121
|
+
const workoutSteps = data.messages?.workout_step ?? []
|
|
122
|
+
const diveSummaries = data.messages?.dive_summary ?? []
|
|
97
123
|
```
|
|
98
124
|
|
|
99
|
-
|
|
125
|
+
Existing root lists, cascade nesting, and last-message root properties remain
|
|
126
|
+
unchanged.
|
|
100
127
|
|
|
101
|
-
|
|
128
|
+
## Inputs
|
|
102
129
|
|
|
103
|
-
|
|
130
|
+
Both parser methods accept:
|
|
104
131
|
|
|
105
|
-
|
|
132
|
+
- Node.js `Buffer`
|
|
133
|
+
- `ArrayBuffer`
|
|
106
134
|
|
|
107
|
-
|
|
135
|
+
```javascript
|
|
136
|
+
const parsed = await new FitParser().parseAsync(arrayBuffer)
|
|
137
|
+
```
|
|
108
138
|
|
|
109
|
-
|
|
139
|
+
## Developer fields
|
|
110
140
|
|
|
111
|
-
|
|
141
|
+
FIT producers may define custom fields outside the standard profile. The
|
|
142
|
+
parser consumes every developer field's declared byte size so later messages
|
|
143
|
+
stay aligned. If a field description is not available yet, that value is
|
|
144
|
+
omitted. Subsequent values are decoded by name once the description appears.
|
|
112
145
|
|
|
113
|
-
|
|
146
|
+
## Encoding
|
|
114
147
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
148
|
+
`FitEncoder` writes FIT headers, message definitions, data messages, and CRCs.
|
|
149
|
+
It is profile-agnostic: callers provide profile message and field numbers,
|
|
150
|
+
base types, sizes, and values in their raw FIT representation. Applying FIT
|
|
151
|
+
scales and offsets is the caller's responsibility.
|
|
118
152
|
|
|
119
|
-
|
|
153
|
+
```javascript
|
|
154
|
+
import { FitBaseType, FitEncoder } from 'fit-file-parser'
|
|
120
155
|
|
|
121
|
-
|
|
122
|
-
|
|
156
|
+
const encoder = new FitEncoder()
|
|
157
|
+
encoder.writeMessage(0, [
|
|
158
|
+
{
|
|
159
|
+
number: 0,
|
|
160
|
+
size: 1,
|
|
161
|
+
baseType: FitBaseType.Enum,
|
|
162
|
+
value: 6,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
number: 4,
|
|
166
|
+
size: 4,
|
|
167
|
+
baseType: FitBaseType.Uint32,
|
|
168
|
+
value: FitEncoder.toFitTimestamp(new Date()),
|
|
169
|
+
},
|
|
170
|
+
])
|
|
171
|
+
|
|
172
|
+
const fitBytes = encoder.close()
|
|
123
173
|
```
|
|
124
174
|
|
|
125
|
-
|
|
175
|
+
`writeMessage(globalMessageNumber, fields, localMessageNumber?)` accepts local
|
|
176
|
+
message numbers from 0 through 15. Definitions are emitted automatically and
|
|
177
|
+
reused until the shape assigned to that local number changes. `close()` returns
|
|
178
|
+
a `Uint8Array`.
|
|
126
179
|
|
|
127
|
-
|
|
128
|
-
npm run type-check
|
|
129
|
-
```
|
|
180
|
+
The encoder also provides:
|
|
130
181
|
|
|
131
|
-
|
|
182
|
+
- `FitEncoder.string(value)` for null-terminated UTF-8 field bytes.
|
|
183
|
+
- `FitEncoder.toFitTimestamp(value)` for FIT timestamps.
|
|
184
|
+
- `FitEncoder.calculateCRC(bytes)` for FIT-compatible CRC calculation.
|
|
132
185
|
|
|
133
|
-
|
|
186
|
+
Scalar 64-bit values use `bigint`. Strings, numeric arrays, and other
|
|
187
|
+
variable-length values use exact-size `Uint8Array` values. Invalid field
|
|
188
|
+
definitions or numeric ranges throw before a partial message is written.
|
|
134
189
|
|
|
135
|
-
|
|
190
|
+
## TypeScript and module formats
|
|
136
191
|
|
|
137
|
-
|
|
192
|
+
The package includes TypeScript declarations and exports
|
|
193
|
+
`FitParserOptions`, `FitEncoderField`, and `FitEncoderOptions`.
|
|
138
194
|
|
|
139
|
-
|
|
140
|
-
- `cascade`: Returned object is organized as a tree, eg. each lap contains a `records` fields, that is an array of its records (**default**)
|
|
141
|
-
- `list`: Returned object is organized as lists of sessions, laps, records, etc..., without parent-child relation
|
|
142
|
-
- `both`: A mix of the two other modes, eg. `records` are available inside the root field as well as inside each laps
|
|
143
|
-
- `lengthUnit`: String
|
|
144
|
-
- `m`: Lengths are in meters (**default**)
|
|
145
|
-
- `km`: Lengths are in kilometers
|
|
146
|
-
- `mi`: Lengths are in miles
|
|
147
|
-
- `temperatureUnit`: String
|
|
148
|
-
- `celsius`:Temperatures are in °C (**default**)
|
|
149
|
-
- `kelvin`: Temperatures are in °K
|
|
150
|
-
- `fahrenheit`: Temperatures are in °F
|
|
151
|
-
- `speedUnit`: String
|
|
152
|
-
- `m/s`: Speeds are in meters per seconds (**default**)
|
|
153
|
-
- `km/h`: Speeds are in kilometers per hour
|
|
154
|
-
- `mph`: Speeds are in miles per hour
|
|
155
|
-
- `force`: Boolean
|
|
156
|
-
- `true`: Continues even if they are errors (**default for now**)
|
|
157
|
-
- `false`: Stops if an error occurs
|
|
158
|
-
- `elapsedRecordField`: Boolean
|
|
159
|
-
- `true`: Includes `elapsed_time`, containing the elapsed time in seconds since the first record, and `timer_time`, containing the time shown on the device, inside each `record` field
|
|
160
|
-
- `false` (**default**)
|
|
195
|
+
ESM:
|
|
161
196
|
|
|
162
|
-
|
|
197
|
+
```javascript
|
|
198
|
+
import FitParser, { FitBaseType, FitEncoder } from 'fit-file-parser'
|
|
199
|
+
```
|
|
163
200
|
|
|
164
|
-
|
|
201
|
+
CommonJS:
|
|
165
202
|
|
|
166
|
-
|
|
203
|
+
```javascript
|
|
204
|
+
const {
|
|
205
|
+
default: FitParser,
|
|
206
|
+
FitBaseType,
|
|
207
|
+
FitEncoder,
|
|
208
|
+
} = require('fit-file-parser')
|
|
209
|
+
```
|
|
167
210
|
|
|
168
|
-
|
|
211
|
+
## Development
|
|
169
212
|
|
|
170
|
-
|
|
213
|
+
Run commands from the repository root.
|
|
214
|
+
|
|
215
|
+
| Command | Purpose |
|
|
216
|
+
| ---------------------------------- | -------------------------------------------------- |
|
|
217
|
+
| `npm ci` | Install locked dependencies. |
|
|
218
|
+
| `npm run build` | Build ESM and CommonJS output. |
|
|
219
|
+
| `npm test -- --run` | Run the complete test suite once. |
|
|
220
|
+
| `npm test -- --run test/<file>.ts` | Run a focused test file. |
|
|
221
|
+
| `npm run codegen` | Regenerate the Garmin profile and public types. |
|
|
222
|
+
| `npm run codegen:check` | Verify both generated files are current. |
|
|
223
|
+
| `npm run profile:audit` | Audit SDK profile coverage and private overlays. |
|
|
224
|
+
| `npm run corpus:check -- <path>` | Validate an external FIT corpus without file data. |
|
|
225
|
+
| `npm run lint` | Check lint and formatting rules. |
|
|
226
|
+
| `npm run fmt` | Apply the configured formatting rules. |
|
|
227
|
+
| `npm run type-check` | Run TypeScript without emitting files. |
|
|
228
|
+
| `npm run examples` | Build and regenerate checked-in example outputs. |
|
|
229
|
+
| `npm run check` | Run profile audit, lint, types, tests, and builds. |
|
|
230
|
+
|
|
231
|
+
Do not edit `src/garmin_profile.generated.ts` or `src/fit_types.ts` manually.
|
|
232
|
+
Update the pinned SDK, compatibility overrides, or a generator, then run
|
|
233
|
+
`npm run codegen`.
|
|
234
|
+
|
|
235
|
+
Repository-specific automation guidance is tracked in
|
|
236
|
+
[`.agent/README.md`](./.agent/README.md). More examples are available in the
|
|
237
|
+
[`examples`](./examples) directory, and release notes are in the
|
|
238
|
+
[`CHANGELOG`](./CHANGELOG.md).
|
|
171
239
|
|
|
172
|
-
|
|
240
|
+
## Contributors
|
|
173
241
|
|
|
174
|
-
|
|
175
|
-
|
|
242
|
+
This project started from work by
|
|
243
|
+
[Pierre Jacquier](https://github.com/pierremtb). Thanks to
|
|
244
|
+
[Mikael Lofjärd](https://github.com/mlofjard) for
|
|
245
|
+
[his early prototype](https://github.com/mlofjard/jsonfit), and to everyone in
|
|
246
|
+
[`CONTRIBUTORS.md`](./CONTRIBUTORS.md).
|
|
176
247
|
|
|
177
248
|
## License
|
|
178
249
|
|
|
179
|
-
MIT
|
|
250
|
+
MIT; see [`LICENSE`](./LICENSE).
|
|
180
251
|
|
|
181
|
-
|
|
252
|
+
Copyright 2019-present Dimitrios Kanellopoulos.
|
package/dist/binary.d.ts
CHANGED
|
@@ -17,8 +17,12 @@ export interface DeveloperFieldDefinition {
|
|
|
17
17
|
resolvedFieldDef?: FieldDefinition;
|
|
18
18
|
resolvedFrom?: unknown;
|
|
19
19
|
}
|
|
20
|
+
export interface DecoderState {
|
|
21
|
+
lastTimestamp?: number;
|
|
22
|
+
monitoringTimestamp?: number;
|
|
23
|
+
}
|
|
20
24
|
export declare function addEndian(littleEndian: boolean, bytes: number[]): number;
|
|
21
|
-
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView): {
|
|
25
|
+
export declare function readRecord(blob: Uint8Array, messageTypes: MessageTypeDefinition[], developerFields: any[], startIndex: number, options: FitParserOptions, startDate: number | undefined, pausedTime: number, dataView?: DataView, decoderState?: DecoderState): {
|
|
22
26
|
messageType: MesgNum | '';
|
|
23
27
|
nextIndex: number;
|
|
24
28
|
message?: any;
|