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/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 = new Date(row.timestamp || row.start_time).getTime();
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 = new Date(lap.start_time).getTime();
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) {
@@ -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: MesgNum | '';
3
+ name: MessageName | '';
5
4
  getAttributes: (fieldNum: number) => MessageObject;
6
5
  };
7
6
  export declare function getFitMessageBaseType(inp: any): any;
@@ -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[];
@@ -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 ts.factory.createArrayTypeNode(ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword));
51
- case 'bool':
52
- return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
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, ts.factory.createUnionTypeNode([...names.map(n => ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral(String(n)))), ...(name === 'mesg_num'
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').reduce((acc, id) => {
257
+ ...Object.keys(msg).filter(n => n !== 'name').map((id) => {
228
258
  const def = msg[Number(id)];
229
- if (!usedFields.has(def.field)) {
230
- usedFields.add(def.field);
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.0.2",
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/codegen.ts",
59
- "codegen:check": "tsx codegen/codegen.ts --check",
60
- "check": "npm run codegen:check && npm run lint && npm run type-check && npm test -- --run && npm run build",
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": "^21.208.0",
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",