@kaiord/fit 4.0.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/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +1954 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1954 @@
|
|
|
1
|
+
import { sportSchema, durationTypeSchema, createFitParsingError, createConsoleLogger, fileTypeSchema, workoutSchema, convertLengthToMeters, subSportSchema, lengthUnitSchema, FIT_TO_SWIM_STROKE, equipmentSchema, targetTypeSchema, targetUnitSchema, intensitySchema } from '@kaiord/core';
|
|
2
|
+
import { Stream, Decoder, Encoder, Profile } from '@garmin/fitsdk';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
|
|
5
|
+
// src/providers.ts
|
|
6
|
+
|
|
7
|
+
// src/adapters/shared/message-numbers.ts
|
|
8
|
+
var FIT_MESSAGE_NUMBERS = {
|
|
9
|
+
FILE_ID: 0,
|
|
10
|
+
WORKOUT: 26,
|
|
11
|
+
WORKOUT_STEP: 27
|
|
12
|
+
};
|
|
13
|
+
z.enum([
|
|
14
|
+
"device",
|
|
15
|
+
"settings",
|
|
16
|
+
"sport",
|
|
17
|
+
"activity",
|
|
18
|
+
"workout",
|
|
19
|
+
"course",
|
|
20
|
+
"schedules",
|
|
21
|
+
"weight",
|
|
22
|
+
"totals",
|
|
23
|
+
"goals",
|
|
24
|
+
"bloodPressure",
|
|
25
|
+
"monitoringA",
|
|
26
|
+
"activitySummary",
|
|
27
|
+
"monitoringDaily",
|
|
28
|
+
"monitoringB",
|
|
29
|
+
"segment",
|
|
30
|
+
"segmentList",
|
|
31
|
+
"exdConfiguration"
|
|
32
|
+
]);
|
|
33
|
+
var FIT_FILE_TYPE_TO_NUMBER = {
|
|
34
|
+
device: 1,
|
|
35
|
+
settings: 2,
|
|
36
|
+
sport: 3,
|
|
37
|
+
activity: 4,
|
|
38
|
+
workout: 5,
|
|
39
|
+
course: 6,
|
|
40
|
+
schedules: 7,
|
|
41
|
+
weight: 9,
|
|
42
|
+
totals: 10,
|
|
43
|
+
goals: 11,
|
|
44
|
+
bloodPressure: 14,
|
|
45
|
+
monitoringA: 15,
|
|
46
|
+
activitySummary: 20,
|
|
47
|
+
monitoringDaily: 28,
|
|
48
|
+
monitoringB: 32,
|
|
49
|
+
segment: 34,
|
|
50
|
+
segmentList: 35,
|
|
51
|
+
exdConfiguration: 40
|
|
52
|
+
};
|
|
53
|
+
var isRepetitionBlock = (step) => {
|
|
54
|
+
return "repeatCount" in step;
|
|
55
|
+
};
|
|
56
|
+
var DEFAULT_SPORT = sportSchema.enum.cycling;
|
|
57
|
+
var mapSportType = (fitSport) => {
|
|
58
|
+
if (!fitSport) {
|
|
59
|
+
return DEFAULT_SPORT;
|
|
60
|
+
}
|
|
61
|
+
return fitSport.toLowerCase();
|
|
62
|
+
};
|
|
63
|
+
var fitSubSportSchema = z.enum([
|
|
64
|
+
"generic",
|
|
65
|
+
"treadmill",
|
|
66
|
+
"street",
|
|
67
|
+
"trail",
|
|
68
|
+
"track",
|
|
69
|
+
"spin",
|
|
70
|
+
"indoorCycling",
|
|
71
|
+
"road",
|
|
72
|
+
"mountain",
|
|
73
|
+
"downhill",
|
|
74
|
+
"recumbent",
|
|
75
|
+
"cyclocross",
|
|
76
|
+
"handCycling",
|
|
77
|
+
"trackCycling",
|
|
78
|
+
"indoorRowing",
|
|
79
|
+
"elliptical",
|
|
80
|
+
"stairClimbing",
|
|
81
|
+
"lapSwimming",
|
|
82
|
+
"openWater",
|
|
83
|
+
"flexibilityTraining",
|
|
84
|
+
"strengthTraining",
|
|
85
|
+
"warmUp",
|
|
86
|
+
"match",
|
|
87
|
+
"exercise",
|
|
88
|
+
"challenge",
|
|
89
|
+
"indoorSkiing",
|
|
90
|
+
"cardioTraining",
|
|
91
|
+
"indoorWalking",
|
|
92
|
+
"eBikeFitness",
|
|
93
|
+
"bmx",
|
|
94
|
+
"casualWalking",
|
|
95
|
+
"speedWalking",
|
|
96
|
+
"bikeToRunTransition",
|
|
97
|
+
"runToBikeTransition",
|
|
98
|
+
"swimToBikeTransition",
|
|
99
|
+
"atv",
|
|
100
|
+
"motocross",
|
|
101
|
+
"backcountry",
|
|
102
|
+
"resort",
|
|
103
|
+
"rcDrone",
|
|
104
|
+
"wingsuit",
|
|
105
|
+
"whitewater",
|
|
106
|
+
"skateSkiing",
|
|
107
|
+
"yoga",
|
|
108
|
+
"pilates",
|
|
109
|
+
"indoorRunning",
|
|
110
|
+
"gravelCycling",
|
|
111
|
+
"eBikeMountain",
|
|
112
|
+
"commuting",
|
|
113
|
+
"mixedSurface",
|
|
114
|
+
"navigate",
|
|
115
|
+
"trackMe",
|
|
116
|
+
"map",
|
|
117
|
+
"singleGasDiving",
|
|
118
|
+
"multiGasDiving",
|
|
119
|
+
"gaugeDiving",
|
|
120
|
+
"apneaDiving",
|
|
121
|
+
"apneaHunting",
|
|
122
|
+
"virtualActivity",
|
|
123
|
+
"obstacle",
|
|
124
|
+
"all"
|
|
125
|
+
]);
|
|
126
|
+
|
|
127
|
+
// src/adapters/sub-sport/sub-sport.mapper.ts
|
|
128
|
+
var FIT_TO_KRD_SUB_SPORT_MAP = {
|
|
129
|
+
generic: "generic",
|
|
130
|
+
treadmill: "treadmill",
|
|
131
|
+
street: "street",
|
|
132
|
+
trail: "trail",
|
|
133
|
+
track: "track",
|
|
134
|
+
spin: "spin",
|
|
135
|
+
indoorCycling: "indoor_cycling",
|
|
136
|
+
road: "road",
|
|
137
|
+
mountain: "mountain",
|
|
138
|
+
downhill: "downhill",
|
|
139
|
+
recumbent: "recumbent",
|
|
140
|
+
cyclocross: "cyclocross",
|
|
141
|
+
handCycling: "hand_cycling",
|
|
142
|
+
trackCycling: "track_cycling",
|
|
143
|
+
indoorRowing: "indoor_rowing",
|
|
144
|
+
elliptical: "elliptical",
|
|
145
|
+
stairClimbing: "stair_climbing",
|
|
146
|
+
lapSwimming: "lap_swimming",
|
|
147
|
+
openWater: "open_water",
|
|
148
|
+
flexibilityTraining: "flexibility_training",
|
|
149
|
+
strengthTraining: "strength_training",
|
|
150
|
+
warmUp: "warm_up",
|
|
151
|
+
match: "match",
|
|
152
|
+
exercise: "exercise",
|
|
153
|
+
challenge: "challenge",
|
|
154
|
+
indoorSkiing: "indoor_skiing",
|
|
155
|
+
cardioTraining: "cardio_training",
|
|
156
|
+
indoorWalking: "indoor_walking",
|
|
157
|
+
eBikeFitness: "e_bike_fitness",
|
|
158
|
+
bmx: "bmx",
|
|
159
|
+
casualWalking: "casual_walking",
|
|
160
|
+
speedWalking: "speed_walking",
|
|
161
|
+
bikeToRunTransition: "bike_to_run_transition",
|
|
162
|
+
runToBikeTransition: "run_to_bike_transition",
|
|
163
|
+
swimToBikeTransition: "swim_to_bike_transition",
|
|
164
|
+
atv: "atv",
|
|
165
|
+
motocross: "motocross",
|
|
166
|
+
backcountry: "backcountry",
|
|
167
|
+
resort: "resort",
|
|
168
|
+
rcDrone: "rc_drone",
|
|
169
|
+
wingsuit: "wingsuit",
|
|
170
|
+
whitewater: "whitewater",
|
|
171
|
+
skateSkiing: "skate_skiing",
|
|
172
|
+
yoga: "yoga",
|
|
173
|
+
pilates: "pilates",
|
|
174
|
+
indoorRunning: "indoor_running",
|
|
175
|
+
gravelCycling: "gravel_cycling",
|
|
176
|
+
eBikeMountain: "e_bike_mountain",
|
|
177
|
+
commuting: "commuting",
|
|
178
|
+
mixedSurface: "mixed_surface",
|
|
179
|
+
navigate: "navigate",
|
|
180
|
+
trackMe: "track_me",
|
|
181
|
+
map: "map",
|
|
182
|
+
singleGasDiving: "single_gas_diving",
|
|
183
|
+
multiGasDiving: "multi_gas_diving",
|
|
184
|
+
gaugeDiving: "gauge_diving",
|
|
185
|
+
apneaDiving: "apnea_diving",
|
|
186
|
+
apneaHunting: "apnea_hunting",
|
|
187
|
+
virtualActivity: "virtual_activity",
|
|
188
|
+
obstacle: "obstacle",
|
|
189
|
+
all: "all"
|
|
190
|
+
};
|
|
191
|
+
var KRD_TO_FIT_SUB_SPORT_MAP = Object.fromEntries(
|
|
192
|
+
Object.entries(FIT_TO_KRD_SUB_SPORT_MAP).map(([fit, krd]) => [krd, fit])
|
|
193
|
+
);
|
|
194
|
+
var mapSubSportToKrd = (fitSubSport) => {
|
|
195
|
+
const result = fitSubSportSchema.safeParse(fitSubSport);
|
|
196
|
+
if (!result.success) {
|
|
197
|
+
return subSportSchema.enum.generic;
|
|
198
|
+
}
|
|
199
|
+
return FIT_TO_KRD_SUB_SPORT_MAP[result.data] || subSportSchema.enum.generic;
|
|
200
|
+
};
|
|
201
|
+
var mapSubSportToFit = (krdSubSport) => {
|
|
202
|
+
const result = subSportSchema.safeParse(krdSubSport);
|
|
203
|
+
if (!result.success) {
|
|
204
|
+
return fitSubSportSchema.enum.generic;
|
|
205
|
+
}
|
|
206
|
+
return KRD_TO_FIT_SUB_SPORT_MAP[result.data] || fitSubSportSchema.enum.generic;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// src/adapters/krd-to-fit/krd-to-fit-metadata.mapper.ts
|
|
210
|
+
var DEFAULT_MANUFACTURER = "garmin";
|
|
211
|
+
var mapManufacturer = (manufacturer, logger) => {
|
|
212
|
+
if (!manufacturer) {
|
|
213
|
+
return DEFAULT_MANUFACTURER;
|
|
214
|
+
}
|
|
215
|
+
const manufacturerEnum = Profile.types.manufacturer;
|
|
216
|
+
const manufacturerValues = Object.values(manufacturerEnum);
|
|
217
|
+
const normalized = manufacturer.toLowerCase();
|
|
218
|
+
const matched = manufacturerValues.find(
|
|
219
|
+
(value) => value.toLowerCase() === normalized || value.toLowerCase().startsWith(normalized) || normalized.startsWith(value.toLowerCase())
|
|
220
|
+
);
|
|
221
|
+
if (matched) return matched;
|
|
222
|
+
logger.warn(
|
|
223
|
+
`Unknown manufacturer "${manufacturer}", using fallback "${DEFAULT_MANUFACTURER}"`,
|
|
224
|
+
{ original: manufacturer, fallback: DEFAULT_MANUFACTURER }
|
|
225
|
+
);
|
|
226
|
+
return DEFAULT_MANUFACTURER;
|
|
227
|
+
};
|
|
228
|
+
var convertMetadataToFileId = (krd, logger) => {
|
|
229
|
+
logger.debug("Converting metadata to file_id message");
|
|
230
|
+
const fileType = krd.type === "structured_workout" ? "workout" : krd.type === "recorded_activity" ? "activity" : krd.type === "course" ? "course" : "workout";
|
|
231
|
+
const fileId = {
|
|
232
|
+
type: FIT_FILE_TYPE_TO_NUMBER[fileType] ?? FIT_FILE_TYPE_TO_NUMBER.workout,
|
|
233
|
+
timeCreated: new Date(krd.metadata.created),
|
|
234
|
+
manufacturer: mapManufacturer(krd.metadata.manufacturer, logger)
|
|
235
|
+
};
|
|
236
|
+
if (krd.metadata.product !== void 0) {
|
|
237
|
+
const productNumber = parseInt(krd.metadata.product, 10);
|
|
238
|
+
if (!isNaN(productNumber)) {
|
|
239
|
+
fileId.product = productNumber;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (krd.metadata.serialNumber) {
|
|
243
|
+
const serialNumber = parseInt(krd.metadata.serialNumber, 10);
|
|
244
|
+
if (!isNaN(serialNumber)) {
|
|
245
|
+
fileId.serialNumber = serialNumber;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return fileId;
|
|
249
|
+
};
|
|
250
|
+
var convertWorkoutMetadata = (workout, logger) => {
|
|
251
|
+
logger.debug("Converting workout metadata");
|
|
252
|
+
const numValidSteps = countValidSteps(workout.steps);
|
|
253
|
+
const workoutMesg = {
|
|
254
|
+
wktName: workout.name,
|
|
255
|
+
sport: workout.sport,
|
|
256
|
+
numValidSteps
|
|
257
|
+
};
|
|
258
|
+
if (workout.subSport !== void 0) {
|
|
259
|
+
workoutMesg.subSport = mapSubSportToFit(workout.subSport);
|
|
260
|
+
}
|
|
261
|
+
if (workout.poolLength !== void 0) {
|
|
262
|
+
workoutMesg.poolLength = workout.poolLength;
|
|
263
|
+
workoutMesg.poolLengthUnit = 0;
|
|
264
|
+
}
|
|
265
|
+
return workoutMesg;
|
|
266
|
+
};
|
|
267
|
+
var countValidSteps = (steps) => {
|
|
268
|
+
let count = 0;
|
|
269
|
+
for (const step of steps) {
|
|
270
|
+
if (isRepetitionBlock(step)) {
|
|
271
|
+
count += step.steps.length + 1;
|
|
272
|
+
} else {
|
|
273
|
+
count += 1;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return count;
|
|
277
|
+
};
|
|
278
|
+
var fitDurationTypeSchema = z.enum([
|
|
279
|
+
"time",
|
|
280
|
+
"distance",
|
|
281
|
+
"repeatUntilStepsCmplt",
|
|
282
|
+
"repeatUntilHrGreaterThan",
|
|
283
|
+
"hrLessThan",
|
|
284
|
+
"hrGreaterThan",
|
|
285
|
+
"calories",
|
|
286
|
+
"powerLessThan",
|
|
287
|
+
"powerGreaterThan",
|
|
288
|
+
"repeatUntilTime",
|
|
289
|
+
"repeatUntilDistance",
|
|
290
|
+
"repeatUntilCalories",
|
|
291
|
+
"repeatUntilHrLessThan",
|
|
292
|
+
"repeatUntilPowerLessThan",
|
|
293
|
+
"repeatUntilPowerGreaterThan",
|
|
294
|
+
"open"
|
|
295
|
+
]);
|
|
296
|
+
var fitTargetTypeSchema = z.enum([
|
|
297
|
+
"power",
|
|
298
|
+
"heartRate",
|
|
299
|
+
"cadence",
|
|
300
|
+
"speed",
|
|
301
|
+
"swimStroke",
|
|
302
|
+
"open"
|
|
303
|
+
]);
|
|
304
|
+
var fitEquipmentSchema = z.enum([
|
|
305
|
+
"none",
|
|
306
|
+
"swimFins",
|
|
307
|
+
"swimKickboard",
|
|
308
|
+
"swimPaddles",
|
|
309
|
+
"swimPullBuoy",
|
|
310
|
+
"swimSnorkel"
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
// src/adapters/equipment/equipment.mapper.ts
|
|
314
|
+
var FIT_TO_KRD_EQUIPMENT_MAP = {
|
|
315
|
+
none: "none",
|
|
316
|
+
swimFins: "swim_fins",
|
|
317
|
+
swimKickboard: "swim_kickboard",
|
|
318
|
+
swimPaddles: "swim_paddles",
|
|
319
|
+
swimPullBuoy: "swim_pull_buoy",
|
|
320
|
+
swimSnorkel: "swim_snorkel"
|
|
321
|
+
};
|
|
322
|
+
var KRD_TO_FIT_EQUIPMENT_MAP = Object.fromEntries(
|
|
323
|
+
Object.entries(FIT_TO_KRD_EQUIPMENT_MAP).map(([fit, krd]) => [krd, fit])
|
|
324
|
+
);
|
|
325
|
+
var mapEquipmentToKrd = (fitEquipment) => {
|
|
326
|
+
const result = fitEquipmentSchema.safeParse(fitEquipment);
|
|
327
|
+
if (!result.success) {
|
|
328
|
+
return equipmentSchema.enum.none;
|
|
329
|
+
}
|
|
330
|
+
return FIT_TO_KRD_EQUIPMENT_MAP[result.data] || equipmentSchema.enum.none;
|
|
331
|
+
};
|
|
332
|
+
var mapEquipmentToFit = (krdEquipment) => {
|
|
333
|
+
const result = equipmentSchema.safeParse(krdEquipment);
|
|
334
|
+
if (!result.success) {
|
|
335
|
+
return fitEquipmentSchema.enum.none;
|
|
336
|
+
}
|
|
337
|
+
return KRD_TO_FIT_EQUIPMENT_MAP[result.data] || fitEquipmentSchema.enum.none;
|
|
338
|
+
};
|
|
339
|
+
var convertConditionalDuration = (duration, message) => {
|
|
340
|
+
if (duration.type === durationTypeSchema.enum.heart_rate_less_than) {
|
|
341
|
+
message.durationType = fitDurationTypeSchema.enum.hrLessThan;
|
|
342
|
+
message.durationHr = duration.bpm;
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
if (duration.type === durationTypeSchema.enum.power_less_than) {
|
|
346
|
+
message.durationType = fitDurationTypeSchema.enum.powerLessThan;
|
|
347
|
+
message.durationPower = duration.watts;
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
if (duration.type === durationTypeSchema.enum.power_greater_than) {
|
|
351
|
+
message.durationType = fitDurationTypeSchema.enum.powerGreaterThan;
|
|
352
|
+
message.durationPower = duration.watts;
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return false;
|
|
356
|
+
};
|
|
357
|
+
var convertRepeatDuration = (duration, message) => {
|
|
358
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_time) {
|
|
359
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilTime;
|
|
360
|
+
message.durationTime = duration.seconds;
|
|
361
|
+
message.durationStep = duration.repeatFrom;
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_distance) {
|
|
365
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilDistance;
|
|
366
|
+
message.durationDistance = duration.meters;
|
|
367
|
+
message.durationStep = duration.repeatFrom;
|
|
368
|
+
return true;
|
|
369
|
+
}
|
|
370
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_calories) {
|
|
371
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilCalories;
|
|
372
|
+
message.durationCalories = duration.calories;
|
|
373
|
+
message.durationStep = duration.repeatFrom;
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
};
|
|
378
|
+
var convertRepeatHrPowerDuration = (duration, message) => {
|
|
379
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_heart_rate_greater_than) {
|
|
380
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilHrGreaterThan;
|
|
381
|
+
message.durationHr = duration.bpm;
|
|
382
|
+
message.durationStep = duration.repeatFrom;
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_heart_rate_less_than) {
|
|
386
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilHrLessThan;
|
|
387
|
+
message.durationHr = duration.bpm;
|
|
388
|
+
message.durationStep = duration.repeatFrom;
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_power_less_than) {
|
|
392
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilPowerLessThan;
|
|
393
|
+
message.durationPower = duration.watts;
|
|
394
|
+
message.durationStep = duration.repeatFrom;
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
if (duration.type === durationTypeSchema.enum.repeat_until_power_greater_than) {
|
|
398
|
+
message.durationType = fitDurationTypeSchema.enum.repeatUntilPowerGreaterThan;
|
|
399
|
+
message.durationPower = duration.watts;
|
|
400
|
+
message.durationStep = duration.repeatFrom;
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
};
|
|
405
|
+
var convertSimpleDuration = (duration, message) => {
|
|
406
|
+
if (duration.type === durationTypeSchema.enum.time) {
|
|
407
|
+
message.durationType = fitDurationTypeSchema.enum.time;
|
|
408
|
+
message.durationTime = duration.seconds;
|
|
409
|
+
message.durationValue = duration.seconds * 1e3;
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
if (duration.type === durationTypeSchema.enum.distance) {
|
|
413
|
+
message.durationType = fitDurationTypeSchema.enum.distance;
|
|
414
|
+
message.durationDistance = duration.meters;
|
|
415
|
+
return true;
|
|
416
|
+
}
|
|
417
|
+
if (duration.type === durationTypeSchema.enum.calories) {
|
|
418
|
+
message.durationType = fitDurationTypeSchema.enum.calories;
|
|
419
|
+
message.durationCalories = duration.calories;
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
return false;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/adapters/krd-to-fit/krd-to-fit-duration.mapper.ts
|
|
426
|
+
var convertDuration = (step, message) => {
|
|
427
|
+
const { duration } = step;
|
|
428
|
+
if (convertSimpleDuration(duration, message)) return;
|
|
429
|
+
if (convertConditionalDuration(duration, message)) return;
|
|
430
|
+
if (convertRepeatDuration(duration, message)) return;
|
|
431
|
+
if (convertRepeatHrPowerDuration(duration, message)) return;
|
|
432
|
+
message.durationType = fitDurationTypeSchema.enum.open;
|
|
433
|
+
};
|
|
434
|
+
var convertCadenceTarget = (step, message) => {
|
|
435
|
+
message.targetType = fitTargetTypeSchema.enum.cadence;
|
|
436
|
+
if (step.target.type !== targetTypeSchema.enum.cadence) return;
|
|
437
|
+
const value = step.target.value;
|
|
438
|
+
if (value.unit === targetUnitSchema.enum.range) {
|
|
439
|
+
message.targetValue = 0;
|
|
440
|
+
message.customTargetCadenceLow = value.min;
|
|
441
|
+
message.customTargetCadenceHigh = value.max;
|
|
442
|
+
} else {
|
|
443
|
+
message.targetValue = 0;
|
|
444
|
+
message.customTargetCadenceLow = value.value;
|
|
445
|
+
message.customTargetCadenceHigh = value.value;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
var convertHeartRateTarget = (step, message) => {
|
|
449
|
+
message.targetType = fitTargetTypeSchema.enum.heartRate;
|
|
450
|
+
if (step.target.type !== targetTypeSchema.enum.heart_rate) return;
|
|
451
|
+
const value = step.target.value;
|
|
452
|
+
if (value.unit === targetUnitSchema.enum.zone) {
|
|
453
|
+
message.targetHrZone = value.value;
|
|
454
|
+
} else if (value.unit === targetUnitSchema.enum.range) {
|
|
455
|
+
message.targetValue = 0;
|
|
456
|
+
message.customTargetHeartRateLow = value.min;
|
|
457
|
+
message.customTargetHeartRateHigh = value.max;
|
|
458
|
+
} else if (value.unit === targetUnitSchema.enum.bpm) {
|
|
459
|
+
message.targetValue = value.value + 100;
|
|
460
|
+
} else if (value.unit === targetUnitSchema.enum.percent_max) {
|
|
461
|
+
message.targetValue = value.value;
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
var convertPaceTarget = (step, message) => {
|
|
465
|
+
message.targetType = fitTargetTypeSchema.enum.speed;
|
|
466
|
+
if (step.target.type !== targetTypeSchema.enum.pace) return;
|
|
467
|
+
const value = step.target.value;
|
|
468
|
+
if (value.unit === targetUnitSchema.enum.zone) {
|
|
469
|
+
message.targetSpeedZone = value.value;
|
|
470
|
+
} else if (value.unit === targetUnitSchema.enum.range) {
|
|
471
|
+
message.targetValue = 0;
|
|
472
|
+
message.customTargetSpeedLow = value.min;
|
|
473
|
+
message.customTargetSpeedHigh = value.max;
|
|
474
|
+
} else {
|
|
475
|
+
message.targetValue = 0;
|
|
476
|
+
message.customTargetSpeedLow = value.value;
|
|
477
|
+
message.customTargetSpeedHigh = value.value;
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
var convertPowerTarget = (step, message) => {
|
|
481
|
+
message.targetType = fitTargetTypeSchema.enum.power;
|
|
482
|
+
if (step.target.type !== targetTypeSchema.enum.power) return;
|
|
483
|
+
const value = step.target.value;
|
|
484
|
+
if (value.unit === targetUnitSchema.enum.zone) {
|
|
485
|
+
message.targetPowerZone = value.value;
|
|
486
|
+
} else if (value.unit === targetUnitSchema.enum.range) {
|
|
487
|
+
message.targetValue = 0;
|
|
488
|
+
message.customTargetPowerLow = value.min;
|
|
489
|
+
message.customTargetPowerHigh = value.max;
|
|
490
|
+
} else if (value.unit === targetUnitSchema.enum.watts) {
|
|
491
|
+
message.targetValue = value.value + 1e3;
|
|
492
|
+
} else if (value.unit === targetUnitSchema.enum.percent_ftp) {
|
|
493
|
+
message.targetValue = value.value;
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
var convertStrokeTarget = (step, message) => {
|
|
497
|
+
message.targetType = fitTargetTypeSchema.enum.swimStroke;
|
|
498
|
+
if (step.target.type !== targetTypeSchema.enum.stroke_type) return;
|
|
499
|
+
const strokeValue = step.target.value;
|
|
500
|
+
message.targetValue = strokeValue.value;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// src/adapters/krd-to-fit/krd-to-fit-target.mapper.ts
|
|
504
|
+
var convertTarget = (step, message) => {
|
|
505
|
+
if (step.target.type === targetTypeSchema.enum.open) {
|
|
506
|
+
message.targetType = fitTargetTypeSchema.enum.open;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (step.target.type === targetTypeSchema.enum.power) {
|
|
510
|
+
convertPowerTarget(step, message);
|
|
511
|
+
} else if (step.target.type === targetTypeSchema.enum.heart_rate) {
|
|
512
|
+
convertHeartRateTarget(step, message);
|
|
513
|
+
} else if (step.target.type === targetTypeSchema.enum.cadence) {
|
|
514
|
+
convertCadenceTarget(step, message);
|
|
515
|
+
} else if (step.target.type === targetTypeSchema.enum.pace) {
|
|
516
|
+
convertPaceTarget(step, message);
|
|
517
|
+
} else if (step.target.type === targetTypeSchema.enum.stroke_type) {
|
|
518
|
+
convertStrokeTarget(step, message);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/adapters/krd-to-fit/krd-to-fit-step.mapper.ts
|
|
523
|
+
var FIT_NOTES_MAX_LENGTH = 256;
|
|
524
|
+
var convertWorkoutStep = (step, messageIndex, logger, options = {}) => {
|
|
525
|
+
const { notesTruncation = "truncate" } = options;
|
|
526
|
+
logger.debug("Converting workout step", { stepIndex: step.stepIndex });
|
|
527
|
+
const workoutStepMesg = {
|
|
528
|
+
messageIndex
|
|
529
|
+
};
|
|
530
|
+
if (step.name) {
|
|
531
|
+
workoutStepMesg.wktStepName = step.name;
|
|
532
|
+
}
|
|
533
|
+
if (step.intensity) {
|
|
534
|
+
workoutStepMesg.intensity = step.intensity;
|
|
535
|
+
}
|
|
536
|
+
if (step.notes !== void 0) {
|
|
537
|
+
workoutStepMesg.notes = convertNotes(
|
|
538
|
+
step.notes,
|
|
539
|
+
step.stepIndex,
|
|
540
|
+
notesTruncation,
|
|
541
|
+
logger
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
if (step.equipment !== void 0) {
|
|
545
|
+
workoutStepMesg.equipment = mapEquipmentToFit(step.equipment);
|
|
546
|
+
}
|
|
547
|
+
convertDuration(step, workoutStepMesg);
|
|
548
|
+
convertTarget(step, workoutStepMesg);
|
|
549
|
+
return workoutStepMesg;
|
|
550
|
+
};
|
|
551
|
+
var convertNotes = (notes, stepIndex, behavior, logger) => {
|
|
552
|
+
if (notes.length <= FIT_NOTES_MAX_LENGTH) {
|
|
553
|
+
return notes;
|
|
554
|
+
}
|
|
555
|
+
if (behavior === "error") {
|
|
556
|
+
throw createFitParsingError(
|
|
557
|
+
`Notes exceed ${FIT_NOTES_MAX_LENGTH} characters at step ${stepIndex} (length: ${notes.length}). Use notesTruncation: "truncate" to auto-truncate.`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
logger.warn(
|
|
561
|
+
`Notes truncated from ${notes.length} to ${FIT_NOTES_MAX_LENGTH} characters`,
|
|
562
|
+
{ stepIndex, originalLength: notes.length }
|
|
563
|
+
);
|
|
564
|
+
return notes.substring(0, FIT_NOTES_MAX_LENGTH);
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
// src/adapters/krd-to-fit/krd-to-fit-workout.mapper.ts
|
|
568
|
+
var convertWorkoutSteps = (workout, logger, options = {}) => {
|
|
569
|
+
logger.debug("Converting workout steps", { stepCount: workout.steps.length });
|
|
570
|
+
const messages = [];
|
|
571
|
+
let messageIndex = 0;
|
|
572
|
+
for (const step of workout.steps) {
|
|
573
|
+
if (isRepetitionBlock(step)) {
|
|
574
|
+
const repetitionMessages = convertRepetitionBlock(
|
|
575
|
+
step,
|
|
576
|
+
messageIndex,
|
|
577
|
+
logger,
|
|
578
|
+
options
|
|
579
|
+
);
|
|
580
|
+
messages.push(...repetitionMessages);
|
|
581
|
+
messageIndex += repetitionMessages.length;
|
|
582
|
+
} else {
|
|
583
|
+
const stepMessage = convertWorkoutStep(
|
|
584
|
+
step,
|
|
585
|
+
messageIndex,
|
|
586
|
+
logger,
|
|
587
|
+
options
|
|
588
|
+
);
|
|
589
|
+
messages.push(stepMessage);
|
|
590
|
+
messageIndex += 1;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return messages;
|
|
594
|
+
};
|
|
595
|
+
var convertRepetitionBlock = (block, startIndex, logger, options) => {
|
|
596
|
+
logger.debug("Converting repetition block", {
|
|
597
|
+
repeatCount: block.repeatCount,
|
|
598
|
+
stepCount: block.steps.length
|
|
599
|
+
});
|
|
600
|
+
const messages = [];
|
|
601
|
+
let messageIndex = startIndex;
|
|
602
|
+
for (const step of block.steps) {
|
|
603
|
+
const stepMessage = convertWorkoutStep(step, messageIndex, logger, options);
|
|
604
|
+
messages.push(stepMessage);
|
|
605
|
+
messageIndex += 1;
|
|
606
|
+
}
|
|
607
|
+
const repeatMessage = {
|
|
608
|
+
mesgNum: FIT_MESSAGE_NUMBERS.WORKOUT_STEP,
|
|
609
|
+
messageIndex,
|
|
610
|
+
durationType: fitDurationTypeSchema.enum.repeatUntilStepsCmplt,
|
|
611
|
+
durationStep: startIndex,
|
|
612
|
+
repeatSteps: block.repeatCount,
|
|
613
|
+
targetType: fitTargetTypeSchema.enum.open
|
|
614
|
+
};
|
|
615
|
+
messages.push(repeatMessage);
|
|
616
|
+
return messages;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// src/adapters/krd-to-fit/krd-to-fit.converter.ts
|
|
620
|
+
var toRecord = (value) => {
|
|
621
|
+
if (typeof value === "object" && value !== null) {
|
|
622
|
+
return value;
|
|
623
|
+
}
|
|
624
|
+
return {};
|
|
625
|
+
};
|
|
626
|
+
var extractWorkout = (krd, logger) => {
|
|
627
|
+
const rawWorkout = krd.extensions?.structured_workout;
|
|
628
|
+
if (!rawWorkout) {
|
|
629
|
+
throw createFitParsingError("KRD missing workout data in extensions");
|
|
630
|
+
}
|
|
631
|
+
const result = workoutSchema.safeParse(rawWorkout);
|
|
632
|
+
if (!result.success) {
|
|
633
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
634
|
+
logger.error("Invalid workout data in KRD extensions", { issues });
|
|
635
|
+
throw createFitParsingError(`Invalid workout data: ${issues}`);
|
|
636
|
+
}
|
|
637
|
+
return result.data;
|
|
638
|
+
};
|
|
639
|
+
var convertKRDToMessages = (krd, logger) => {
|
|
640
|
+
logger.debug("Converting KRD to FIT messages");
|
|
641
|
+
const messages = [];
|
|
642
|
+
const fileIdMessage = convertMetadataToFileId(krd, logger);
|
|
643
|
+
messages.push({
|
|
644
|
+
mesgNum: FIT_MESSAGE_NUMBERS.FILE_ID,
|
|
645
|
+
...fileIdMessage
|
|
646
|
+
});
|
|
647
|
+
const workout = extractWorkout(krd, logger);
|
|
648
|
+
const workoutMessage = convertWorkoutMetadata(workout, logger);
|
|
649
|
+
messages.push({
|
|
650
|
+
mesgNum: FIT_MESSAGE_NUMBERS.WORKOUT,
|
|
651
|
+
...workoutMessage
|
|
652
|
+
});
|
|
653
|
+
const workoutStepMessages = convertWorkoutSteps(workout, logger);
|
|
654
|
+
for (const stepMessage of workoutStepMessages) {
|
|
655
|
+
messages.push({
|
|
656
|
+
mesgNum: FIT_MESSAGE_NUMBERS.WORKOUT_STEP,
|
|
657
|
+
...toRecord(stepMessage)
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
logger.debug("Converted KRD to FIT messages", {
|
|
661
|
+
messageCount: messages.length
|
|
662
|
+
});
|
|
663
|
+
return messages;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
// src/adapters/shared/coordinate.converter.ts
|
|
667
|
+
var SEMICIRCLES_TO_DEGREES = 180 / Math.pow(2, 31);
|
|
668
|
+
var semicirclesToDegrees = (semicircles) => semicircles * SEMICIRCLES_TO_DEGREES;
|
|
669
|
+
var validateCoordinates = (latSemicircles, lonSemicircles) => {
|
|
670
|
+
if (!Number.isFinite(latSemicircles) || !Number.isFinite(lonSemicircles)) {
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
const degreesLat = semicirclesToDegrees(latSemicircles);
|
|
674
|
+
const degreesLon = semicirclesToDegrees(lonSemicircles);
|
|
675
|
+
return degreesLat >= -90 && degreesLat <= 90 && degreesLon >= -180 && degreesLon <= 180;
|
|
676
|
+
};
|
|
677
|
+
var fitMessageKeySchema = z.enum([
|
|
678
|
+
"fileIdMesgs",
|
|
679
|
+
"workoutMesgs",
|
|
680
|
+
"workoutStepMesgs",
|
|
681
|
+
"sessionMesgs",
|
|
682
|
+
"recordMesgs",
|
|
683
|
+
"eventMesgs",
|
|
684
|
+
"lapMesgs"
|
|
685
|
+
]);
|
|
686
|
+
var fitEventSchema = z.enum([
|
|
687
|
+
"timer",
|
|
688
|
+
"workout",
|
|
689
|
+
"workoutStep",
|
|
690
|
+
"powerDown",
|
|
691
|
+
"powerUp",
|
|
692
|
+
"offCourse",
|
|
693
|
+
"session",
|
|
694
|
+
"lap",
|
|
695
|
+
"coursePoint",
|
|
696
|
+
"battery",
|
|
697
|
+
"virtualPartnerPace",
|
|
698
|
+
"hrHighAlert",
|
|
699
|
+
"hrLowAlert",
|
|
700
|
+
"speedHighAlert",
|
|
701
|
+
"speedLowAlert",
|
|
702
|
+
"cadHighAlert",
|
|
703
|
+
"cadLowAlert",
|
|
704
|
+
"powerHighAlert",
|
|
705
|
+
"powerLowAlert",
|
|
706
|
+
"recoveryHr",
|
|
707
|
+
"batteryLow",
|
|
708
|
+
"timeDurationAlert",
|
|
709
|
+
"distanceDurationAlert",
|
|
710
|
+
"calorieDurationAlert",
|
|
711
|
+
"activity",
|
|
712
|
+
"fitnessEquipment",
|
|
713
|
+
"length",
|
|
714
|
+
"userMarker",
|
|
715
|
+
"sportPoint",
|
|
716
|
+
"calibration",
|
|
717
|
+
"frontGearChange",
|
|
718
|
+
"rearGearChange",
|
|
719
|
+
"riderPositionChange",
|
|
720
|
+
"elevHighAlert",
|
|
721
|
+
"elevLowAlert"
|
|
722
|
+
]);
|
|
723
|
+
var fitEventTypeSchema = z.enum([
|
|
724
|
+
"start",
|
|
725
|
+
"stop",
|
|
726
|
+
"consecutiveDepreciated",
|
|
727
|
+
"marker",
|
|
728
|
+
"stopAll",
|
|
729
|
+
"beginDepreciated",
|
|
730
|
+
"endDepreciated",
|
|
731
|
+
"endAllDepreciated",
|
|
732
|
+
"stopDisable",
|
|
733
|
+
"stopDisableAll"
|
|
734
|
+
]);
|
|
735
|
+
var fitEventMessageSchema = z.object({
|
|
736
|
+
timestamp: z.number(),
|
|
737
|
+
event: fitEventSchema,
|
|
738
|
+
eventType: fitEventTypeSchema,
|
|
739
|
+
eventGroup: z.number().optional(),
|
|
740
|
+
data: z.number().optional(),
|
|
741
|
+
data16: z.number().optional()
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// src/adapters/event/event-type-maps.ts
|
|
745
|
+
var FIT_EVENT_TO_KRD_TYPE = {
|
|
746
|
+
timer: "event_timer",
|
|
747
|
+
workout: "event_start",
|
|
748
|
+
workoutStep: "event_workout_step_change",
|
|
749
|
+
powerDown: "event_stop",
|
|
750
|
+
powerUp: "event_start",
|
|
751
|
+
offCourse: "event_marker",
|
|
752
|
+
session: "event_session_start",
|
|
753
|
+
lap: "event_lap",
|
|
754
|
+
coursePoint: "event_marker",
|
|
755
|
+
battery: "event_marker",
|
|
756
|
+
virtualPartnerPace: "event_marker",
|
|
757
|
+
hrHighAlert: "event_marker",
|
|
758
|
+
hrLowAlert: "event_marker",
|
|
759
|
+
speedHighAlert: "event_marker",
|
|
760
|
+
speedLowAlert: "event_marker",
|
|
761
|
+
cadHighAlert: "event_marker",
|
|
762
|
+
cadLowAlert: "event_marker",
|
|
763
|
+
powerHighAlert: "event_marker",
|
|
764
|
+
powerLowAlert: "event_marker",
|
|
765
|
+
recoveryHr: "event_marker",
|
|
766
|
+
batteryLow: "event_marker",
|
|
767
|
+
timeDurationAlert: "event_marker",
|
|
768
|
+
distanceDurationAlert: "event_marker",
|
|
769
|
+
calorieDurationAlert: "event_marker",
|
|
770
|
+
activity: "event_activity_start",
|
|
771
|
+
fitnessEquipment: "event_marker",
|
|
772
|
+
length: "event_lap",
|
|
773
|
+
userMarker: "event_marker",
|
|
774
|
+
sportPoint: "event_marker",
|
|
775
|
+
calibration: "event_marker",
|
|
776
|
+
frontGearChange: "event_marker",
|
|
777
|
+
rearGearChange: "event_marker",
|
|
778
|
+
riderPositionChange: "event_marker",
|
|
779
|
+
elevHighAlert: "event_marker",
|
|
780
|
+
elevLowAlert: "event_marker"
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
// src/adapters/event/event.mapper.ts
|
|
784
|
+
var mapEventTypeToKrd = (fitEvent, fitEventType) => {
|
|
785
|
+
if (fitEvent === "timer") {
|
|
786
|
+
switch (fitEventType) {
|
|
787
|
+
case "start":
|
|
788
|
+
return "event_start";
|
|
789
|
+
case "stop":
|
|
790
|
+
return "event_stop";
|
|
791
|
+
case "stopDisable":
|
|
792
|
+
case "stopDisableAll":
|
|
793
|
+
return "event_pause";
|
|
794
|
+
default:
|
|
795
|
+
return "event_timer";
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return FIT_EVENT_TO_KRD_TYPE[fitEvent] ?? "event_marker";
|
|
799
|
+
};
|
|
800
|
+
var mapFitEventToKrd = (fit) => ({
|
|
801
|
+
timestamp: new Date(fit.timestamp * 1e3).toISOString(),
|
|
802
|
+
eventType: mapEventTypeToKrd(fit.event, fit.eventType),
|
|
803
|
+
eventGroup: fit.eventGroup,
|
|
804
|
+
data: fit.data
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
// src/adapters/event/fit-to-krd-event.converter.ts
|
|
808
|
+
var convertFitToKrdEvent = (data) => {
|
|
809
|
+
const fitEvent = fitEventMessageSchema.parse(data);
|
|
810
|
+
return mapFitEventToKrd(fitEvent);
|
|
811
|
+
};
|
|
812
|
+
var convertFitToKrdEvents = (events) => {
|
|
813
|
+
return events.map(convertFitToKrdEvent);
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
// src/adapters/extensions/extensions.extractor.ts
|
|
817
|
+
var extractFitExtensions = (messages, logger) => {
|
|
818
|
+
logger.debug("Extracting FIT extensions");
|
|
819
|
+
const unknownMessages = extractUnknownMessages(messages, logger);
|
|
820
|
+
const developerFields = extractAllDeveloperFields(messages);
|
|
821
|
+
return buildExtensions(unknownMessages, developerFields, logger);
|
|
822
|
+
};
|
|
823
|
+
var extractUnknownMessages = (messages, logger) => {
|
|
824
|
+
const knownMessageKeys = /* @__PURE__ */ new Set([
|
|
825
|
+
fitMessageKeySchema.enum.fileIdMesgs,
|
|
826
|
+
fitMessageKeySchema.enum.workoutMesgs,
|
|
827
|
+
fitMessageKeySchema.enum.workoutStepMesgs
|
|
828
|
+
]);
|
|
829
|
+
const unknownMessages = {};
|
|
830
|
+
for (const [key, value] of Object.entries(messages)) {
|
|
831
|
+
if (!knownMessageKeys.has(key) && value && Array.isArray(value)) {
|
|
832
|
+
logger.debug("Found unknown message type", { messageType: key });
|
|
833
|
+
unknownMessages[key] = value;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return unknownMessages;
|
|
837
|
+
};
|
|
838
|
+
var extractAllDeveloperFields = (messages) => {
|
|
839
|
+
const developerFields = [];
|
|
840
|
+
for (const value of Object.values(messages)) {
|
|
841
|
+
if (value && Array.isArray(value)) {
|
|
842
|
+
for (const message of value) {
|
|
843
|
+
if (message && typeof message === "object") {
|
|
844
|
+
const devFields = extractDeveloperFields(message);
|
|
845
|
+
if (devFields.length > 0) {
|
|
846
|
+
developerFields.push(...devFields);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return developerFields;
|
|
853
|
+
};
|
|
854
|
+
var extractDeveloperFields = (message) => {
|
|
855
|
+
const devFields = [];
|
|
856
|
+
for (const [key, value] of Object.entries(message)) {
|
|
857
|
+
if (key.startsWith("developer_") || key.includes("DeveloperField")) {
|
|
858
|
+
devFields.push({
|
|
859
|
+
fieldName: key,
|
|
860
|
+
value
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return devFields;
|
|
865
|
+
};
|
|
866
|
+
var buildExtensions = (unknownMessages, developerFields, logger) => {
|
|
867
|
+
const extensions = {};
|
|
868
|
+
if (developerFields.length > 0) {
|
|
869
|
+
logger.info("Preserved developer fields", {
|
|
870
|
+
count: developerFields.length
|
|
871
|
+
});
|
|
872
|
+
extensions.developerFields = developerFields;
|
|
873
|
+
}
|
|
874
|
+
if (Object.keys(unknownMessages).length > 0) {
|
|
875
|
+
logger.info("Preserved unknown message types", {
|
|
876
|
+
types: Object.keys(unknownMessages)
|
|
877
|
+
});
|
|
878
|
+
extensions.unknownMessages = unknownMessages;
|
|
879
|
+
}
|
|
880
|
+
return extensions;
|
|
881
|
+
};
|
|
882
|
+
var fitLapTriggerSchema = z.enum([
|
|
883
|
+
"manual",
|
|
884
|
+
"time",
|
|
885
|
+
"distance",
|
|
886
|
+
"positionStart",
|
|
887
|
+
"positionLap",
|
|
888
|
+
"positionWaypoint",
|
|
889
|
+
"positionMarked",
|
|
890
|
+
"sessionEnd",
|
|
891
|
+
"fitnessEquipment"
|
|
892
|
+
]);
|
|
893
|
+
var fitSportSchema = z.enum([
|
|
894
|
+
"cycling",
|
|
895
|
+
"running",
|
|
896
|
+
"swimming",
|
|
897
|
+
"generic"
|
|
898
|
+
]);
|
|
899
|
+
|
|
900
|
+
// src/adapters/schemas/fit-lap.ts
|
|
901
|
+
var fitLapSchema = z.object({
|
|
902
|
+
// Identifiers
|
|
903
|
+
messageIndex: z.number().optional(),
|
|
904
|
+
timestamp: z.number(),
|
|
905
|
+
// Timing
|
|
906
|
+
startTime: z.number(),
|
|
907
|
+
totalElapsedTime: z.number(),
|
|
908
|
+
totalTimerTime: z.number(),
|
|
909
|
+
// Distance
|
|
910
|
+
totalDistance: z.number().optional(),
|
|
911
|
+
// Speed (enhanced values preferred when available)
|
|
912
|
+
avgSpeed: z.number().optional(),
|
|
913
|
+
maxSpeed: z.number().optional(),
|
|
914
|
+
enhancedAvgSpeed: z.number().optional(),
|
|
915
|
+
enhancedMaxSpeed: z.number().optional(),
|
|
916
|
+
// Heart rate
|
|
917
|
+
avgHeartRate: z.number().optional(),
|
|
918
|
+
maxHeartRate: z.number().optional(),
|
|
919
|
+
// Cadence
|
|
920
|
+
avgCadence: z.number().optional(),
|
|
921
|
+
maxCadence: z.number().optional(),
|
|
922
|
+
// Power
|
|
923
|
+
avgPower: z.number().optional(),
|
|
924
|
+
maxPower: z.number().optional(),
|
|
925
|
+
normalizedPower: z.number().optional(),
|
|
926
|
+
// Elevation
|
|
927
|
+
totalAscent: z.number().optional(),
|
|
928
|
+
totalDescent: z.number().optional(),
|
|
929
|
+
// Calories
|
|
930
|
+
totalCalories: z.number().optional(),
|
|
931
|
+
// Classification
|
|
932
|
+
lapTrigger: fitLapTriggerSchema.optional(),
|
|
933
|
+
sport: fitSportSchema.optional(),
|
|
934
|
+
subSport: fitSubSportSchema.optional(),
|
|
935
|
+
// Swimming
|
|
936
|
+
numLengths: z.number().optional(),
|
|
937
|
+
swimStroke: z.number().optional(),
|
|
938
|
+
// Workout reference
|
|
939
|
+
wktStepIndex: z.number().optional()
|
|
940
|
+
});
|
|
941
|
+
|
|
942
|
+
// src/adapters/lap/lap-trigger.mapper.ts
|
|
943
|
+
var mapFitLapTriggerToKrd = (fit) => {
|
|
944
|
+
switch (fit) {
|
|
945
|
+
case "manual":
|
|
946
|
+
return "manual";
|
|
947
|
+
case "time":
|
|
948
|
+
return "time";
|
|
949
|
+
case "distance":
|
|
950
|
+
return "distance";
|
|
951
|
+
case "positionStart":
|
|
952
|
+
case "positionLap":
|
|
953
|
+
case "positionWaypoint":
|
|
954
|
+
case "positionMarked":
|
|
955
|
+
return "position";
|
|
956
|
+
case "sessionEnd":
|
|
957
|
+
return "session_end";
|
|
958
|
+
case "fitnessEquipment":
|
|
959
|
+
return "fitness_equipment";
|
|
960
|
+
}
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
// src/adapters/lap/lap.mapper.ts
|
|
964
|
+
var mapFitLapToKrd = (fit) => ({
|
|
965
|
+
// Timing - convert ms to seconds
|
|
966
|
+
startTime: new Date(fit.startTime * 1e3).toISOString(),
|
|
967
|
+
totalElapsedTime: fit.totalElapsedTime / 1e3,
|
|
968
|
+
totalTimerTime: fit.totalTimerTime / 1e3,
|
|
969
|
+
// Distance
|
|
970
|
+
totalDistance: fit.totalDistance,
|
|
971
|
+
// Heart rate
|
|
972
|
+
avgHeartRate: fit.avgHeartRate,
|
|
973
|
+
maxHeartRate: fit.maxHeartRate,
|
|
974
|
+
// Cadence
|
|
975
|
+
avgCadence: fit.avgCadence,
|
|
976
|
+
maxCadence: fit.maxCadence,
|
|
977
|
+
// Power
|
|
978
|
+
avgPower: fit.avgPower,
|
|
979
|
+
maxPower: fit.maxPower,
|
|
980
|
+
normalizedPower: fit.normalizedPower,
|
|
981
|
+
// Speed - prefer enhanced values
|
|
982
|
+
avgSpeed: fit.enhancedAvgSpeed ?? fit.avgSpeed,
|
|
983
|
+
maxSpeed: fit.enhancedMaxSpeed ?? fit.maxSpeed,
|
|
984
|
+
// Elevation
|
|
985
|
+
totalAscent: fit.totalAscent,
|
|
986
|
+
totalDescent: fit.totalDescent,
|
|
987
|
+
// Calories
|
|
988
|
+
totalCalories: fit.totalCalories,
|
|
989
|
+
// Classification
|
|
990
|
+
trigger: fit.lapTrigger ? mapFitLapTriggerToKrd(fit.lapTrigger) : void 0,
|
|
991
|
+
sport: fit.sport,
|
|
992
|
+
subSport: fit.subSport ? mapSubSportToKrd(fit.subSport) : void 0,
|
|
993
|
+
// Workout reference
|
|
994
|
+
workoutStepIndex: fit.wktStepIndex,
|
|
995
|
+
// Swimming
|
|
996
|
+
numLengths: fit.numLengths,
|
|
997
|
+
swimStroke: fit.swimStroke !== void 0 ? FIT_TO_SWIM_STROKE[fit.swimStroke] : void 0
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
// src/adapters/lap/fit-to-krd-lap.converter.ts
|
|
1001
|
+
var convertFitToKrdLap = (data) => {
|
|
1002
|
+
const fitLap = fitLapSchema.parse(data);
|
|
1003
|
+
return mapFitLapToKrd(fitLap);
|
|
1004
|
+
};
|
|
1005
|
+
var convertFitToKrdLaps = (laps) => {
|
|
1006
|
+
return laps.map(convertFitToKrdLap);
|
|
1007
|
+
};
|
|
1008
|
+
var fitRecordSchema = z.object({
|
|
1009
|
+
timestamp: z.number(),
|
|
1010
|
+
positionLat: z.number().optional(),
|
|
1011
|
+
positionLong: z.number().optional(),
|
|
1012
|
+
altitude: z.number().optional(),
|
|
1013
|
+
enhancedAltitude: z.number().optional(),
|
|
1014
|
+
speed: z.number().optional(),
|
|
1015
|
+
enhancedSpeed: z.number().optional(),
|
|
1016
|
+
distance: z.number().optional(),
|
|
1017
|
+
heartRate: z.number().optional(),
|
|
1018
|
+
cadence: z.number().optional(),
|
|
1019
|
+
fractionalCadence: z.number().optional(),
|
|
1020
|
+
power: z.number().optional(),
|
|
1021
|
+
temperature: z.number().optional(),
|
|
1022
|
+
verticalOscillation: z.number().optional(),
|
|
1023
|
+
stanceTime: z.number().optional(),
|
|
1024
|
+
stanceTimePercent: z.number().optional(),
|
|
1025
|
+
stepLength: z.number().optional(),
|
|
1026
|
+
compressedTimestamp: z.number().optional()
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
// src/adapters/record/record.mapper.ts
|
|
1030
|
+
var mapFitRecordToKrd = (fit) => {
|
|
1031
|
+
const record = {
|
|
1032
|
+
timestamp: new Date(fit.timestamp * 1e3).toISOString()
|
|
1033
|
+
};
|
|
1034
|
+
if (fit.positionLat !== void 0 && fit.positionLong !== void 0) {
|
|
1035
|
+
record.position = {
|
|
1036
|
+
lat: semicirclesToDegrees(fit.positionLat),
|
|
1037
|
+
lon: semicirclesToDegrees(fit.positionLong)
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
if (fit.enhancedAltitude !== void 0) {
|
|
1041
|
+
record.altitude = fit.enhancedAltitude;
|
|
1042
|
+
} else if (fit.altitude !== void 0) {
|
|
1043
|
+
record.altitude = fit.altitude;
|
|
1044
|
+
}
|
|
1045
|
+
if (fit.enhancedSpeed !== void 0) {
|
|
1046
|
+
record.speed = fit.enhancedSpeed;
|
|
1047
|
+
} else if (fit.speed !== void 0) {
|
|
1048
|
+
record.speed = fit.speed;
|
|
1049
|
+
}
|
|
1050
|
+
if (fit.distance !== void 0) record.distance = fit.distance;
|
|
1051
|
+
if (fit.heartRate !== void 0) record.heartRate = fit.heartRate;
|
|
1052
|
+
if (fit.power !== void 0) record.power = fit.power;
|
|
1053
|
+
if (fit.temperature !== void 0) record.temperature = fit.temperature;
|
|
1054
|
+
if (fit.cadence !== void 0) {
|
|
1055
|
+
record.cadence = fit.cadence + (fit.fractionalCadence ?? 0);
|
|
1056
|
+
}
|
|
1057
|
+
if (fit.verticalOscillation !== void 0) {
|
|
1058
|
+
record.verticalOscillation = fit.verticalOscillation;
|
|
1059
|
+
}
|
|
1060
|
+
if (fit.stanceTime !== void 0) record.stanceTime = fit.stanceTime;
|
|
1061
|
+
if (fit.stepLength !== void 0) record.stepLength = fit.stepLength;
|
|
1062
|
+
return record;
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// src/adapters/record/fit-to-krd-record.converter.ts
|
|
1066
|
+
var convertFitToKrdRecord = (data) => {
|
|
1067
|
+
const fitRecord = fitRecordSchema.parse(data);
|
|
1068
|
+
const hasLat = fitRecord.positionLat !== void 0;
|
|
1069
|
+
const hasLon = fitRecord.positionLong !== void 0;
|
|
1070
|
+
if (hasLat !== hasLon) {
|
|
1071
|
+
throw new Error("Partial coordinates: both lat and lon must be present");
|
|
1072
|
+
}
|
|
1073
|
+
if (hasLat && hasLon) {
|
|
1074
|
+
if (!validateCoordinates(fitRecord.positionLat, fitRecord.positionLong)) {
|
|
1075
|
+
throw new Error("Invalid coordinates: out of range");
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return mapFitRecordToKrd(fitRecord);
|
|
1079
|
+
};
|
|
1080
|
+
var convertFitToKrdRecords = (records) => {
|
|
1081
|
+
return records.map(convertFitToKrdRecord);
|
|
1082
|
+
};
|
|
1083
|
+
var fitSessionSchema = z.object({
|
|
1084
|
+
timestamp: z.number(),
|
|
1085
|
+
startTime: z.number(),
|
|
1086
|
+
totalElapsedTime: z.number(),
|
|
1087
|
+
totalTimerTime: z.number(),
|
|
1088
|
+
totalDistance: z.number().optional(),
|
|
1089
|
+
totalCalories: z.number().optional(),
|
|
1090
|
+
avgSpeed: z.number().optional(),
|
|
1091
|
+
maxSpeed: z.number().optional(),
|
|
1092
|
+
enhancedAvgSpeed: z.number().optional(),
|
|
1093
|
+
enhancedMaxSpeed: z.number().optional(),
|
|
1094
|
+
avgHeartRate: z.number().optional(),
|
|
1095
|
+
maxHeartRate: z.number().optional(),
|
|
1096
|
+
avgCadence: z.number().optional(),
|
|
1097
|
+
maxCadence: z.number().optional(),
|
|
1098
|
+
avgPower: z.number().optional(),
|
|
1099
|
+
maxPower: z.number().optional(),
|
|
1100
|
+
normalizedPower: z.number().optional(),
|
|
1101
|
+
trainingStressScore: z.number().optional(),
|
|
1102
|
+
intensityFactor: z.number().optional(),
|
|
1103
|
+
totalAscent: z.number().optional(),
|
|
1104
|
+
totalDescent: z.number().optional(),
|
|
1105
|
+
sport: fitSportSchema,
|
|
1106
|
+
subSport: fitSubSportSchema.optional(),
|
|
1107
|
+
numLaps: z.number().optional(),
|
|
1108
|
+
firstLapIndex: z.number().optional()
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
// src/adapters/session/session.mapper.ts
|
|
1112
|
+
var mapFitSessionToKrd = (fit) => ({
|
|
1113
|
+
startTime: new Date(fit.startTime * 1e3).toISOString(),
|
|
1114
|
+
totalElapsedTime: fit.totalElapsedTime / 1e3,
|
|
1115
|
+
totalTimerTime: fit.totalTimerTime !== void 0 ? fit.totalTimerTime / 1e3 : void 0,
|
|
1116
|
+
totalDistance: fit.totalDistance,
|
|
1117
|
+
sport: String(fit.sport),
|
|
1118
|
+
subSport: fit.subSport ? mapSubSportToKrd(fit.subSport) : void 0,
|
|
1119
|
+
avgHeartRate: fit.avgHeartRate,
|
|
1120
|
+
maxHeartRate: fit.maxHeartRate,
|
|
1121
|
+
avgCadence: fit.avgCadence,
|
|
1122
|
+
maxCadence: fit.maxCadence,
|
|
1123
|
+
avgPower: fit.avgPower,
|
|
1124
|
+
maxPower: fit.maxPower,
|
|
1125
|
+
normalizedPower: fit.normalizedPower,
|
|
1126
|
+
trainingStressScore: fit.trainingStressScore,
|
|
1127
|
+
intensityFactor: fit.intensityFactor,
|
|
1128
|
+
totalCalories: fit.totalCalories,
|
|
1129
|
+
totalAscent: fit.totalAscent,
|
|
1130
|
+
totalDescent: fit.totalDescent,
|
|
1131
|
+
avgSpeed: fit.enhancedAvgSpeed ?? fit.avgSpeed,
|
|
1132
|
+
maxSpeed: fit.enhancedMaxSpeed ?? fit.maxSpeed
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
// src/adapters/session/fit-to-krd-session.converter.ts
|
|
1136
|
+
var convertFitToKrdSession = (data) => {
|
|
1137
|
+
const fitSession = fitSessionSchema.parse(data);
|
|
1138
|
+
return mapFitSessionToKrd(fitSession);
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
// src/adapters/messages/activity.mapper.ts
|
|
1142
|
+
var KRD_VERSION = "1.0";
|
|
1143
|
+
var convertTimeCreated = (timeCreated) => {
|
|
1144
|
+
if (timeCreated instanceof Date) return timeCreated.toISOString();
|
|
1145
|
+
if (typeof timeCreated === "number")
|
|
1146
|
+
return new Date(timeCreated * 1e3).toISOString();
|
|
1147
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1148
|
+
};
|
|
1149
|
+
var mapActivityFileToKRD = (messages, logger) => {
|
|
1150
|
+
const fileId = messages[fitMessageKeySchema.enum.fileIdMesgs]?.[0];
|
|
1151
|
+
const sessionMsgs = messages[fitMessageKeySchema.enum.sessionMesgs] || [];
|
|
1152
|
+
const recordMsgs = messages[fitMessageKeySchema.enum.recordMesgs] || [];
|
|
1153
|
+
const eventMsgs = messages[fitMessageKeySchema.enum.eventMesgs] || [];
|
|
1154
|
+
const lapMsgs = messages[fitMessageKeySchema.enum.lapMesgs] || [];
|
|
1155
|
+
logger.debug("Mapping activity file", {
|
|
1156
|
+
sessions: sessionMsgs.length,
|
|
1157
|
+
records: recordMsgs.length,
|
|
1158
|
+
events: eventMsgs.length,
|
|
1159
|
+
laps: lapMsgs.length
|
|
1160
|
+
});
|
|
1161
|
+
const session = sessionMsgs.length > 0 ? convertFitToKrdSession(sessionMsgs[0]) : void 0;
|
|
1162
|
+
const records = convertFitToKrdRecords(recordMsgs);
|
|
1163
|
+
const events = convertFitToKrdEvents(eventMsgs);
|
|
1164
|
+
const laps = convertFitToKrdLaps(lapMsgs);
|
|
1165
|
+
const fitExtensions = extractFitExtensions(messages, logger);
|
|
1166
|
+
const created = convertTimeCreated(fileId?.timeCreated);
|
|
1167
|
+
return {
|
|
1168
|
+
version: KRD_VERSION,
|
|
1169
|
+
type: fileTypeSchema.enum.recorded_activity,
|
|
1170
|
+
metadata: {
|
|
1171
|
+
created,
|
|
1172
|
+
sport: session?.sport ?? "other",
|
|
1173
|
+
subSport: session?.subSport
|
|
1174
|
+
},
|
|
1175
|
+
sessions: session ? [session] : void 0,
|
|
1176
|
+
laps: laps.length > 0 ? laps : void 0,
|
|
1177
|
+
records: records.length > 0 ? records : void 0,
|
|
1178
|
+
events: events.length > 0 ? events : void 0,
|
|
1179
|
+
extensions: {
|
|
1180
|
+
fit: fitExtensions
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
// src/adapters/metadata/metadata.mapper.ts
|
|
1186
|
+
var mapMetadata = (fileId, workoutMsg, logger) => {
|
|
1187
|
+
logger.debug("Mapping metadata from FIT messages");
|
|
1188
|
+
const sport = mapSportType(workoutMsg?.sport);
|
|
1189
|
+
const created = mapCreatedTimestamp(fileId);
|
|
1190
|
+
return {
|
|
1191
|
+
created,
|
|
1192
|
+
manufacturer: fileId?.manufacturer,
|
|
1193
|
+
product: fileId?.garminProduct || fileId?.product?.toString(),
|
|
1194
|
+
serialNumber: fileId?.serialNumber?.toString(),
|
|
1195
|
+
sport
|
|
1196
|
+
};
|
|
1197
|
+
};
|
|
1198
|
+
var mapCreatedTimestamp = (fileId) => {
|
|
1199
|
+
if (!fileId?.timeCreated) {
|
|
1200
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1201
|
+
}
|
|
1202
|
+
if (typeof fileId.timeCreated === "string") {
|
|
1203
|
+
return fileId.timeCreated;
|
|
1204
|
+
}
|
|
1205
|
+
if (fileId.timeCreated instanceof Date) {
|
|
1206
|
+
return fileId.timeCreated.toISOString();
|
|
1207
|
+
}
|
|
1208
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1209
|
+
};
|
|
1210
|
+
var FIT_LENGTH_UNIT_MAP = {
|
|
1211
|
+
0: "meters",
|
|
1212
|
+
1: "yards"
|
|
1213
|
+
};
|
|
1214
|
+
var mapLengthUnitToKrd = (fitUnit) => {
|
|
1215
|
+
if (fitUnit === void 0) {
|
|
1216
|
+
return lengthUnitSchema.enum.meters;
|
|
1217
|
+
}
|
|
1218
|
+
return FIT_LENGTH_UNIT_MAP[fitUnit] || lengthUnitSchema.enum.meters;
|
|
1219
|
+
};
|
|
1220
|
+
var convertTimeDuration = (data) => {
|
|
1221
|
+
if (data.durationTime !== void 0) {
|
|
1222
|
+
return {
|
|
1223
|
+
type: durationTypeSchema.enum.time,
|
|
1224
|
+
seconds: data.durationTime
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
return null;
|
|
1228
|
+
};
|
|
1229
|
+
var convertDistanceDuration = (data) => {
|
|
1230
|
+
if (data.durationDistance !== void 0) {
|
|
1231
|
+
return {
|
|
1232
|
+
type: durationTypeSchema.enum.distance,
|
|
1233
|
+
meters: data.durationDistance
|
|
1234
|
+
};
|
|
1235
|
+
}
|
|
1236
|
+
return null;
|
|
1237
|
+
};
|
|
1238
|
+
var convertHeartRateLessThan = (data) => {
|
|
1239
|
+
if (data.durationHr !== void 0) {
|
|
1240
|
+
return {
|
|
1241
|
+
type: durationTypeSchema.enum.heart_rate_less_than,
|
|
1242
|
+
bpm: data.durationHr
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
1245
|
+
return null;
|
|
1246
|
+
};
|
|
1247
|
+
var convertHeartRateGreaterThan = (data) => {
|
|
1248
|
+
if (data.repeatHr !== void 0 && data.durationStep !== void 0) {
|
|
1249
|
+
return {
|
|
1250
|
+
type: durationTypeSchema.enum.repeat_until_heart_rate_greater_than,
|
|
1251
|
+
bpm: data.repeatHr,
|
|
1252
|
+
repeatFrom: data.durationStep
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
return null;
|
|
1256
|
+
};
|
|
1257
|
+
var convertCaloriesDuration = (data) => {
|
|
1258
|
+
if (data.durationCalories !== void 0) {
|
|
1259
|
+
return {
|
|
1260
|
+
type: durationTypeSchema.enum.calories,
|
|
1261
|
+
calories: data.durationCalories
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
return null;
|
|
1265
|
+
};
|
|
1266
|
+
var convertPowerLessThan = (data) => {
|
|
1267
|
+
if (data.durationPower !== void 0) {
|
|
1268
|
+
return {
|
|
1269
|
+
type: durationTypeSchema.enum.power_less_than,
|
|
1270
|
+
watts: data.durationPower
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
return null;
|
|
1274
|
+
};
|
|
1275
|
+
var convertPowerGreaterThan = (data) => {
|
|
1276
|
+
if (data.durationPower !== void 0) {
|
|
1277
|
+
return {
|
|
1278
|
+
type: durationTypeSchema.enum.power_greater_than,
|
|
1279
|
+
watts: data.durationPower
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
return null;
|
|
1283
|
+
};
|
|
1284
|
+
var convertRepeatUntilTime = (data) => {
|
|
1285
|
+
if (data.durationTime !== void 0 && data.durationStep !== void 0) {
|
|
1286
|
+
return {
|
|
1287
|
+
type: durationTypeSchema.enum.repeat_until_time,
|
|
1288
|
+
seconds: data.durationTime,
|
|
1289
|
+
repeatFrom: data.durationStep
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return null;
|
|
1293
|
+
};
|
|
1294
|
+
var convertRepeatUntilDistance = (data) => {
|
|
1295
|
+
if (data.durationDistance !== void 0 && data.durationStep !== void 0) {
|
|
1296
|
+
return {
|
|
1297
|
+
type: durationTypeSchema.enum.repeat_until_distance,
|
|
1298
|
+
meters: data.durationDistance,
|
|
1299
|
+
repeatFrom: data.durationStep
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
return null;
|
|
1303
|
+
};
|
|
1304
|
+
var convertRepeatUntilCalories = (data) => {
|
|
1305
|
+
if (data.durationCalories !== void 0 && data.durationStep !== void 0) {
|
|
1306
|
+
return {
|
|
1307
|
+
type: durationTypeSchema.enum.repeat_until_calories,
|
|
1308
|
+
calories: data.durationCalories,
|
|
1309
|
+
repeatFrom: data.durationStep
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
return null;
|
|
1313
|
+
};
|
|
1314
|
+
var convertRepeatUntilHrLessThan = (data) => {
|
|
1315
|
+
if (data.durationHr !== void 0 && data.durationStep !== void 0) {
|
|
1316
|
+
return {
|
|
1317
|
+
type: durationTypeSchema.enum.repeat_until_heart_rate_less_than,
|
|
1318
|
+
bpm: data.durationHr,
|
|
1319
|
+
repeatFrom: data.durationStep
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
return null;
|
|
1323
|
+
};
|
|
1324
|
+
var convertRepeatUntilPowerLessThan = (data) => {
|
|
1325
|
+
if (data.durationPower !== void 0 && data.durationStep !== void 0) {
|
|
1326
|
+
return {
|
|
1327
|
+
type: durationTypeSchema.enum.repeat_until_power_less_than,
|
|
1328
|
+
watts: data.durationPower,
|
|
1329
|
+
repeatFrom: data.durationStep
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
return null;
|
|
1333
|
+
};
|
|
1334
|
+
var convertRepeatUntilPowerGreaterThan = (data) => {
|
|
1335
|
+
if (data.durationPower !== void 0 && data.durationStep !== void 0) {
|
|
1336
|
+
return {
|
|
1337
|
+
type: durationTypeSchema.enum.repeat_until_power_greater_than,
|
|
1338
|
+
watts: data.durationPower,
|
|
1339
|
+
repeatFrom: data.durationStep
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
return null;
|
|
1343
|
+
};
|
|
1344
|
+
|
|
1345
|
+
// src/adapters/duration/duration.converter.ts
|
|
1346
|
+
var DURATION_CONVERTERS = {
|
|
1347
|
+
[fitDurationTypeSchema.enum.time]: convertTimeDuration,
|
|
1348
|
+
[fitDurationTypeSchema.enum.distance]: convertDistanceDuration,
|
|
1349
|
+
[fitDurationTypeSchema.enum.hrLessThan]: convertHeartRateLessThan,
|
|
1350
|
+
[fitDurationTypeSchema.enum.repeatUntilHrGreaterThan]: convertHeartRateGreaterThan,
|
|
1351
|
+
[fitDurationTypeSchema.enum.calories]: convertCaloriesDuration,
|
|
1352
|
+
[fitDurationTypeSchema.enum.powerLessThan]: convertPowerLessThan,
|
|
1353
|
+
[fitDurationTypeSchema.enum.powerGreaterThan]: convertPowerGreaterThan,
|
|
1354
|
+
[fitDurationTypeSchema.enum.repeatUntilTime]: convertRepeatUntilTime,
|
|
1355
|
+
[fitDurationTypeSchema.enum.repeatUntilDistance]: convertRepeatUntilDistance,
|
|
1356
|
+
[fitDurationTypeSchema.enum.repeatUntilCalories]: convertRepeatUntilCalories,
|
|
1357
|
+
[fitDurationTypeSchema.enum.repeatUntilHrLessThan]: convertRepeatUntilHrLessThan,
|
|
1358
|
+
[fitDurationTypeSchema.enum.repeatUntilPowerLessThan]: convertRepeatUntilPowerLessThan,
|
|
1359
|
+
[fitDurationTypeSchema.enum.repeatUntilPowerGreaterThan]: convertRepeatUntilPowerGreaterThan
|
|
1360
|
+
};
|
|
1361
|
+
var convertFitDuration = (data) => {
|
|
1362
|
+
const result = fitDurationTypeSchema.safeParse(data.durationType);
|
|
1363
|
+
if (!result.success) {
|
|
1364
|
+
return { type: durationTypeSchema.enum.open };
|
|
1365
|
+
}
|
|
1366
|
+
const converter = DURATION_CONVERTERS[result.data];
|
|
1367
|
+
if (converter) {
|
|
1368
|
+
return converter(data) || { type: durationTypeSchema.enum.open };
|
|
1369
|
+
}
|
|
1370
|
+
return { type: durationTypeSchema.enum.open };
|
|
1371
|
+
};
|
|
1372
|
+
|
|
1373
|
+
// src/adapters/duration/duration.mapper.ts
|
|
1374
|
+
var mapDuration = (step) => {
|
|
1375
|
+
return convertFitDuration(step);
|
|
1376
|
+
};
|
|
1377
|
+
var mapDurationType = (fitDurationType) => {
|
|
1378
|
+
if (fitDurationType === fitDurationTypeSchema.enum.time)
|
|
1379
|
+
return durationTypeSchema.enum.time;
|
|
1380
|
+
if (fitDurationType === fitDurationTypeSchema.enum.distance)
|
|
1381
|
+
return durationTypeSchema.enum.distance;
|
|
1382
|
+
if (fitDurationType === fitDurationTypeSchema.enum.hrLessThan)
|
|
1383
|
+
return durationTypeSchema.enum.heart_rate_less_than;
|
|
1384
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilHrGreaterThan)
|
|
1385
|
+
return durationTypeSchema.enum.repeat_until_heart_rate_greater_than;
|
|
1386
|
+
if (fitDurationType === fitDurationTypeSchema.enum.calories)
|
|
1387
|
+
return durationTypeSchema.enum.calories;
|
|
1388
|
+
if (fitDurationType === fitDurationTypeSchema.enum.powerLessThan)
|
|
1389
|
+
return durationTypeSchema.enum.power_less_than;
|
|
1390
|
+
if (fitDurationType === fitDurationTypeSchema.enum.powerGreaterThan)
|
|
1391
|
+
return durationTypeSchema.enum.power_greater_than;
|
|
1392
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilTime)
|
|
1393
|
+
return durationTypeSchema.enum.repeat_until_time;
|
|
1394
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilDistance)
|
|
1395
|
+
return durationTypeSchema.enum.repeat_until_distance;
|
|
1396
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilCalories)
|
|
1397
|
+
return durationTypeSchema.enum.repeat_until_calories;
|
|
1398
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilHrLessThan)
|
|
1399
|
+
return durationTypeSchema.enum.repeat_until_heart_rate_less_than;
|
|
1400
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilPowerLessThan)
|
|
1401
|
+
return durationTypeSchema.enum.repeat_until_power_less_than;
|
|
1402
|
+
if (fitDurationType === fitDurationTypeSchema.enum.repeatUntilPowerGreaterThan)
|
|
1403
|
+
return durationTypeSchema.enum.repeat_until_power_greater_than;
|
|
1404
|
+
return durationTypeSchema.enum.open;
|
|
1405
|
+
};
|
|
1406
|
+
var convertCadenceTarget2 = (data) => {
|
|
1407
|
+
const rangeTarget = buildCadenceRangeTarget(data);
|
|
1408
|
+
if (rangeTarget) return rangeTarget;
|
|
1409
|
+
const zoneTarget = buildCadenceZoneTarget(data);
|
|
1410
|
+
if (zoneTarget) return zoneTarget;
|
|
1411
|
+
const valueTarget = buildCadenceValueTarget(data);
|
|
1412
|
+
if (valueTarget) return valueTarget;
|
|
1413
|
+
return { type: targetTypeSchema.enum.open };
|
|
1414
|
+
};
|
|
1415
|
+
var buildCadenceRangeTarget = (data) => {
|
|
1416
|
+
if (data.customTargetCadenceLow !== void 0 && data.customTargetCadenceHigh !== void 0) {
|
|
1417
|
+
return {
|
|
1418
|
+
type: targetTypeSchema.enum.cadence,
|
|
1419
|
+
value: {
|
|
1420
|
+
unit: targetUnitSchema.enum.range,
|
|
1421
|
+
min: data.customTargetCadenceLow,
|
|
1422
|
+
max: data.customTargetCadenceHigh
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
if (data.customTargetValueLow !== void 0 && data.customTargetValueHigh !== void 0) {
|
|
1427
|
+
return {
|
|
1428
|
+
type: targetTypeSchema.enum.cadence,
|
|
1429
|
+
value: {
|
|
1430
|
+
unit: targetUnitSchema.enum.range,
|
|
1431
|
+
min: data.customTargetValueLow,
|
|
1432
|
+
max: data.customTargetValueHigh
|
|
1433
|
+
}
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
return null;
|
|
1437
|
+
};
|
|
1438
|
+
var buildCadenceZoneTarget = (data) => {
|
|
1439
|
+
if (data.targetCadenceZone !== void 0) {
|
|
1440
|
+
return {
|
|
1441
|
+
type: targetTypeSchema.enum.cadence,
|
|
1442
|
+
value: {
|
|
1443
|
+
unit: targetUnitSchema.enum.rpm,
|
|
1444
|
+
value: data.targetCadenceZone
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
return null;
|
|
1449
|
+
};
|
|
1450
|
+
var buildCadenceValueTarget = (data) => {
|
|
1451
|
+
if (data.targetValue !== void 0) {
|
|
1452
|
+
return {
|
|
1453
|
+
type: targetTypeSchema.enum.cadence,
|
|
1454
|
+
value: {
|
|
1455
|
+
unit: targetUnitSchema.enum.rpm,
|
|
1456
|
+
value: data.targetValue
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
return null;
|
|
1461
|
+
};
|
|
1462
|
+
var convertHeartRateTarget2 = (data) => {
|
|
1463
|
+
const rangeTarget = buildHeartRateRangeTarget(data);
|
|
1464
|
+
if (rangeTarget) return rangeTarget;
|
|
1465
|
+
const zoneTarget = buildHeartRateZoneTarget(data);
|
|
1466
|
+
if (zoneTarget) return zoneTarget;
|
|
1467
|
+
if (data.targetValue !== void 0) {
|
|
1468
|
+
return convertHeartRateValue(data.targetValue);
|
|
1469
|
+
}
|
|
1470
|
+
return { type: targetTypeSchema.enum.open };
|
|
1471
|
+
};
|
|
1472
|
+
var buildHeartRateRangeTarget = (data) => {
|
|
1473
|
+
if (data.customTargetHeartRateLow !== void 0 && data.customTargetHeartRateHigh !== void 0) {
|
|
1474
|
+
return {
|
|
1475
|
+
type: targetTypeSchema.enum.heart_rate,
|
|
1476
|
+
value: {
|
|
1477
|
+
unit: targetUnitSchema.enum.range,
|
|
1478
|
+
min: data.customTargetHeartRateLow,
|
|
1479
|
+
max: data.customTargetHeartRateHigh
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
if (data.customTargetValueLow !== void 0 && data.customTargetValueHigh !== void 0) {
|
|
1484
|
+
return {
|
|
1485
|
+
type: targetTypeSchema.enum.heart_rate,
|
|
1486
|
+
value: {
|
|
1487
|
+
unit: targetUnitSchema.enum.range,
|
|
1488
|
+
min: data.customTargetValueLow,
|
|
1489
|
+
max: data.customTargetValueHigh
|
|
1490
|
+
}
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
return null;
|
|
1494
|
+
};
|
|
1495
|
+
var buildHeartRateZoneTarget = (data) => {
|
|
1496
|
+
if (data.targetHrZone !== void 0) {
|
|
1497
|
+
if (data.targetHrZone >= 1 && data.targetHrZone <= 5) {
|
|
1498
|
+
return {
|
|
1499
|
+
type: targetTypeSchema.enum.heart_rate,
|
|
1500
|
+
value: {
|
|
1501
|
+
unit: targetUnitSchema.enum.zone,
|
|
1502
|
+
value: data.targetHrZone
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
return convertHeartRateValue(data.targetHrZone);
|
|
1507
|
+
}
|
|
1508
|
+
return null;
|
|
1509
|
+
};
|
|
1510
|
+
var convertHeartRateValue = (value) => {
|
|
1511
|
+
if (value > 100) {
|
|
1512
|
+
return {
|
|
1513
|
+
type: targetTypeSchema.enum.heart_rate,
|
|
1514
|
+
value: {
|
|
1515
|
+
unit: targetUnitSchema.enum.bpm,
|
|
1516
|
+
value: value - 100
|
|
1517
|
+
}
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
if (value > 0) {
|
|
1521
|
+
return {
|
|
1522
|
+
type: targetTypeSchema.enum.heart_rate,
|
|
1523
|
+
value: {
|
|
1524
|
+
unit: targetUnitSchema.enum.percent_max,
|
|
1525
|
+
value
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
return { type: targetTypeSchema.enum.open };
|
|
1530
|
+
};
|
|
1531
|
+
var convertPaceTarget2 = (data) => {
|
|
1532
|
+
const rangeTarget = buildPaceRangeTarget(data);
|
|
1533
|
+
if (rangeTarget) return rangeTarget;
|
|
1534
|
+
const zoneTarget = buildPaceZoneTarget(data);
|
|
1535
|
+
if (zoneTarget) return zoneTarget;
|
|
1536
|
+
const valueTarget = buildPaceValueTarget(data);
|
|
1537
|
+
if (valueTarget) return valueTarget;
|
|
1538
|
+
return { type: targetTypeSchema.enum.open };
|
|
1539
|
+
};
|
|
1540
|
+
var buildPaceRangeTarget = (data) => {
|
|
1541
|
+
if (data.customTargetSpeedLow !== void 0 && data.customTargetSpeedHigh !== void 0) {
|
|
1542
|
+
return {
|
|
1543
|
+
type: targetTypeSchema.enum.pace,
|
|
1544
|
+
value: {
|
|
1545
|
+
unit: targetUnitSchema.enum.range,
|
|
1546
|
+
min: data.customTargetSpeedLow,
|
|
1547
|
+
max: data.customTargetSpeedHigh
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
if (data.customTargetValueLow !== void 0 && data.customTargetValueHigh !== void 0) {
|
|
1552
|
+
return {
|
|
1553
|
+
type: targetTypeSchema.enum.pace,
|
|
1554
|
+
value: {
|
|
1555
|
+
unit: targetUnitSchema.enum.range,
|
|
1556
|
+
min: data.customTargetValueLow,
|
|
1557
|
+
max: data.customTargetValueHigh
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
1561
|
+
return null;
|
|
1562
|
+
};
|
|
1563
|
+
var buildPaceZoneTarget = (data) => {
|
|
1564
|
+
if (data.targetSpeedZone !== void 0) {
|
|
1565
|
+
return {
|
|
1566
|
+
type: targetTypeSchema.enum.pace,
|
|
1567
|
+
value: {
|
|
1568
|
+
unit: targetUnitSchema.enum.zone,
|
|
1569
|
+
value: data.targetSpeedZone
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
}
|
|
1573
|
+
return null;
|
|
1574
|
+
};
|
|
1575
|
+
var buildPaceValueTarget = (data) => {
|
|
1576
|
+
if (data.targetValue !== void 0) {
|
|
1577
|
+
return {
|
|
1578
|
+
type: targetTypeSchema.enum.pace,
|
|
1579
|
+
value: {
|
|
1580
|
+
unit: targetUnitSchema.enum.mps,
|
|
1581
|
+
value: data.targetValue
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
return null;
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
// src/adapters/target/power-helpers.ts
|
|
1589
|
+
var interpretWorkoutPower = (value) => {
|
|
1590
|
+
if (value >= 1e3) {
|
|
1591
|
+
return {
|
|
1592
|
+
type: "watts",
|
|
1593
|
+
value: value - 1e3
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
return {
|
|
1597
|
+
type: "percentage",
|
|
1598
|
+
value
|
|
1599
|
+
};
|
|
1600
|
+
};
|
|
1601
|
+
var convertPowerValue = (value) => {
|
|
1602
|
+
if (value > 1e3) {
|
|
1603
|
+
return {
|
|
1604
|
+
unit: "watts",
|
|
1605
|
+
value: value - 1e3
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
if (value > 0) {
|
|
1609
|
+
return {
|
|
1610
|
+
unit: "percent_ftp",
|
|
1611
|
+
value
|
|
1612
|
+
};
|
|
1613
|
+
}
|
|
1614
|
+
return null;
|
|
1615
|
+
};
|
|
1616
|
+
|
|
1617
|
+
// src/adapters/target/target-power.converter.ts
|
|
1618
|
+
var convertPowerTarget2 = (data) => {
|
|
1619
|
+
const rangeTarget = buildPowerRangeTarget(data);
|
|
1620
|
+
if (rangeTarget) return rangeTarget;
|
|
1621
|
+
const zoneTarget = buildPowerZoneTarget(data);
|
|
1622
|
+
if (zoneTarget) return zoneTarget;
|
|
1623
|
+
if (data.targetValue !== void 0) {
|
|
1624
|
+
const powerValue = convertPowerValue(data.targetValue);
|
|
1625
|
+
if (powerValue) {
|
|
1626
|
+
return {
|
|
1627
|
+
type: targetTypeSchema.enum.power,
|
|
1628
|
+
value: powerValue
|
|
1629
|
+
};
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
return { type: targetTypeSchema.enum.open };
|
|
1633
|
+
};
|
|
1634
|
+
var buildPowerRangeTarget = (data) => {
|
|
1635
|
+
if (data.customTargetPowerLow !== void 0 && data.customTargetPowerHigh !== void 0) {
|
|
1636
|
+
const minValue = interpretWorkoutPower(data.customTargetPowerLow);
|
|
1637
|
+
const maxValue = interpretWorkoutPower(data.customTargetPowerHigh);
|
|
1638
|
+
return {
|
|
1639
|
+
type: targetTypeSchema.enum.power,
|
|
1640
|
+
value: {
|
|
1641
|
+
unit: targetUnitSchema.enum.range,
|
|
1642
|
+
min: minValue.value,
|
|
1643
|
+
max: maxValue.value
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
if (data.customTargetValueLow !== void 0 && data.customTargetValueHigh !== void 0) {
|
|
1648
|
+
const minValue = interpretWorkoutPower(data.customTargetValueLow);
|
|
1649
|
+
const maxValue = interpretWorkoutPower(data.customTargetValueHigh);
|
|
1650
|
+
return {
|
|
1651
|
+
type: targetTypeSchema.enum.power,
|
|
1652
|
+
value: {
|
|
1653
|
+
unit: targetUnitSchema.enum.range,
|
|
1654
|
+
min: minValue.value,
|
|
1655
|
+
max: maxValue.value
|
|
1656
|
+
}
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
return null;
|
|
1660
|
+
};
|
|
1661
|
+
var buildPowerZoneTarget = (data) => {
|
|
1662
|
+
if (data.targetPowerZone !== void 0) {
|
|
1663
|
+
return {
|
|
1664
|
+
type: targetTypeSchema.enum.power,
|
|
1665
|
+
value: {
|
|
1666
|
+
unit: targetUnitSchema.enum.zone,
|
|
1667
|
+
value: data.targetPowerZone
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
}
|
|
1671
|
+
return null;
|
|
1672
|
+
};
|
|
1673
|
+
var convertStrokeTypeTarget = (data) => {
|
|
1674
|
+
if (data.targetSwimStroke !== void 0) {
|
|
1675
|
+
return {
|
|
1676
|
+
type: targetTypeSchema.enum.stroke_type,
|
|
1677
|
+
value: {
|
|
1678
|
+
unit: targetUnitSchema.enum.swim_stroke,
|
|
1679
|
+
value: data.targetSwimStroke
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
return { type: targetTypeSchema.enum.open };
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
// src/adapters/target/target.converter.ts
|
|
1687
|
+
var convertFitTarget = (data) => {
|
|
1688
|
+
if (data.targetType === fitTargetTypeSchema.enum.power) {
|
|
1689
|
+
return convertPowerTarget2(data);
|
|
1690
|
+
}
|
|
1691
|
+
if (data.targetType === fitTargetTypeSchema.enum.heartRate) {
|
|
1692
|
+
return convertHeartRateTarget2(data);
|
|
1693
|
+
}
|
|
1694
|
+
if (data.targetType === fitTargetTypeSchema.enum.cadence) {
|
|
1695
|
+
return convertCadenceTarget2(data);
|
|
1696
|
+
}
|
|
1697
|
+
if (data.targetType === fitTargetTypeSchema.enum.speed) {
|
|
1698
|
+
return convertPaceTarget2(data);
|
|
1699
|
+
}
|
|
1700
|
+
if (data.targetType === fitTargetTypeSchema.enum.swimStroke) {
|
|
1701
|
+
return convertStrokeTypeTarget(data);
|
|
1702
|
+
}
|
|
1703
|
+
return { type: targetTypeSchema.enum.open };
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// src/adapters/target/target.mapper.ts
|
|
1707
|
+
var mapTarget = (step) => {
|
|
1708
|
+
return convertFitTarget(step);
|
|
1709
|
+
};
|
|
1710
|
+
var mapTargetType = (fitTargetType) => {
|
|
1711
|
+
if (fitTargetType === fitTargetTypeSchema.enum.power)
|
|
1712
|
+
return targetTypeSchema.enum.power;
|
|
1713
|
+
if (fitTargetType === fitTargetTypeSchema.enum.heartRate)
|
|
1714
|
+
return targetTypeSchema.enum.heart_rate;
|
|
1715
|
+
if (fitTargetType === fitTargetTypeSchema.enum.cadence)
|
|
1716
|
+
return targetTypeSchema.enum.cadence;
|
|
1717
|
+
if (fitTargetType === fitTargetTypeSchema.enum.speed)
|
|
1718
|
+
return targetTypeSchema.enum.pace;
|
|
1719
|
+
if (fitTargetType === fitTargetTypeSchema.enum.swimStroke)
|
|
1720
|
+
return targetTypeSchema.enum.stroke_type;
|
|
1721
|
+
return targetTypeSchema.enum.open;
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
// src/adapters/workout/step.mapper.ts
|
|
1725
|
+
var mapStep = (step, index) => {
|
|
1726
|
+
const duration = mapDuration(step);
|
|
1727
|
+
const target = mapTarget(step);
|
|
1728
|
+
const workoutStep = {
|
|
1729
|
+
stepIndex: step.messageIndex ?? index,
|
|
1730
|
+
name: step.wktStepName,
|
|
1731
|
+
durationType: mapDurationType(step.durationType),
|
|
1732
|
+
duration,
|
|
1733
|
+
targetType: mapTargetType(step.targetType),
|
|
1734
|
+
target,
|
|
1735
|
+
intensity: mapIntensity(step.intensity)
|
|
1736
|
+
};
|
|
1737
|
+
if (step.notes !== void 0) {
|
|
1738
|
+
workoutStep.notes = step.notes;
|
|
1739
|
+
}
|
|
1740
|
+
if (step.equipment !== void 0) {
|
|
1741
|
+
workoutStep.equipment = mapEquipmentToKrd(step.equipment);
|
|
1742
|
+
}
|
|
1743
|
+
return workoutStep;
|
|
1744
|
+
};
|
|
1745
|
+
var mapIntensity = (intensity) => {
|
|
1746
|
+
if (!intensity) return void 0;
|
|
1747
|
+
const normalized = intensity.toLowerCase();
|
|
1748
|
+
const validIntensities = intensitySchema.options;
|
|
1749
|
+
if (validIntensities.includes(normalized)) {
|
|
1750
|
+
return normalized;
|
|
1751
|
+
}
|
|
1752
|
+
return void 0;
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// src/adapters/workout/repetition.builder.ts
|
|
1756
|
+
var findRepetitionStepIndices = (workoutSteps) => {
|
|
1757
|
+
const indices = /* @__PURE__ */ new Set();
|
|
1758
|
+
for (let i = 0; i < workoutSteps.length; i++) {
|
|
1759
|
+
const step = workoutSteps[i];
|
|
1760
|
+
if (step.durationType === fitDurationTypeSchema.enum.repeatUntilStepsCmplt) {
|
|
1761
|
+
const startIndex = step.durationStep || 0;
|
|
1762
|
+
for (let j = startIndex; j < i; j++) {
|
|
1763
|
+
indices.add(j);
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
return indices;
|
|
1768
|
+
};
|
|
1769
|
+
var buildWorkoutSteps = (workoutSteps, repetitionStepIndices) => {
|
|
1770
|
+
const steps = [];
|
|
1771
|
+
for (let i = 0; i < workoutSteps.length; i++) {
|
|
1772
|
+
const step = workoutSteps[i];
|
|
1773
|
+
if (step.durationType === fitDurationTypeSchema.enum.repeatUntilStepsCmplt && step.repeatSteps) {
|
|
1774
|
+
const repetitionBlock = buildRepetitionBlock(step, workoutSteps, i);
|
|
1775
|
+
steps.push(repetitionBlock);
|
|
1776
|
+
} else if (!repetitionStepIndices.has(i)) {
|
|
1777
|
+
steps.push(mapStep(step, i));
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
return steps;
|
|
1781
|
+
};
|
|
1782
|
+
var buildRepetitionBlock = (step, workoutSteps, currentIndex) => {
|
|
1783
|
+
const repeatCount = step.repeatSteps;
|
|
1784
|
+
const startIndex = step.durationStep || 0;
|
|
1785
|
+
const repeatedSteps = [];
|
|
1786
|
+
for (let j = startIndex; j < currentIndex; j++) {
|
|
1787
|
+
repeatedSteps.push(mapStep(workoutSteps[j], j));
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
repeatCount,
|
|
1791
|
+
steps: repeatedSteps
|
|
1792
|
+
};
|
|
1793
|
+
};
|
|
1794
|
+
|
|
1795
|
+
// src/adapters/workout/workout.mapper.ts
|
|
1796
|
+
var mapWorkout = (workoutMsg, workoutSteps, logger) => {
|
|
1797
|
+
logger.debug("Mapping workout steps", {
|
|
1798
|
+
stepCount: workoutSteps.length
|
|
1799
|
+
});
|
|
1800
|
+
const repetitionStepIndices = findRepetitionStepIndices(workoutSteps);
|
|
1801
|
+
const steps = buildWorkoutSteps(workoutSteps, repetitionStepIndices);
|
|
1802
|
+
const workout = {
|
|
1803
|
+
name: workoutMsg?.wktName,
|
|
1804
|
+
sport: mapSportType(workoutMsg?.sport),
|
|
1805
|
+
steps
|
|
1806
|
+
};
|
|
1807
|
+
if (workoutMsg?.subSport !== void 0) {
|
|
1808
|
+
workout.subSport = mapSubSportToKrd(workoutMsg.subSport);
|
|
1809
|
+
}
|
|
1810
|
+
if (workoutMsg?.poolLength !== void 0) {
|
|
1811
|
+
const unit = mapLengthUnitToKrd(workoutMsg.poolLengthUnit);
|
|
1812
|
+
workout.poolLength = convertLengthToMeters(workoutMsg.poolLength, unit);
|
|
1813
|
+
workout.poolLengthUnit = "meters";
|
|
1814
|
+
}
|
|
1815
|
+
return workout;
|
|
1816
|
+
};
|
|
1817
|
+
var validateMessages = (fileId, workoutMsg, messages, logger, options = {}) => {
|
|
1818
|
+
const { strict = true } = options;
|
|
1819
|
+
if (!fileId) {
|
|
1820
|
+
const message = "Missing required fileId message in FIT file";
|
|
1821
|
+
if (strict) {
|
|
1822
|
+
throw createFitParsingError(message);
|
|
1823
|
+
}
|
|
1824
|
+
logger.warn(message);
|
|
1825
|
+
}
|
|
1826
|
+
if (!workoutMsg) {
|
|
1827
|
+
const message = "Missing required workout message in FIT file";
|
|
1828
|
+
if (strict) {
|
|
1829
|
+
throw createFitParsingError(message);
|
|
1830
|
+
}
|
|
1831
|
+
logger.warn(message);
|
|
1832
|
+
}
|
|
1833
|
+
const workoutMessages = messages[fitMessageKeySchema.enum.workoutMesgs];
|
|
1834
|
+
if (workoutMessages && workoutMessages.length > 1) {
|
|
1835
|
+
logger.warn("Multiple workout messages found, using first one", {
|
|
1836
|
+
count: workoutMessages.length
|
|
1837
|
+
});
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
|
|
1841
|
+
// src/adapters/messages/workout.mapper.ts
|
|
1842
|
+
var KRD_VERSION2 = "1.0";
|
|
1843
|
+
var mapWorkoutFileToKRD = (messages, logger) => {
|
|
1844
|
+
const fileId = messages[fitMessageKeySchema.enum.fileIdMesgs]?.[0];
|
|
1845
|
+
const workoutMsg = messages[fitMessageKeySchema.enum.workoutMesgs]?.[0];
|
|
1846
|
+
const workoutSteps = messages[fitMessageKeySchema.enum.workoutStepMesgs] || [];
|
|
1847
|
+
validateMessages(fileId, workoutMsg, messages, logger);
|
|
1848
|
+
const metadata = mapMetadata(fileId, workoutMsg, logger);
|
|
1849
|
+
const workout = mapWorkout(workoutMsg, workoutSteps, logger);
|
|
1850
|
+
const fitExtensions = extractFitExtensions(messages, logger);
|
|
1851
|
+
return {
|
|
1852
|
+
version: KRD_VERSION2,
|
|
1853
|
+
type: fileTypeSchema.enum.structured_workout,
|
|
1854
|
+
metadata,
|
|
1855
|
+
extensions: { structured_workout: workout, fit: fitExtensions }
|
|
1856
|
+
};
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
// src/adapters/messages/messages.mapper.ts
|
|
1860
|
+
var detectFileType = (messages) => {
|
|
1861
|
+
const workoutMsgs = messages[fitMessageKeySchema.enum.workoutMesgs];
|
|
1862
|
+
if (workoutMsgs && workoutMsgs.length > 0)
|
|
1863
|
+
return fileTypeSchema.enum.structured_workout;
|
|
1864
|
+
const sessionMsgs = messages[fitMessageKeySchema.enum.sessionMesgs];
|
|
1865
|
+
const recordMsgs = messages[fitMessageKeySchema.enum.recordMesgs];
|
|
1866
|
+
if (sessionMsgs && sessionMsgs.length > 0 || recordMsgs && recordMsgs.length > 0) {
|
|
1867
|
+
return fileTypeSchema.enum.recorded_activity;
|
|
1868
|
+
}
|
|
1869
|
+
return fileTypeSchema.enum.structured_workout;
|
|
1870
|
+
};
|
|
1871
|
+
var mapMessagesToKRD = (messages, logger) => {
|
|
1872
|
+
logger.debug("Mapping FIT messages to KRD", {
|
|
1873
|
+
messageCount: Object.keys(messages).length
|
|
1874
|
+
});
|
|
1875
|
+
const fileType = detectFileType(messages);
|
|
1876
|
+
logger.debug("Detected file type", { fileType });
|
|
1877
|
+
switch (fileType) {
|
|
1878
|
+
case fileTypeSchema.enum.recorded_activity:
|
|
1879
|
+
return mapActivityFileToKRD(messages, logger);
|
|
1880
|
+
case fileTypeSchema.enum.structured_workout:
|
|
1881
|
+
default:
|
|
1882
|
+
return mapWorkoutFileToKRD(messages, logger);
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
// src/adapters/garmin-fitsdk.ts
|
|
1887
|
+
var createGarminFitSdkReader = (logger) => async (buffer) => {
|
|
1888
|
+
try {
|
|
1889
|
+
logger.debug("Parsing FIT file", { bufferSize: buffer.length });
|
|
1890
|
+
if (buffer.length === 0) {
|
|
1891
|
+
logger.error("Empty FIT buffer");
|
|
1892
|
+
throw createFitParsingError("Cannot parse empty FIT buffer");
|
|
1893
|
+
}
|
|
1894
|
+
const stream = Stream.fromByteArray(Array.from(buffer));
|
|
1895
|
+
const decoder = new Decoder(stream);
|
|
1896
|
+
const { messages, errors } = decoder.read();
|
|
1897
|
+
if (errors.length > 0) {
|
|
1898
|
+
logger.error("FIT parsing errors detected", { errors });
|
|
1899
|
+
throw createFitParsingError(`FIT parsing errors: ${errors.join(", ")}`);
|
|
1900
|
+
}
|
|
1901
|
+
logger.info("FIT file parsed successfully");
|
|
1902
|
+
return mapMessagesToKRD(messages, logger);
|
|
1903
|
+
} catch (error) {
|
|
1904
|
+
if (error instanceof Error && error.name === "FitParsingError") {
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
logger.error("Failed to parse FIT file", { error });
|
|
1908
|
+
throw createFitParsingError("Failed to parse FIT file", error);
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
var createGarminFitSdkWriter = (logger) => async (krd) => {
|
|
1912
|
+
try {
|
|
1913
|
+
logger.debug("Encoding KRD to FIT");
|
|
1914
|
+
const encoder = new Encoder();
|
|
1915
|
+
const messages = convertKRDToMessages(krd, logger);
|
|
1916
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1917
|
+
const message = messages[i];
|
|
1918
|
+
try {
|
|
1919
|
+
logger.debug(`Writing message ${i + 1}/${messages.length}`, {
|
|
1920
|
+
mesgNum: message.mesgNum
|
|
1921
|
+
});
|
|
1922
|
+
encoder.writeMesg(message);
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
logger.error(`Failed to write message ${i + 1}`, {
|
|
1925
|
+
message: JSON.stringify(message, null, 2),
|
|
1926
|
+
error
|
|
1927
|
+
});
|
|
1928
|
+
throw error;
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
const buffer = encoder.close();
|
|
1932
|
+
logger.info("KRD encoded to FIT successfully");
|
|
1933
|
+
return new Uint8Array(buffer);
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
if (error instanceof Error && error.name === "FitParsingError") {
|
|
1936
|
+
throw error;
|
|
1937
|
+
}
|
|
1938
|
+
logger.error("Failed to write FIT file", { error });
|
|
1939
|
+
throw createFitParsingError("Failed to write FIT file", error);
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
// src/providers.ts
|
|
1944
|
+
var createFitProviders = (logger) => {
|
|
1945
|
+
const log = logger || createConsoleLogger();
|
|
1946
|
+
return {
|
|
1947
|
+
fitReader: createGarminFitSdkReader(log),
|
|
1948
|
+
fitWriter: createGarminFitSdkWriter(log)
|
|
1949
|
+
};
|
|
1950
|
+
};
|
|
1951
|
+
|
|
1952
|
+
export { createFitProviders, createGarminFitSdkReader, createGarminFitSdkWriter };
|
|
1953
|
+
//# sourceMappingURL=index.js.map
|
|
1954
|
+
//# sourceMappingURL=index.js.map
|