@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/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