@kaiord/zwo 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,1202 @@
1
+ import { createZwiftParsingError, createConsoleLogger, createZwiftValidationError, intensitySchema, targetTypeSchema, targetUnitSchema, durationTypeSchema } from '@kaiord/core';
2
+ import { XMLParser, XMLValidator, XMLBuilder } from 'fast-xml-parser';
3
+
4
+ // src/providers.ts
5
+ var detectIntervalType = (step) => {
6
+ if (step.target.type === targetTypeSchema.enum.open) {
7
+ return "FreeRide";
8
+ }
9
+ if (step.target.type === targetTypeSchema.enum.power) {
10
+ const powerValue = step.target.value;
11
+ if (powerValue.unit === targetUnitSchema.enum.range) {
12
+ if (step.intensity === intensitySchema.enum.warmup) {
13
+ return "Warmup";
14
+ }
15
+ if (step.intensity === intensitySchema.enum.cooldown) {
16
+ return "Cooldown";
17
+ }
18
+ return "Ramp";
19
+ }
20
+ if (powerValue.unit === targetUnitSchema.enum.percent_ftp || powerValue.unit === targetUnitSchema.enum.watts) {
21
+ return "SteadyState";
22
+ }
23
+ }
24
+ return "SteadyState";
25
+ };
26
+
27
+ // src/adapters/krd-to-zwift/text-events-encoder.ts
28
+ var encodeTextEvents = (step) => {
29
+ const stepExtensions = step.extensions?.zwift;
30
+ const textEvents = stepExtensions?.textEvents;
31
+ if (!textEvents || textEvents.length === 0) {
32
+ return void 0;
33
+ }
34
+ if (textEvents.length === 1) {
35
+ const event = textEvents[0];
36
+ const encoded = {
37
+ "@_message": event.message
38
+ };
39
+ if (event.timeoffset !== void 0) {
40
+ encoded["@_timeoffset"] = event.timeoffset;
41
+ }
42
+ if (event.distoffset !== void 0) {
43
+ encoded["@_distoffset"] = event.distoffset;
44
+ }
45
+ return encoded;
46
+ }
47
+ return textEvents.map((event) => {
48
+ const encoded = {
49
+ "@_message": event.message
50
+ };
51
+ if (event.timeoffset !== void 0) {
52
+ encoded["@_timeoffset"] = event.timeoffset;
53
+ }
54
+ if (event.distoffset !== void 0) {
55
+ encoded["@_distoffset"] = event.distoffset;
56
+ }
57
+ return encoded;
58
+ });
59
+ };
60
+
61
+ // src/adapters/krd-to-zwift/intervals-t-encoder.ts
62
+ var encodeDurations = (onStep, offStep, intervalsT) => {
63
+ if (onStep.duration.type === "time") {
64
+ intervalsT["@_OnDuration"] = onStep.duration.seconds;
65
+ } else if (onStep.duration.type === "distance") {
66
+ intervalsT["@_OnDuration"] = onStep.duration.meters;
67
+ }
68
+ if (offStep.duration.type === "time") {
69
+ intervalsT["@_OffDuration"] = offStep.duration.seconds;
70
+ } else if (offStep.duration.type === "distance") {
71
+ intervalsT["@_OffDuration"] = offStep.duration.meters;
72
+ }
73
+ };
74
+ var encodePowerTargets = (onStep, offStep, intervalsT) => {
75
+ if (onStep.target.type === "power" && onStep.target.value.unit === "percent_ftp") {
76
+ intervalsT["@_OnPower"] = onStep.target.value.value / 100;
77
+ }
78
+ if (offStep.target.type === "power" && offStep.target.value.unit === "percent_ftp") {
79
+ intervalsT["@_OffPower"] = offStep.target.value.value / 100;
80
+ }
81
+ };
82
+ var encodeCadenceTargets = (onStep, offStep, intervalsT) => {
83
+ if (onStep.target.type === "cadence" && onStep.target.value.unit === "rpm") {
84
+ intervalsT["@_Cadence"] = onStep.target.value.value;
85
+ } else {
86
+ const onStepExtensions = onStep.extensions?.zwift;
87
+ const cadence = onStepExtensions?.cadence;
88
+ if (cadence !== void 0) {
89
+ intervalsT["@_Cadence"] = cadence;
90
+ }
91
+ }
92
+ if (offStep.target.type === "cadence" && offStep.target.value.unit === "rpm") {
93
+ intervalsT["@_CadenceResting"] = offStep.target.value.value;
94
+ } else {
95
+ const offStepExtensions = offStep.extensions?.zwift;
96
+ const cadenceResting = offStepExtensions?.cadence;
97
+ if (cadenceResting !== void 0) {
98
+ intervalsT["@_CadenceResting"] = cadenceResting;
99
+ }
100
+ }
101
+ };
102
+ var encodeIntervalsT = (repetitionBlock) => {
103
+ const onStep = repetitionBlock.steps[0];
104
+ const offStep = repetitionBlock.steps[1];
105
+ const intervalsT = {
106
+ "@_Repeat": repetitionBlock.repeatCount
107
+ };
108
+ encodeDurations(onStep, offStep, intervalsT);
109
+ encodePowerTargets(onStep, offStep, intervalsT);
110
+ encodeCadenceTargets(onStep, offStep, intervalsT);
111
+ const textEvents = encodeTextEvents(onStep);
112
+ if (textEvents) {
113
+ intervalsT.textevent = textEvents;
114
+ }
115
+ return intervalsT;
116
+ };
117
+
118
+ // src/adapters/krd-to-zwift/duration-encoder.ts
119
+ var encodeDuration = (step, interval, logger) => {
120
+ if (step.duration.type === "time") {
121
+ interval["@_Duration"] = step.duration.seconds;
122
+ } else if (step.duration.type === "distance") {
123
+ interval["@_Duration"] = step.duration.meters;
124
+ interval["@_kaiord:originalDurationType"] = "distance";
125
+ interval["@_kaiord:originalDurationMeters"] = step.duration.meters;
126
+ logger?.warn("Lossy conversion: distance duration converted to time", {
127
+ originalMeters: step.duration.meters,
128
+ convertedSeconds: step.duration.meters,
129
+ stepIndex: step.stepIndex
130
+ });
131
+ } else if (step.duration.type === "open") {
132
+ interval["@_Duration"] = 0;
133
+ } else {
134
+ interval["@_Duration"] = 300;
135
+ interval["@_kaiord:originalDurationType"] = step.duration.type;
136
+ if ("bpm" in step.duration) {
137
+ interval["@_kaiord:originalDurationBpm"] = step.duration.bpm;
138
+ } else if ("watts" in step.duration) {
139
+ interval["@_kaiord:originalDurationWatts"] = step.duration.watts;
140
+ }
141
+ logger?.warn("Lossy conversion: unsupported duration type", {
142
+ originalType: step.duration.type,
143
+ fallbackSeconds: 300,
144
+ stepIndex: step.stepIndex
145
+ });
146
+ }
147
+ };
148
+
149
+ // src/adapters/krd-to-zwift/metadata-encoder.ts
150
+ var encodeHrRange = (step, interval, logger) => {
151
+ if (step.target.type === "heart_rate" && step.target.value.unit === "range") {
152
+ interval["@_kaiord:hrTargetLow"] = step.target.value.min;
153
+ interval["@_kaiord:hrTargetHigh"] = step.target.value.max;
154
+ logger?.warn("Lossy conversion: heart rate target not supported by Zwift", {
155
+ hrRange: { low: step.target.value.min, high: step.target.value.max },
156
+ stepIndex: step.stepIndex
157
+ });
158
+ }
159
+ };
160
+ var encodeHrBpm = (step, interval, logger) => {
161
+ if (step.target.type === "heart_rate" && step.target.value.unit === "bpm") {
162
+ interval["@_kaiord:hrTargetBpm"] = step.target.value.value;
163
+ logger?.warn("Lossy conversion: heart rate target not supported by Zwift", {
164
+ hrBpm: step.target.value.value,
165
+ stepIndex: step.stepIndex
166
+ });
167
+ }
168
+ };
169
+ var encodeHrZone = (step, interval, logger) => {
170
+ if (step.target.type === "heart_rate" && step.target.value.unit === "zone") {
171
+ interval["@_kaiord:hrTargetZone"] = step.target.value.value;
172
+ logger?.warn("Lossy conversion: heart rate target not supported by Zwift", {
173
+ hrZone: step.target.value.value,
174
+ stepIndex: step.stepIndex
175
+ });
176
+ }
177
+ };
178
+ var encodeHrPercentMax = (step, interval, logger) => {
179
+ if (step.target.type === "heart_rate" && step.target.value.unit === "percent_max") {
180
+ interval["@_kaiord:hrTargetPercentMax"] = step.target.value.value;
181
+ logger?.warn("Lossy conversion: heart rate target not supported by Zwift", {
182
+ hrPercentMax: step.target.value.value,
183
+ stepIndex: step.stepIndex
184
+ });
185
+ }
186
+ };
187
+ var encodeHeartRateTarget = (step, interval, logger) => {
188
+ encodeHrRange(step, interval, logger);
189
+ encodeHrBpm(step, interval, logger);
190
+ encodeHrZone(step, interval, logger);
191
+ encodeHrPercentMax(step, interval, logger);
192
+ };
193
+ var encodeMetadata = (step, interval) => {
194
+ if (step.name) {
195
+ interval["@_kaiord:name"] = step.name;
196
+ }
197
+ if (step.intensity) {
198
+ interval["@_kaiord:intensity"] = step.intensity;
199
+ }
200
+ if (step.equipment) {
201
+ interval["@_kaiord:equipment"] = step.equipment;
202
+ }
203
+ };
204
+ var convertZwiftPowerTarget = (ftpPercentage) => {
205
+ return {
206
+ type: targetTypeSchema.enum.power,
207
+ value: {
208
+ unit: "percent_ftp",
209
+ value: ftpPercentage * 100
210
+ }
211
+ };
212
+ };
213
+ var convertZwiftPowerRange = (powerLow, powerHigh) => {
214
+ return {
215
+ type: targetTypeSchema.enum.power,
216
+ value: {
217
+ unit: "range",
218
+ min: powerLow * 100,
219
+ max: powerHigh * 100
220
+ }
221
+ };
222
+ };
223
+ var convertPowerZoneToPercentFtp = (zone) => {
224
+ const zoneMap = {
225
+ 1: 55,
226
+ 2: 75,
227
+ 3: 90,
228
+ 4: 105,
229
+ 5: 120,
230
+ 6: 150,
231
+ 7: 200
232
+ };
233
+ return zoneMap[zone] || 100;
234
+ };
235
+ var convertZwiftCadenceTarget = (cadence, isRunning = false) => {
236
+ const rpm = isRunning ? cadence / 2 : cadence;
237
+ return {
238
+ type: targetTypeSchema.enum.cadence,
239
+ value: {
240
+ unit: "rpm",
241
+ value: rpm
242
+ }
243
+ };
244
+ };
245
+
246
+ // src/adapters/krd-to-zwift/power-encoder.ts
247
+ var encodeSteadyStatePowerTarget = (step, interval) => {
248
+ if (step.target.type !== "power") return;
249
+ if (step.target.value.unit === "percent_ftp") {
250
+ interval["@_kaiord:powerUnit"] = "percent_ftp";
251
+ interval["@_Power"] = step.target.value.value / 100;
252
+ } else if (step.target.value.unit === "zone") {
253
+ interval["@_kaiord:powerUnit"] = "zone";
254
+ interval["@_kaiord:powerZone"] = step.target.value.value;
255
+ const percentFtp = convertPowerZoneToPercentFtp(step.target.value.value);
256
+ interval["@_Power"] = percentFtp / 100;
257
+ } else if (step.target.value.unit === "watts") {
258
+ interval["@_kaiord:powerUnit"] = "watts";
259
+ interval["@_kaiord:originalWatts"] = step.target.value.value;
260
+ const assumedFtp = 250;
261
+ interval["@_kaiord:assumedFtp"] = assumedFtp;
262
+ const percentFtp = step.target.value.value / assumedFtp * 100;
263
+ interval["@_Power"] = percentFtp / 100;
264
+ }
265
+ };
266
+ var encodeRampPowerTarget = (step, interval, logger) => {
267
+ if (step.target.type !== "power") return;
268
+ if (step.target.value.unit === "range") {
269
+ let powerLow = step.target.value.min;
270
+ let powerHigh = step.target.value.max;
271
+ interval["@_kaiord:powerUnit"] = "watts";
272
+ const assumedFtp = 250;
273
+ const originalLow = powerLow;
274
+ const originalHigh = powerHigh;
275
+ powerLow = powerLow / assumedFtp * 100;
276
+ powerHigh = powerHigh / assumedFtp * 100;
277
+ interval["@_kaiord:originalWattsLow"] = originalLow;
278
+ interval["@_kaiord:originalWattsHigh"] = originalHigh;
279
+ interval["@_kaiord:assumedFtp"] = assumedFtp;
280
+ logger?.warn("Lossy conversion: watts converted to percent FTP", {
281
+ originalWatts: { low: originalLow, high: originalHigh },
282
+ assumedFtp,
283
+ convertedPercentFtp: { low: powerLow, high: powerHigh },
284
+ stepIndex: step.stepIndex
285
+ });
286
+ interval["@_PowerLow"] = powerLow / 100;
287
+ interval["@_PowerHigh"] = powerHigh / 100;
288
+ } else if (step.target.value.unit === "zone") {
289
+ interval["@_kaiord:powerUnit"] = "zone";
290
+ interval["@_kaiord:powerZone"] = step.target.value.value;
291
+ const percentFtp = convertPowerZoneToPercentFtp(step.target.value.value);
292
+ interval["@_PowerLow"] = percentFtp / 100;
293
+ interval["@_PowerHigh"] = percentFtp / 100;
294
+ } else if (step.target.value.unit === "percent_ftp") {
295
+ interval["@_kaiord:powerUnit"] = "percent_ftp";
296
+ interval["@_PowerLow"] = step.target.value.value / 100;
297
+ interval["@_PowerHigh"] = step.target.value.value / 100;
298
+ }
299
+ };
300
+
301
+ // src/adapters/krd-to-zwift/target-encoder.ts
302
+ var encodeSteadyStateTargets = (step, interval) => {
303
+ encodeSteadyStatePowerTarget(step, interval);
304
+ if (step.target.type === "pace" && step.target.value.unit === "mps") {
305
+ interval["@_pace"] = 1e3 / step.target.value.value;
306
+ }
307
+ };
308
+ var encodeRampTargets = (step, interval, logger) => {
309
+ encodeRampPowerTarget(step, interval, logger);
310
+ if (step.target.type === "pace" && step.target.value.unit === "range") {
311
+ interval["@_paceLow"] = 1e3 / step.target.value.max;
312
+ interval["@_paceHigh"] = 1e3 / step.target.value.min;
313
+ }
314
+ };
315
+ var encodeFreeRideTargets = (step, interval) => {
316
+ const stepExtensions = step.extensions?.zwift;
317
+ const flatRoad = stepExtensions?.flatRoad || stepExtensions?.FlatRoad;
318
+ if (flatRoad !== void 0) {
319
+ interval["@_FlatRoad"] = flatRoad;
320
+ }
321
+ };
322
+ var encodeTargets = (step, intervalType, interval, logger) => {
323
+ if (intervalType === "SteadyState") {
324
+ encodeSteadyStateTargets(step, interval);
325
+ } else if (intervalType === "Warmup" || intervalType === "Ramp" || intervalType === "Cooldown") {
326
+ encodeRampTargets(step, interval, logger);
327
+ } else if (intervalType === "FreeRide") {
328
+ encodeFreeRideTargets(step, interval);
329
+ }
330
+ };
331
+ var encodeCadence = (step, interval) => {
332
+ if (step.target.type === "cadence" && step.target.value.unit === "rpm") {
333
+ interval["@_Cadence"] = step.target.value.value;
334
+ }
335
+ };
336
+
337
+ // src/adapters/krd-to-zwift/step-encoder.ts
338
+ var convertStepToInterval = (step, intervalType, logger) => {
339
+ const interval = {};
340
+ encodeDuration(step, interval, logger);
341
+ encodeTargets(step, intervalType, interval, logger);
342
+ encodeCadence(step, interval);
343
+ encodeHeartRateTarget(step, interval, logger);
344
+ encodeMetadata(step, interval);
345
+ const textEvents = encodeTextEvents(step);
346
+ if (textEvents) {
347
+ interval.textevent = textEvents;
348
+ }
349
+ return interval;
350
+ };
351
+
352
+ // src/adapters/krd-to-zwift/intervals-encoder.ts
353
+ var convertStepsToZwiftIntervals = (steps, logger) => {
354
+ const intervals = {};
355
+ for (const step of steps) {
356
+ if ("repeatCount" in step) {
357
+ const repetitionBlock = step;
358
+ if (repetitionBlock.steps.length === 2) {
359
+ const intervalsT = encodeIntervalsT(repetitionBlock);
360
+ if (!intervals.IntervalsT) {
361
+ intervals.IntervalsT = [];
362
+ }
363
+ intervals.IntervalsT.push(intervalsT);
364
+ }
365
+ } else {
366
+ const workoutStep = step;
367
+ const intervalType = detectIntervalType(workoutStep);
368
+ const interval = convertStepToInterval(workoutStep, intervalType, logger);
369
+ if (!intervals[intervalType]) {
370
+ intervals[intervalType] = [];
371
+ }
372
+ intervals[intervalType].push(interval);
373
+ }
374
+ }
375
+ return intervals;
376
+ };
377
+
378
+ // src/adapters/krd-to-zwift/metadata-builder.ts
379
+ var addKrdMetadata = (workoutFile, metadata, fitExtensions) => {
380
+ if (metadata.created) {
381
+ workoutFile["@_kaiord:timeCreated"] = metadata.created;
382
+ }
383
+ if (metadata.manufacturer) {
384
+ workoutFile["@_kaiord:manufacturer"] = metadata.manufacturer;
385
+ }
386
+ if (metadata.product) {
387
+ workoutFile["@_kaiord:product"] = metadata.product;
388
+ }
389
+ if (metadata.serialNumber) {
390
+ workoutFile["@_kaiord:serialNumber"] = metadata.serialNumber;
391
+ }
392
+ if (fitExtensions) {
393
+ if (fitExtensions.type) {
394
+ workoutFile["@_kaiord:fitType"] = fitExtensions.type;
395
+ }
396
+ if (fitExtensions.hrm_fit_single_byte_product_id) {
397
+ workoutFile["@_kaiord:hrmFitProductId"] = fitExtensions.hrm_fit_single_byte_product_id;
398
+ }
399
+ }
400
+ };
401
+
402
+ // src/adapters/krd-to-zwift/workout-properties.ts
403
+ var addWorkoutProperties = (workoutFile, workoutName, zwiftExtensions) => {
404
+ if (zwiftExtensions.author) {
405
+ workoutFile.author = zwiftExtensions.author;
406
+ }
407
+ if (workoutName) {
408
+ workoutFile.name = workoutName;
409
+ }
410
+ if (zwiftExtensions.description) {
411
+ workoutFile.description = zwiftExtensions.description;
412
+ }
413
+ if (zwiftExtensions.thresholdSecPerKm !== void 0) {
414
+ workoutFile.thresholdSecPerKm = zwiftExtensions.thresholdSecPerKm;
415
+ }
416
+ const tags = zwiftExtensions.tags;
417
+ if (tags && tags.length > 0) {
418
+ workoutFile.tags = {
419
+ tag: tags.map((name) => ({ "@_name": name })).filter((t) => t["@_name"])
420
+ };
421
+ }
422
+ };
423
+ var mapSportType = (sport) => {
424
+ return sport === "cycling" ? "bike" : sport === "running" ? "run" : "bike";
425
+ };
426
+
427
+ // src/adapters/krd-to-zwift/workout-file-builder.ts
428
+ var buildWorkoutFile = (workoutData, zwiftExtensions, metadata, fitExtensions, logger) => {
429
+ const workoutFile = {};
430
+ addWorkoutProperties(workoutFile, workoutData.name, zwiftExtensions);
431
+ workoutFile.sportType = mapSportType(workoutData.sport);
432
+ const intervals = convertStepsToZwiftIntervals(
433
+ workoutData.steps || [],
434
+ logger
435
+ );
436
+ workoutFile.workout = intervals;
437
+ addKrdMetadata(workoutFile, metadata, fitExtensions);
438
+ return workoutFile;
439
+ };
440
+
441
+ // src/adapters/krd-to-zwift.converter.ts
442
+ var extractWorkoutData = (krd) => {
443
+ const workout = krd.extensions?.structured_workout;
444
+ if (!workout || typeof workout !== "object") {
445
+ throw createZwiftParsingError("KRD missing workout in extensions");
446
+ }
447
+ return workout;
448
+ };
449
+ var buildXmlString = (workoutFile) => {
450
+ const builder = new XMLBuilder({
451
+ ignoreAttributes: false,
452
+ attributeNamePrefix: "@_",
453
+ format: true,
454
+ indentBy: " "
455
+ });
456
+ const workoutFileWithNamespace = {
457
+ "@_xmlns:kaiord": "http://kaiord.dev/zwift-extensions/1.0",
458
+ ...workoutFile
459
+ };
460
+ const xmlObj = {
461
+ "?xml": {
462
+ "@_version": "1.0",
463
+ "@_encoding": "UTF-8"
464
+ },
465
+ workout_file: workoutFileWithNamespace
466
+ };
467
+ return builder.build(xmlObj);
468
+ };
469
+ var convertKRDToZwift = (krd, logger) => {
470
+ logger.debug("Building Zwift workout structure from KRD");
471
+ const workoutData = extractWorkoutData(krd);
472
+ const zwiftExtensions = krd.extensions?.zwift || {};
473
+ const workoutFile = buildWorkoutFile(
474
+ workoutData,
475
+ zwiftExtensions,
476
+ krd.metadata,
477
+ krd.extensions?.fit,
478
+ logger
479
+ );
480
+ const xmlString = buildXmlString(workoutFile);
481
+ logger.debug("Zwift XML structure built successfully");
482
+ return xmlString;
483
+ };
484
+ var validateInputZwiftXml = async (xmlString, validator, logger) => {
485
+ logger.debug("Validating Zwift file against XSD", {
486
+ xmlLength: xmlString.length
487
+ });
488
+ const validationResult = await validator(xmlString);
489
+ if (!validationResult.valid) {
490
+ logger.error("Zwift file does not conform to XSD schema", {
491
+ errors: validationResult.errors
492
+ });
493
+ throw createZwiftValidationError(
494
+ "Zwift file does not conform to XSD schema",
495
+ validationResult.errors
496
+ );
497
+ }
498
+ };
499
+ var validateGeneratedZwiftXml = async (xmlString, validator, logger) => {
500
+ logger.debug("Validating generated Zwift XML against XSD", {
501
+ xmlLength: xmlString.length
502
+ });
503
+ const validationResult = await validator(xmlString);
504
+ if (!validationResult.valid) {
505
+ logger.error("Generated Zwift XML does not conform to XSD schema", {
506
+ errors: validationResult.errors
507
+ });
508
+ throw createZwiftValidationError(
509
+ "Generated Zwift XML does not conform to XSD schema",
510
+ validationResult.errors
511
+ );
512
+ }
513
+ };
514
+ var validateZwiftStructure = (zwiftData, logger) => {
515
+ if (!zwiftData || typeof zwiftData !== "object" || !("workout_file" in zwiftData)) {
516
+ const error = createZwiftParsingError(
517
+ "Invalid Zwift format: missing workout_file element"
518
+ );
519
+ logger.error("Invalid Zwift structure", { error });
520
+ throw error;
521
+ }
522
+ };
523
+
524
+ // src/adapters/zwift-to-krd/intervals-extractor.ts
525
+ var extractIntervals = (workout) => {
526
+ if (!workout) return [];
527
+ const intervals = [];
528
+ const intervalTypes = [
529
+ "SteadyState",
530
+ "Warmup",
531
+ "Ramp",
532
+ "Cooldown",
533
+ "IntervalsT",
534
+ "FreeRide"
535
+ ];
536
+ for (const type of intervalTypes) {
537
+ const data = workout[type];
538
+ if (data) {
539
+ if (Array.isArray(data)) {
540
+ for (const item of data) {
541
+ intervals.push({ type, data: item });
542
+ }
543
+ } else {
544
+ intervals.push({ type, data });
545
+ }
546
+ }
547
+ }
548
+ return intervals;
549
+ };
550
+ var extractTags = (tags) => {
551
+ if (!tags || !tags.tag) return [];
552
+ const tagArray = Array.isArray(tags.tag) ? tags.tag : [tags.tag];
553
+ return tagArray.map((t) => t["@_name"]);
554
+ };
555
+ var mapZwiftDuration = (data) => {
556
+ if (data["kaiord:originalDurationType"] === "distance") {
557
+ return {
558
+ type: durationTypeSchema.enum.distance,
559
+ meters: data["kaiord:originalDurationMeters"] || data.Duration || 0
560
+ };
561
+ }
562
+ if (data["kaiord:originalDurationType"] === "heart_rate_less_than") {
563
+ return {
564
+ type: durationTypeSchema.enum.heart_rate_less_than,
565
+ bpm: data["kaiord:originalDurationBpm"] || 0
566
+ };
567
+ }
568
+ if (data["kaiord:originalDurationType"] === "power_less_than") {
569
+ return {
570
+ type: durationTypeSchema.enum.power_less_than,
571
+ watts: data["kaiord:originalDurationWatts"] || 0
572
+ };
573
+ }
574
+ if (data.Duration === void 0 || data.Duration <= 0) {
575
+ return { type: durationTypeSchema.enum.open };
576
+ }
577
+ if (data.durationType === "distance") {
578
+ return {
579
+ type: durationTypeSchema.enum.distance,
580
+ meters: data.Duration
581
+ };
582
+ }
583
+ return {
584
+ type: durationTypeSchema.enum.time,
585
+ seconds: data.Duration
586
+ };
587
+ };
588
+ var createOnStep = (data) => {
589
+ const onDurationData = {
590
+ Duration: data.OnDuration,
591
+ durationType: data.durationType
592
+ };
593
+ const onDuration = mapZwiftDuration(onDurationData);
594
+ let onTarget;
595
+ if (data.OnPower !== void 0) {
596
+ onTarget = convertZwiftPowerTarget(data.OnPower);
597
+ } else if (data.Cadence !== void 0) {
598
+ onTarget = convertZwiftCadenceTarget(data.Cadence);
599
+ } else {
600
+ onTarget = { type: targetTypeSchema.enum.open };
601
+ }
602
+ const textEventData = extractTextEvents(data.textevent);
603
+ const onStep = {
604
+ stepIndex: data.stepIndex,
605
+ durationType: onDuration.type,
606
+ duration: onDuration,
607
+ targetType: onTarget.type,
608
+ target: onTarget,
609
+ intensity: intensitySchema.enum.active,
610
+ ...textEventData
611
+ };
612
+ if (data.OnPower !== void 0 && data.Cadence !== void 0) {
613
+ onStep.extensions = {
614
+ zwift: {
615
+ cadence: data.Cadence
616
+ }
617
+ };
618
+ }
619
+ return onStep;
620
+ };
621
+ var createOffStep = (data) => {
622
+ const offDurationData = {
623
+ Duration: data.OffDuration,
624
+ durationType: data.durationType
625
+ };
626
+ const offDuration = mapZwiftDuration(offDurationData);
627
+ let offTarget;
628
+ if (data.OffPower !== void 0) {
629
+ offTarget = convertZwiftPowerTarget(data.OffPower);
630
+ } else if (data.CadenceResting !== void 0) {
631
+ offTarget = convertZwiftCadenceTarget(data.CadenceResting);
632
+ } else {
633
+ offTarget = { type: targetTypeSchema.enum.open };
634
+ }
635
+ const offStep = {
636
+ stepIndex: data.stepIndex + 1,
637
+ durationType: offDuration.type,
638
+ duration: offDuration,
639
+ targetType: offTarget.type,
640
+ target: offTarget,
641
+ intensity: intensitySchema.enum.recovery
642
+ };
643
+ if (data.OffPower !== void 0 && data.CadenceResting !== void 0) {
644
+ offStep.extensions = {
645
+ zwift: {
646
+ cadence: data.CadenceResting
647
+ }
648
+ };
649
+ }
650
+ return offStep;
651
+ };
652
+
653
+ // src/adapters/interval/intervals-t.mapper.ts
654
+ var mapIntervalsTToKrd = (data) => {
655
+ return {
656
+ repeatCount: data.Repeat,
657
+ steps: [createOnStep(data), createOffStep(data)]
658
+ };
659
+ };
660
+ var restoreHrRange = (data) => {
661
+ if (data["kaiord:hrTargetLow"] !== void 0 && data["kaiord:hrTargetHigh"] !== void 0) {
662
+ return {
663
+ type: targetTypeSchema.enum.heart_rate,
664
+ value: {
665
+ unit: "range",
666
+ min: data["kaiord:hrTargetLow"],
667
+ max: data["kaiord:hrTargetHigh"]
668
+ }
669
+ };
670
+ }
671
+ return null;
672
+ };
673
+ var restoreHrBpm = (data) => {
674
+ if (data["kaiord:hrTargetBpm"] !== void 0) {
675
+ return {
676
+ type: targetTypeSchema.enum.heart_rate,
677
+ value: {
678
+ unit: "bpm",
679
+ value: data["kaiord:hrTargetBpm"]
680
+ }
681
+ };
682
+ }
683
+ return null;
684
+ };
685
+ var restoreHrZone = (data) => {
686
+ if (data["kaiord:hrTargetZone"] !== void 0) {
687
+ return {
688
+ type: targetTypeSchema.enum.heart_rate,
689
+ value: {
690
+ unit: "zone",
691
+ value: data["kaiord:hrTargetZone"]
692
+ }
693
+ };
694
+ }
695
+ return null;
696
+ };
697
+ var restoreHrPercentMax = (data) => {
698
+ if (data["kaiord:hrTargetPercentMax"] !== void 0) {
699
+ return {
700
+ type: targetTypeSchema.enum.heart_rate,
701
+ value: {
702
+ unit: "percent_max",
703
+ value: data["kaiord:hrTargetPercentMax"]
704
+ }
705
+ };
706
+ }
707
+ return null;
708
+ };
709
+ var restoreHeartRateTarget = (data) => {
710
+ return restoreHrRange(data) || restoreHrBpm(data) || restoreHrZone(data) || restoreHrPercentMax(data) || null;
711
+ };
712
+
713
+ // src/adapters/interval/target-restoration.ts
714
+ var restoreWattsTarget = (data) => {
715
+ if (data["kaiord:powerUnit"] === "watts" && data["kaiord:originalWattsLow"] !== void 0 && data["kaiord:originalWattsHigh"] !== void 0) {
716
+ return {
717
+ type: targetTypeSchema.enum.power,
718
+ value: {
719
+ unit: "range",
720
+ min: data["kaiord:originalWattsLow"],
721
+ max: data["kaiord:originalWattsHigh"]
722
+ }
723
+ };
724
+ }
725
+ return null;
726
+ };
727
+ var restoreZoneTarget = (data) => {
728
+ if (data["kaiord:powerUnit"] === "zone" && data["kaiord:powerZone"] !== void 0) {
729
+ return {
730
+ type: targetTypeSchema.enum.power,
731
+ value: {
732
+ unit: "zone",
733
+ value: data["kaiord:powerZone"]
734
+ }
735
+ };
736
+ }
737
+ return null;
738
+ };
739
+ var restorePowerTarget = (data, powerLow, powerHigh, convertZwiftPowerRange2) => {
740
+ const wattsTarget = restoreWattsTarget(data);
741
+ if (wattsTarget) return wattsTarget;
742
+ const zoneTarget = restoreZoneTarget(data);
743
+ if (zoneTarget) return zoneTarget;
744
+ if (powerLow !== void 0 && powerHigh !== void 0 && convertZwiftPowerRange2) {
745
+ return convertZwiftPowerRange2(powerLow, powerHigh);
746
+ }
747
+ return null;
748
+ };
749
+
750
+ // src/adapters/interval/ramp-helpers.ts
751
+ var buildRampDurationData = (data) => ({
752
+ Duration: data.Duration,
753
+ durationType: data.durationType,
754
+ "kaiord:originalDurationType": data["kaiord:originalDurationType"],
755
+ "kaiord:originalDurationMeters": data["kaiord:originalDurationMeters"],
756
+ "kaiord:originalDurationBpm": data["kaiord:originalDurationBpm"],
757
+ "kaiord:originalDurationWatts": data["kaiord:originalDurationWatts"]
758
+ });
759
+ var resolveRampTarget = (data) => {
760
+ const powerTarget = restorePowerTarget(
761
+ data,
762
+ data.PowerLow,
763
+ data.PowerHigh,
764
+ convertZwiftPowerRange
765
+ );
766
+ const hrTarget = restoreHeartRateTarget(data);
767
+ return powerTarget || hrTarget || { type: targetTypeSchema.enum.open };
768
+ };
769
+ var resolveIntensity = (data, defaultIntensity) => {
770
+ return data["kaiord:intensity"] || defaultIntensity;
771
+ };
772
+ var addRampMetadata = (step, data) => {
773
+ if (data["kaiord:name"]) step.name = data["kaiord:name"];
774
+ if (data["kaiord:equipment"]) {
775
+ step.equipment = data["kaiord:equipment"];
776
+ }
777
+ };
778
+
779
+ // src/adapters/interval/ramp.mapper.ts
780
+ var mapWarmupToKrd = (data) => {
781
+ return mapRampToKrd(data, intensitySchema.enum.warmup);
782
+ };
783
+ var mapRampToKrd = (data, intensity = intensitySchema.enum.active) => {
784
+ const duration = mapZwiftDuration(buildRampDurationData(data));
785
+ const target = resolveRampTarget(data);
786
+ const textEventData = extractTextEvents(
787
+ data.textevent
788
+ );
789
+ const step = {
790
+ stepIndex: data.stepIndex,
791
+ durationType: duration.type,
792
+ duration,
793
+ targetType: target.type,
794
+ target,
795
+ intensity: resolveIntensity(data, intensity),
796
+ ...textEventData
797
+ };
798
+ addRampMetadata(step, data);
799
+ return step;
800
+ };
801
+ var mapCooldownToKrd = (data) => {
802
+ return mapRampToKrd(data, intensitySchema.enum.cooldown);
803
+ };
804
+ var restoreSteadyStateTarget = (data) => {
805
+ if (data["kaiord:powerUnit"] === "watts" && data["kaiord:originalWatts"]) {
806
+ return {
807
+ type: targetTypeSchema.enum.power,
808
+ value: {
809
+ unit: "watts",
810
+ value: data["kaiord:originalWatts"]
811
+ }
812
+ };
813
+ }
814
+ if (data["kaiord:powerUnit"] === "zone" && data["kaiord:powerZone"]) {
815
+ return {
816
+ type: targetTypeSchema.enum.power,
817
+ value: {
818
+ unit: "zone",
819
+ value: data["kaiord:powerZone"]
820
+ }
821
+ };
822
+ }
823
+ if (data.Power !== void 0) {
824
+ return convertZwiftPowerTarget(data.Power);
825
+ }
826
+ const hrTarget = restoreHeartRateTarget(data);
827
+ return hrTarget || { type: targetTypeSchema.enum.open };
828
+ };
829
+ var mapSteadyStateToKrd = (data) => {
830
+ const durationData = {
831
+ Duration: data.Duration,
832
+ durationType: data.durationType,
833
+ "kaiord:originalDurationType": data["kaiord:originalDurationType"],
834
+ "kaiord:originalDurationMeters": data["kaiord:originalDurationMeters"],
835
+ "kaiord:originalDurationBpm": data["kaiord:originalDurationBpm"],
836
+ "kaiord:originalDurationWatts": data["kaiord:originalDurationWatts"]
837
+ };
838
+ const duration = mapZwiftDuration(durationData);
839
+ const target = restoreSteadyStateTarget(data);
840
+ const textEventData = extractTextEvents(data.textevent);
841
+ const step = {
842
+ stepIndex: data.stepIndex,
843
+ durationType: duration.type,
844
+ duration,
845
+ targetType: target.type,
846
+ target,
847
+ intensity: data["kaiord:intensity"] || intensitySchema.enum.active,
848
+ ...textEventData
849
+ };
850
+ if (data["kaiord:name"]) {
851
+ step.name = data["kaiord:name"];
852
+ }
853
+ if (data["kaiord:equipment"]) {
854
+ step.equipment = data["kaiord:equipment"];
855
+ }
856
+ return step;
857
+ };
858
+
859
+ // src/adapters/interval/index.ts
860
+ var extractTextEvents = (textevent) => {
861
+ if (!textevent) {
862
+ return {};
863
+ }
864
+ const events = Array.isArray(textevent) ? textevent : [textevent];
865
+ if (events.length === 0) {
866
+ return {};
867
+ }
868
+ const primaryMessage = events[0].message;
869
+ const result = {};
870
+ if (primaryMessage) {
871
+ result.notes = primaryMessage;
872
+ }
873
+ if (events.length > 0) {
874
+ result.extensions = {
875
+ zwift: {
876
+ textEvents: events
877
+ }
878
+ };
879
+ }
880
+ return result;
881
+ };
882
+
883
+ // src/adapters/interval/free-ride.mapper.ts
884
+ var mapFreeRideToKrd = (data) => {
885
+ const durationData = {
886
+ Duration: data.Duration,
887
+ durationType: data.durationType
888
+ };
889
+ const duration = mapZwiftDuration(durationData);
890
+ const textEventData = extractTextEvents(data.textevent);
891
+ const step = {
892
+ stepIndex: data.stepIndex,
893
+ durationType: duration.type,
894
+ duration,
895
+ targetType: targetTypeSchema.enum.open,
896
+ target: { type: targetTypeSchema.enum.open },
897
+ intensity: intensitySchema.enum.active,
898
+ ...textEventData
899
+ };
900
+ const flatRoad = data["@_FlatRoad"] ?? data.FlatRoad;
901
+ if (flatRoad !== void 0) {
902
+ step.extensions = {
903
+ ...step.extensions,
904
+ zwift: {
905
+ ...step.extensions?.zwift || {},
906
+ FlatRoad: flatRoad
907
+ }
908
+ };
909
+ }
910
+ return step;
911
+ };
912
+
913
+ // src/adapters/zwift-to-krd/intervals-processor.ts
914
+ var normalizeAttributeNames = (data) => {
915
+ const normalized = {};
916
+ for (const [key, value] of Object.entries(data)) {
917
+ const normalizedKey = key.startsWith("@_") ? key.substring(2) : key;
918
+ normalized[normalizedKey] = value;
919
+ }
920
+ return normalized;
921
+ };
922
+ var processSingleStep = (interval, stepIndex, durationType) => {
923
+ const normalizedData = normalizeAttributeNames(interval.data);
924
+ const data = { ...normalizedData, stepIndex, durationType };
925
+ if (interval.type === "SteadyState") {
926
+ return { step: mapSteadyStateToKrd(data), indexIncrement: 1 };
927
+ } else if (interval.type === "Warmup") {
928
+ return { step: mapWarmupToKrd(data), indexIncrement: 1 };
929
+ } else if (interval.type === "Ramp") {
930
+ return { step: mapRampToKrd(data), indexIncrement: 1 };
931
+ } else if (interval.type === "Cooldown") {
932
+ return { step: mapCooldownToKrd(data), indexIncrement: 1 };
933
+ } else if (interval.type === "FreeRide") {
934
+ return { step: mapFreeRideToKrd(data), indexIncrement: 1 };
935
+ }
936
+ throw new Error(`Unknown interval type: ${interval.type}`);
937
+ };
938
+ var processInterval = (interval, stepIndex, durationType) => {
939
+ if (interval.type === "IntervalsT") {
940
+ const normalizedData = normalizeAttributeNames(interval.data);
941
+ const repetitionBlock = mapIntervalsTToKrd({
942
+ ...normalizedData,
943
+ stepIndex,
944
+ durationType
945
+ });
946
+ return {
947
+ step: repetitionBlock,
948
+ indexIncrement: repetitionBlock.steps.length
949
+ };
950
+ }
951
+ return processSingleStep(interval, stepIndex, durationType);
952
+ };
953
+ var processIntervals = (intervals, durationType) => {
954
+ const steps = [];
955
+ let stepIndex = 0;
956
+ for (const interval of intervals) {
957
+ const result = processInterval(interval, stepIndex, durationType);
958
+ steps.push(result.step);
959
+ stepIndex += result.indexIncrement;
960
+ }
961
+ return steps;
962
+ };
963
+
964
+ // src/adapters/zwift-to-krd/metadata-extractor.ts
965
+ var extractMetadata = (workoutFile, sport) => ({
966
+ created: workoutFile["@_kaiord:timeCreated"] || (/* @__PURE__ */ new Date()).toISOString(),
967
+ sport,
968
+ manufacturer: workoutFile["@_kaiord:manufacturer"],
969
+ product: workoutFile["@_kaiord:product"],
970
+ serialNumber: workoutFile["@_kaiord:serialNumber"]
971
+ });
972
+ var extractFitExtensions = (workoutFile) => {
973
+ if (workoutFile["@_kaiord:fitType"] || workoutFile["@_kaiord:hrmFitProductId"]) {
974
+ return {
975
+ type: workoutFile["@_kaiord:fitType"],
976
+ hrm_fit_single_byte_product_id: workoutFile["@_kaiord:hrmFitProductId"]
977
+ };
978
+ }
979
+ return void 0;
980
+ };
981
+
982
+ // src/adapters/zwift-to-krd.converter.ts
983
+ var convertZwiftToKRD = (zwiftData, logger) => {
984
+ logger.debug("Converting Zwift to KRD");
985
+ const workoutFile = zwiftData.workout_file;
986
+ const sport = workoutFile.sportType === "bike" ? "cycling" : workoutFile.sportType === "run" ? "running" : "generic";
987
+ const durationType = workoutFile.durationType === "distance" ? "distance" : "time";
988
+ const intervals = extractIntervals(workoutFile.workout);
989
+ const steps = processIntervals(intervals, durationType);
990
+ const metadata = extractMetadata(workoutFile, sport);
991
+ const fitExtensions = extractFitExtensions(workoutFile);
992
+ const extensions = {
993
+ structured_workout: {
994
+ name: workoutFile.name,
995
+ sport,
996
+ steps
997
+ },
998
+ zwift: {
999
+ author: workoutFile.author,
1000
+ description: workoutFile.description,
1001
+ durationType,
1002
+ thresholdSecPerKm: workoutFile.thresholdSecPerKm,
1003
+ tags: extractTags(workoutFile.tags)
1004
+ }
1005
+ };
1006
+ if (fitExtensions) {
1007
+ extensions.fit = fitExtensions;
1008
+ }
1009
+ return {
1010
+ version: "1.0",
1011
+ type: "structured_workout",
1012
+ metadata,
1013
+ extensions
1014
+ };
1015
+ };
1016
+
1017
+ // src/adapters/fast-xml-parser.ts
1018
+ var parseZwiftXml = (xmlString, logger) => {
1019
+ logger.debug("Parsing Zwift file");
1020
+ try {
1021
+ const parser = new XMLParser({
1022
+ ignoreAttributes: false,
1023
+ attributeNamePrefix: "@_",
1024
+ parseAttributeValue: true
1025
+ });
1026
+ return parser.parse(xmlString);
1027
+ } catch (error) {
1028
+ logger.error("Failed to parse Zwift XML", { error });
1029
+ throw createZwiftParsingError("Failed to parse Zwift file", error);
1030
+ }
1031
+ };
1032
+ var createFastXmlZwiftReader = (logger, validator) => async (xmlString) => {
1033
+ await validateInputZwiftXml(xmlString, validator, logger);
1034
+ const zwiftData = parseZwiftXml(xmlString, logger);
1035
+ validateZwiftStructure(zwiftData, logger);
1036
+ logger.info("Zwift file parsed successfully");
1037
+ return convertZwiftToKRD(zwiftData, logger);
1038
+ };
1039
+ var createFastXmlZwiftWriter = (logger, validator) => async (krd) => {
1040
+ logger.debug("Converting KRD to Zwift format");
1041
+ let xmlString;
1042
+ try {
1043
+ xmlString = convertKRDToZwift(krd, logger);
1044
+ } catch (error) {
1045
+ logger.error("Failed to convert KRD to Zwift", { error });
1046
+ throw createZwiftParsingError("Failed to convert KRD to Zwift", error);
1047
+ }
1048
+ await validateGeneratedZwiftXml(xmlString, validator, logger);
1049
+ logger.info("KRD to Zwift conversion successful");
1050
+ return xmlString;
1051
+ };
1052
+ var validateXmlWellFormedness = (xmlString, logger) => {
1053
+ const xmlValidation = XMLValidator.validate(xmlString, {
1054
+ allowBooleanAttributes: true
1055
+ });
1056
+ if (xmlValidation !== true) {
1057
+ logger.warn("Zwift XML well-formedness validation failed", {
1058
+ error: xmlValidation.err
1059
+ });
1060
+ return {
1061
+ valid: false,
1062
+ errors: [
1063
+ {
1064
+ field: `line ${xmlValidation.err.line}`,
1065
+ message: `XML validation failed: ${xmlValidation.err.msg}`
1066
+ }
1067
+ ]
1068
+ };
1069
+ }
1070
+ return null;
1071
+ };
1072
+
1073
+ // src/adapters/well-formedness-validator.ts
1074
+ var createWellFormednessValidator = (logger) => async (xmlString) => {
1075
+ try {
1076
+ logger.debug("Validating Zwift XML well-formedness (browser mode)");
1077
+ const wellFormednessError = validateXmlWellFormedness(xmlString, logger);
1078
+ if (wellFormednessError) {
1079
+ return wellFormednessError;
1080
+ }
1081
+ logger.info(
1082
+ "Zwift XML validated successfully (well-formedness only, XSD validation skipped in browser)"
1083
+ );
1084
+ return { valid: true, errors: [] };
1085
+ } catch (error) {
1086
+ logger.error("Zwift well-formedness validation failed", { error });
1087
+ return {
1088
+ valid: false,
1089
+ errors: [
1090
+ {
1091
+ field: "root",
1092
+ message: error instanceof Error ? error.message : "Unknown error"
1093
+ }
1094
+ ]
1095
+ };
1096
+ }
1097
+ };
1098
+
1099
+ // src/adapters/node-modules-loader.ts
1100
+ var isNode = typeof process !== "undefined" && (process.versions?.node || typeof process.env !== "undefined");
1101
+ var validateXML = null;
1102
+ var XSD_SCHEMA_PATH = null;
1103
+ var loadNodeModules = async () => {
1104
+ if (!isNode) {
1105
+ throw new Error("XSD validation is only available in Node.js environments");
1106
+ }
1107
+ if (validateXML && XSD_SCHEMA_PATH) {
1108
+ return { validateXML, XSD_SCHEMA_PATH };
1109
+ }
1110
+ const { createRequire } = await import('module');
1111
+ const { dirname, join } = await import('path');
1112
+ const { fileURLToPath } = await import('url');
1113
+ const require2 = createRequire(import.meta.url);
1114
+ const { validateXML: validateXMLFn } = require2("xsd-schema-validator");
1115
+ const __filename = fileURLToPath(import.meta.url);
1116
+ const __dirname = dirname(__filename);
1117
+ const schemaPath = join(__dirname, "../schema/zwift-workout.xsd");
1118
+ validateXML = validateXMLFn;
1119
+ XSD_SCHEMA_PATH = schemaPath;
1120
+ return { validateXML, XSD_SCHEMA_PATH };
1121
+ };
1122
+
1123
+ // src/adapters/xsd-schema-validator.ts
1124
+ var validateAgainstXsdSchema = async (xmlString, logger) => {
1125
+ const { validateXML: validateXMLFn, XSD_SCHEMA_PATH: schemaPath } = await loadNodeModules();
1126
+ const xsdValidationResult = await validateXMLFn(xmlString, schemaPath);
1127
+ if (!xsdValidationResult.valid) {
1128
+ logger.warn("Zwift XSD validation failed", {
1129
+ messages: xsdValidationResult.messages
1130
+ });
1131
+ return {
1132
+ valid: false,
1133
+ errors: xsdValidationResult.messages.map((msg) => ({
1134
+ field: "schema",
1135
+ message: msg
1136
+ }))
1137
+ };
1138
+ }
1139
+ return null;
1140
+ };
1141
+
1142
+ // src/adapters/xsd-validator.ts
1143
+ var isBrowser = (() => {
1144
+ try {
1145
+ return typeof globalThis.window !== "undefined";
1146
+ } catch {
1147
+ return false;
1148
+ }
1149
+ })();
1150
+ var createZwiftValidator = (logger) => {
1151
+ if (isBrowser) {
1152
+ logger.info(
1153
+ "Browser environment detected, using well-formedness validation for Zwift XML (XSD validation not available)"
1154
+ );
1155
+ return createWellFormednessValidator(logger);
1156
+ }
1157
+ return createXsdZwiftValidator(logger);
1158
+ };
1159
+ var createXsdZwiftValidator = (logger) => async (xmlString) => {
1160
+ try {
1161
+ logger.debug("Validating Zwift XML structure");
1162
+ const wellFormednessError = validateXmlWellFormedness(xmlString, logger);
1163
+ if (wellFormednessError) {
1164
+ return wellFormednessError;
1165
+ }
1166
+ logger.debug(
1167
+ "XML well-formedness validated, proceeding with XSD validation"
1168
+ );
1169
+ const xsdError = await validateAgainstXsdSchema(xmlString, logger);
1170
+ if (xsdError) {
1171
+ return xsdError;
1172
+ }
1173
+ logger.info("Zwift XML validated successfully against XSD schema");
1174
+ return { valid: true, errors: [] };
1175
+ } catch (error) {
1176
+ logger.error("Zwift validation failed", { error });
1177
+ return {
1178
+ valid: false,
1179
+ errors: [
1180
+ {
1181
+ field: "root",
1182
+ message: error instanceof Error ? error.message : "Unknown error"
1183
+ }
1184
+ ]
1185
+ };
1186
+ }
1187
+ };
1188
+
1189
+ // src/providers.ts
1190
+ var createZwoProviders = (logger) => {
1191
+ const log = logger || createConsoleLogger();
1192
+ const zwiftValidator = createZwiftValidator(log);
1193
+ return {
1194
+ zwiftReader: createFastXmlZwiftReader(log, zwiftValidator),
1195
+ zwiftWriter: createFastXmlZwiftWriter(log, zwiftValidator),
1196
+ zwiftValidator
1197
+ };
1198
+ };
1199
+
1200
+ export { createFastXmlZwiftReader, createFastXmlZwiftWriter, createXsdZwiftValidator, createZwiftValidator, createZwoProviders };
1201
+ //# sourceMappingURL=index.js.map
1202
+ //# sourceMappingURL=index.js.map