fit-file-parser 4.0.2 → 5.0.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 +91 -0
- package/README.md +273 -117
- package/dist/binary.d.ts +7 -4
- package/dist/binary.js +207 -163
- package/dist/cjs/binary.d.ts +7 -4
- package/dist/cjs/binary.js +207 -163
- package/dist/cjs/fit-parser.js +41 -21
- package/dist/cjs/fit.d.ts +15 -7
- package/dist/cjs/fit.js +137 -9078
- package/dist/cjs/fit_types.d.ts +1384 -298
- package/dist/cjs/garmin_profile.generated.d.ts +9 -0
- package/dist/cjs/garmin_profile.generated.js +17838 -0
- package/dist/cjs/helper.js +22 -12
- package/dist/cjs/messages.d.ts +2 -3
- package/dist/cjs/type_generator.d.ts +3 -0
- package/dist/cjs/type_generator.js +56 -27
- package/dist/fit-parser.js +41 -21
- package/dist/fit.d.ts +15 -7
- package/dist/fit.js +136 -9077
- package/dist/fit_types.d.ts +1384 -298
- package/dist/garmin_profile.generated.d.ts +9 -0
- package/dist/garmin_profile.generated.js +17835 -0
- package/dist/helper.js +22 -12
- package/dist/messages.d.ts +2 -3
- package/dist/type_generator.d.ts +3 -0
- package/dist/type_generator.js +55 -27
- package/package.json +9 -6
package/dist/helper.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
|
+
function timestampValue(value) {
|
|
2
|
+
if (value === undefined || value === null) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
const timestamp = value instanceof Date
|
|
6
|
+
? value.getTime()
|
|
7
|
+
: new Date(value).getTime();
|
|
8
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
9
|
+
}
|
|
1
10
|
export function mapDataIntoLap(inputLaps, lapKey, data) {
|
|
11
|
+
var _a;
|
|
2
12
|
const laps = [...inputLaps];
|
|
3
13
|
let index = 0;
|
|
4
14
|
for (let i = 0; i < laps.length; i++) {
|
|
5
15
|
const nextLap = laps[i + 1];
|
|
6
16
|
const tempData = [];
|
|
7
|
-
const nextLapStartTime = nextLap
|
|
8
|
-
? new Date(nextLap.start_time).getTime()
|
|
9
|
-
: null;
|
|
17
|
+
const nextLapStartTime = timestampValue(nextLap === null || nextLap === void 0 ? void 0 : nextLap.start_time);
|
|
10
18
|
for (let j = index; j < data.length; j++) {
|
|
11
19
|
const row = data[j];
|
|
12
|
-
if (nextLap && nextLapStartTime) {
|
|
13
|
-
const timestamp =
|
|
14
|
-
if (nextLapStartTime > timestamp) {
|
|
20
|
+
if (nextLap && nextLapStartTime !== undefined) {
|
|
21
|
+
const timestamp = timestampValue((_a = row.timestamp) !== null && _a !== void 0 ? _a : row.start_time);
|
|
22
|
+
if (timestamp === undefined || nextLapStartTime > timestamp) {
|
|
15
23
|
tempData.push(row);
|
|
24
|
+
index = j + 1;
|
|
16
25
|
}
|
|
17
26
|
else if (nextLapStartTime <= timestamp) {
|
|
18
27
|
index = j;
|
|
@@ -21,6 +30,7 @@ export function mapDataIntoLap(inputLaps, lapKey, data) {
|
|
|
21
30
|
}
|
|
22
31
|
else {
|
|
23
32
|
tempData.push(row);
|
|
33
|
+
index = j + 1;
|
|
24
34
|
}
|
|
25
35
|
}
|
|
26
36
|
if (!laps[i][lapKey]) {
|
|
@@ -35,15 +45,14 @@ export function mapDataIntoSession(inputSessions, laps) {
|
|
|
35
45
|
for (let i = 0; i < sessions.length; i++) {
|
|
36
46
|
const nextSession = sessions[i + 1];
|
|
37
47
|
const tempLaps = [];
|
|
38
|
-
const nextSessionStartTime = nextSession
|
|
39
|
-
? new Date(nextSession.start_time).getTime()
|
|
40
|
-
: null;
|
|
48
|
+
const nextSessionStartTime = timestampValue(nextSession === null || nextSession === void 0 ? void 0 : nextSession.start_time);
|
|
41
49
|
for (let j = lapIndex; j < laps.length; j++) {
|
|
42
50
|
const lap = laps[j];
|
|
43
|
-
if (nextSession && nextSessionStartTime) {
|
|
44
|
-
const lapStartTime =
|
|
45
|
-
if (nextSessionStartTime > lapStartTime) {
|
|
51
|
+
if (nextSession && nextSessionStartTime !== undefined) {
|
|
52
|
+
const lapStartTime = timestampValue(lap.start_time);
|
|
53
|
+
if (lapStartTime === undefined || nextSessionStartTime > lapStartTime) {
|
|
46
54
|
tempLaps.push(lap);
|
|
55
|
+
lapIndex = j + 1;
|
|
47
56
|
}
|
|
48
57
|
else if (nextSessionStartTime <= lapStartTime) {
|
|
49
58
|
lapIndex = j;
|
|
@@ -52,6 +61,7 @@ export function mapDataIntoSession(inputSessions, laps) {
|
|
|
52
61
|
}
|
|
53
62
|
else {
|
|
54
63
|
tempLaps.push(lap);
|
|
64
|
+
lapIndex = j + 1;
|
|
55
65
|
}
|
|
56
66
|
}
|
|
57
67
|
if (!sessions[i].laps) {
|
package/dist/messages.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { MessageObject } from './fit.js';
|
|
2
|
-
import type { MesgNum } from './fit_types.js';
|
|
1
|
+
import type { MessageName, MessageObject } from './fit.js';
|
|
3
2
|
export declare function getFitMessage(messageNum: number): {
|
|
4
|
-
name:
|
|
3
|
+
name: MessageName | '';
|
|
5
4
|
getAttributes: (fieldNum: number) => MessageObject;
|
|
6
5
|
};
|
|
7
6
|
export declare function getFitMessageBaseType(inp: any): any;
|
package/dist/type_generator.d.ts
CHANGED
|
@@ -19,6 +19,9 @@ export declare function generateOptions(options: {
|
|
|
19
19
|
}): ts.Node[];
|
|
20
20
|
export declare function generateUtilities(): Statement[];
|
|
21
21
|
export declare function generateFitType(): Statement;
|
|
22
|
+
export declare function generateParsedMessages(messages: {
|
|
23
|
+
[messageId: number]: Message;
|
|
24
|
+
}): Statement;
|
|
22
25
|
export declare function generateMessages(messages: {
|
|
23
26
|
[messageId: number]: Message;
|
|
24
27
|
}): Statement[];
|
package/dist/type_generator.js
CHANGED
|
@@ -38,7 +38,16 @@ export function generateProperty(name, type, optional = true) {
|
|
|
38
38
|
export function generateArrayProperty(name, type, optional = true) {
|
|
39
39
|
return generateProperty(name, ts.factory.createArrayTypeNode(type), optional);
|
|
40
40
|
}
|
|
41
|
+
function generateNullableArrayType(type) {
|
|
42
|
+
return ts.factory.createArrayTypeNode(ts.factory.createParenthesizedType(ts.factory.createUnionTypeNode([
|
|
43
|
+
type,
|
|
44
|
+
ts.factory.createLiteralTypeNode(ts.factory.createNull()),
|
|
45
|
+
])));
|
|
46
|
+
}
|
|
41
47
|
export function generateTypeFromField(def) {
|
|
48
|
+
if (def.array === true) {
|
|
49
|
+
return generateNullableArrayType(generateTypeFromField(Object.assign(Object.assign({}, def), { array: false })));
|
|
50
|
+
}
|
|
42
51
|
switch (def.type) {
|
|
43
52
|
case 'uint32_array':
|
|
44
53
|
case 'uint16_array':
|
|
@@ -47,12 +56,15 @@ export function generateTypeFromField(def) {
|
|
|
47
56
|
case 'sint16_array':
|
|
48
57
|
case 'sint8_array':
|
|
49
58
|
case 'byte_array':
|
|
50
|
-
return
|
|
51
|
-
case '
|
|
52
|
-
return ts.factory.
|
|
59
|
+
return generateNullableArrayType(ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
|
|
60
|
+
case 'exercise_category_array':
|
|
61
|
+
return generateNullableArrayType(ts.factory.createTypeReferenceNode('ExerciseCategory'));
|
|
53
62
|
case 'date_time':
|
|
63
|
+
case 'local_date_time':
|
|
64
|
+
return ts.factory.createTypeReferenceNode('Date');
|
|
54
65
|
case 'string':
|
|
55
66
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
67
|
+
case 'bool':
|
|
56
68
|
case 'uint32':
|
|
57
69
|
case 'uint64':
|
|
58
70
|
case 'uint16':
|
|
@@ -82,18 +94,30 @@ export function generateTypes(types) {
|
|
|
82
94
|
const nodes = [];
|
|
83
95
|
const typeNames = Object.keys(types);
|
|
84
96
|
typeNames.forEach((name) => {
|
|
85
|
-
if (name === 'message_index') {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
97
|
const type = types[name];
|
|
89
|
-
const names = Object.values(type);
|
|
98
|
+
const names = [...new Set(Object.values(type))];
|
|
99
|
+
const isMask = names.includes('mask');
|
|
100
|
+
const typeNode = isMask
|
|
101
|
+
? ts.factory.createTypeLiteralNode([
|
|
102
|
+
generateProperty('value', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
103
|
+
...names
|
|
104
|
+
.filter(value => value !== 'mask')
|
|
105
|
+
.map(value => generateProperty(String(value), ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), false)),
|
|
106
|
+
])
|
|
107
|
+
: names.length === 0
|
|
108
|
+
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
|
|
109
|
+
: ts.factory.createUnionTypeNode([
|
|
110
|
+
...names.map(n => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(String(n)))),
|
|
111
|
+
...(name === 'mesg_num'
|
|
112
|
+
? [
|
|
113
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('definition')),
|
|
114
|
+
]
|
|
115
|
+
: []),
|
|
116
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
|
|
117
|
+
]);
|
|
90
118
|
const typeAlias = ts.factory.createTypeAliasDeclaration([
|
|
91
119
|
ts.factory.createModifier(ts.SyntaxKind.ExportKeyword),
|
|
92
|
-
], snakeToCamel(name), undefined,
|
|
93
|
-
? [
|
|
94
|
-
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral('definition')),
|
|
95
|
-
]
|
|
96
|
-
: [])]));
|
|
120
|
+
], snakeToCamel(name), undefined, typeNode);
|
|
97
121
|
nodes.push(typeAlias);
|
|
98
122
|
});
|
|
99
123
|
return nodes;
|
|
@@ -130,13 +154,6 @@ export function generateUtilities() {
|
|
|
130
154
|
]),
|
|
131
155
|
]));
|
|
132
156
|
nodes.push(unitType);
|
|
133
|
-
const messageIndex = ts.factory.createInterfaceDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], 'MessageIndex', undefined, undefined, [
|
|
134
|
-
generateProperty('0', ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), false),
|
|
135
|
-
generateProperty('value', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword), false),
|
|
136
|
-
generateProperty('reserved', ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), false),
|
|
137
|
-
generateProperty('selected', ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword), false),
|
|
138
|
-
]);
|
|
139
|
-
nodes.push(messageIndex);
|
|
140
157
|
return nodes;
|
|
141
158
|
}
|
|
142
159
|
function generateAdditionalFields(msg) {
|
|
@@ -160,6 +177,7 @@ function generateAdditionalFields(msg) {
|
|
|
160
177
|
developer_data_ids: 'parsed_developer_data_id',
|
|
161
178
|
field_descriptions: 'parsed_field_description',
|
|
162
179
|
sports: 'parsed_sport',
|
|
180
|
+
sets: 'parsed_set',
|
|
163
181
|
splits: 'parsed_split',
|
|
164
182
|
split_summaries: 'parsed_split_summary',
|
|
165
183
|
};
|
|
@@ -191,6 +209,7 @@ export function generateFitType() {
|
|
|
191
209
|
tank_updates: 'parsed_tank_update',
|
|
192
210
|
tank_summaries: 'parsed_tank_summary',
|
|
193
211
|
jumps: 'parsed_jump',
|
|
212
|
+
sets: 'parsed_set',
|
|
194
213
|
splits: 'parsed_split',
|
|
195
214
|
split_summaries: 'parsed_split_summary',
|
|
196
215
|
time_in_zone: 'parsed_time_in_zone',
|
|
@@ -207,31 +226,38 @@ export function generateFitType() {
|
|
|
207
226
|
activity: 'parsed_activity',
|
|
208
227
|
zones_target: '?parsed_zones_target',
|
|
209
228
|
};
|
|
229
|
+
const existingMessageNames = new Set([
|
|
230
|
+
...Object.values(collectionProperties),
|
|
231
|
+
...Object.values(referenceProperties),
|
|
232
|
+
].map(name => name.replace('?', '').replace(/^parsed_/, '')));
|
|
233
|
+
const additionalReferenceProperties = Object.values(FIT.messages)
|
|
234
|
+
.filter(message => !existingMessageNames.has(message.name))
|
|
235
|
+
.map(message => generateProperty(message.name, ts.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`))));
|
|
210
236
|
return ts.factory.createInterfaceDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], 'ParsedFit', undefined, undefined, [
|
|
211
237
|
generateProperty('protocolVersion', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
212
238
|
generateProperty('profileVersion', ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)),
|
|
239
|
+
generateProperty('messages', ts.factory.createTypeReferenceNode('ParsedMessages')),
|
|
213
240
|
...Object.keys(referenceProperties).map(prop => generateProperty(prop, ts.factory.createTypeReferenceNode(snakeToCamel(referenceProperties[prop].replace('?', ''))), referenceProperties[prop].startsWith('?'))),
|
|
214
241
|
...Object.keys(collectionProperties).map(prop => generateArrayProperty(prop, collectionProperties[prop] === 'unknown'
|
|
215
242
|
? ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
|
|
216
243
|
: ts.factory.createTypeReferenceNode(snakeToCamel(collectionProperties[prop])))),
|
|
244
|
+
...additionalReferenceProperties,
|
|
217
245
|
]);
|
|
218
246
|
}
|
|
247
|
+
export function generateParsedMessages(messages) {
|
|
248
|
+
return ts.factory.createInterfaceDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], 'ParsedMessages', undefined, undefined, Object.values(messages).map(message => generateArrayProperty(message.name, ts.factory.createTypeReferenceNode(snakeToCamel(`parsed_${message.name}`)))));
|
|
249
|
+
}
|
|
219
250
|
export function generateMessages(messages) {
|
|
220
251
|
const nodes = [];
|
|
221
252
|
Object.keys(messages).forEach((name) => {
|
|
222
253
|
const msg = FIT.messages[Number(name)];
|
|
223
|
-
const usedFields = new Set();
|
|
224
254
|
const messageType = ts.factory.createInterfaceDeclaration([
|
|
225
255
|
ts.factory.createModifier(ts.SyntaxKind.ExportKeyword),
|
|
226
256
|
], snakeToCamel(`parsed_${msg.name}`), undefined, undefined, [
|
|
227
|
-
...Object.keys(msg).filter(n => n !== 'name').
|
|
257
|
+
...Object.keys(msg).filter(n => n !== 'name').map((id) => {
|
|
228
258
|
const def = msg[Number(id)];
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
acc.push(generateProperty(def.field, generateTypeFromField(def), !['start_time', 'timestamp'].includes(def.field)));
|
|
232
|
-
}
|
|
233
|
-
return acc;
|
|
234
|
-
}, []),
|
|
259
|
+
return generateProperty(def.field, generateTypeFromField(def));
|
|
260
|
+
}),
|
|
235
261
|
...generateAdditionalFields(msg),
|
|
236
262
|
]);
|
|
237
263
|
nodes.push(messageType);
|
|
@@ -251,6 +277,8 @@ export function main() {
|
|
|
251
277
|
nodes.push(...generateOptions(FIT.options));
|
|
252
278
|
nodes.push(...comment('parsed from Fit.messages'));
|
|
253
279
|
nodes.push(...generateMessages(FIT.messages));
|
|
280
|
+
nodes.push(...comment('all recognized messages, retained in file order'));
|
|
281
|
+
nodes.push(generateParsedMessages(FIT.messages));
|
|
254
282
|
nodes.push(...comment('the returned type after parsing a .fit file'));
|
|
255
283
|
nodes.push(generateFitType());
|
|
256
284
|
const nodesArray = ts.factory.createNodeArray(nodes);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fit-file-parser",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "5.0.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Parse your .FIT files easily, directly from JS (Garmin, Polar, Suunto)",
|
|
7
7
|
"author": {
|
|
@@ -54,10 +54,13 @@
|
|
|
54
54
|
}
|
|
55
55
|
],
|
|
56
56
|
"scripts": {
|
|
57
|
-
"dev": "tsx --watch --watch-path=src/type_generator.ts --watch-path=src/fit.ts codegen/codegen.ts",
|
|
58
|
-
"codegen": "tsx codegen/
|
|
59
|
-
"codegen:
|
|
60
|
-
"check": "
|
|
57
|
+
"dev": "tsx --watch --watch-path=src/type_generator.ts --watch-path=src/fit.ts --watch-path=src/garmin_profile.generated.ts codegen/codegen.ts",
|
|
58
|
+
"codegen:profile": "tsx codegen/garmin-profile.ts",
|
|
59
|
+
"codegen": "npm run codegen:profile && tsx codegen/codegen.ts",
|
|
60
|
+
"codegen:check": "tsx codegen/garmin-profile.ts --check && tsx codegen/codegen.ts --check",
|
|
61
|
+
"profile:audit": "npm run codegen:check && tsx scripts/profile-audit.ts",
|
|
62
|
+
"corpus:check": "tsx scripts/corpus-check.ts",
|
|
63
|
+
"check": "npm run profile:audit && npm run lint && npm run type-check && npm test -- --run && npm run build",
|
|
61
64
|
"example-output": "node examples/parse.js examples/example.fit > examples/output-example.json",
|
|
62
65
|
"example-output-tank": "node examples/parse.js examples/example-diving.fit > examples/output-diving.json",
|
|
63
66
|
"example-output-summary-first": "node examples/parse.js examples/triathlon_summary_first.fit > examples/output_summary_first.json",
|
|
@@ -77,7 +80,7 @@
|
|
|
77
80
|
},
|
|
78
81
|
"devDependencies": {
|
|
79
82
|
"@antfu/eslint-config": "^6.2.0",
|
|
80
|
-
"@garmin/fitsdk": "
|
|
83
|
+
"@garmin/fitsdk": "21.208.0",
|
|
81
84
|
"@types/node": "^24.10.1",
|
|
82
85
|
"@vitest/coverage-v8": "^4.0.13",
|
|
83
86
|
"eslint": "^9.10.0",
|