endurance-coach 0.1.1 → 1.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.
Files changed (74) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +318 -35
  3. package/dist/expander/expander.d.ts +20 -0
  4. package/dist/expander/expander.js +339 -0
  5. package/dist/expander/index.d.ts +8 -0
  6. package/dist/expander/index.js +9 -0
  7. package/dist/expander/types.d.ts +169 -0
  8. package/dist/expander/types.js +6 -0
  9. package/dist/expander/zones.d.ts +50 -0
  10. package/dist/expander/zones.js +159 -0
  11. package/dist/index.d.ts +4 -0
  12. package/dist/index.js +9 -1
  13. package/dist/schema/compact-plan.d.ts +175 -0
  14. package/dist/schema/compact-plan.js +64 -0
  15. package/dist/schema/compact-plan.schema.d.ts +277 -0
  16. package/dist/schema/compact-plan.schema.js +205 -0
  17. package/dist/templates/index.d.ts +10 -0
  18. package/dist/templates/index.js +13 -0
  19. package/dist/templates/interpolate.d.ts +51 -0
  20. package/dist/templates/interpolate.js +204 -0
  21. package/dist/templates/loader.d.ts +19 -0
  22. package/dist/templates/loader.js +129 -0
  23. package/dist/templates/template.schema.d.ts +401 -0
  24. package/dist/templates/template.schema.js +101 -0
  25. package/dist/templates/template.types.d.ts +155 -0
  26. package/dist/templates/template.types.js +7 -0
  27. package/dist/templates/yaml-parser.d.ts +15 -0
  28. package/dist/templates/yaml-parser.js +18 -0
  29. package/package.json +2 -1
  30. package/templates/bike/CLAUDE.md +7 -0
  31. package/templates/bike/easy.yaml +38 -0
  32. package/templates/bike/endurance.yaml +42 -0
  33. package/templates/bike/hills.yaml +80 -0
  34. package/templates/bike/overunders.yaml +81 -0
  35. package/templates/bike/rest.yaml +16 -0
  36. package/templates/bike/sweetspot.yaml +80 -0
  37. package/templates/bike/tempo.yaml +79 -0
  38. package/templates/bike/threshold.yaml +83 -0
  39. package/templates/bike/vo2max.yaml +84 -0
  40. package/templates/brick/CLAUDE.md +7 -0
  41. package/templates/brick/halfironman.yaml +72 -0
  42. package/templates/brick/ironman.yaml +72 -0
  43. package/templates/brick/olympic.yaml +70 -0
  44. package/templates/brick/sprint.yaml +70 -0
  45. package/templates/plan-viewer.html +22 -22
  46. package/templates/run/CLAUDE.md +7 -0
  47. package/templates/run/easy.yaml +36 -0
  48. package/templates/run/fartlek.yaml +40 -0
  49. package/templates/run/hills.yaml +36 -0
  50. package/templates/run/intervals.1k.yaml +63 -0
  51. package/templates/run/intervals.400.yaml +63 -0
  52. package/templates/run/intervals.800.yaml +63 -0
  53. package/templates/run/intervals.mile.yaml +64 -0
  54. package/templates/run/long.yaml +41 -0
  55. package/templates/run/progression.yaml +49 -0
  56. package/templates/run/race.5k.yaml +36 -0
  57. package/templates/run/recovery.yaml +36 -0
  58. package/templates/run/rest.yaml +16 -0
  59. package/templates/run/strides.yaml +49 -0
  60. package/templates/run/tempo.yaml +56 -0
  61. package/templates/run/threshold.yaml +56 -0
  62. package/templates/strength/CLAUDE.md +7 -0
  63. package/templates/strength/core.yaml +56 -0
  64. package/templates/strength/foundation.yaml +65 -0
  65. package/templates/strength/full.yaml +73 -0
  66. package/templates/strength/maintenance.yaml +62 -0
  67. package/templates/swim/CLAUDE.md +7 -0
  68. package/templates/swim/aerobic.yaml +67 -0
  69. package/templates/swim/easy.yaml +51 -0
  70. package/templates/swim/openwater.yaml +60 -0
  71. package/templates/swim/rest.yaml +16 -0
  72. package/templates/swim/technique.yaml +67 -0
  73. package/templates/swim/threshold.yaml +75 -0
  74. package/templates/swim/vo2max.yaml +88 -0
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Compact Training Plan Zod Schema
3
+ *
4
+ * Runtime validation for compact training plans generated by AI models.
5
+ */
6
+ import { z } from "zod";
7
+ export declare const SportSchema: z.ZodEnum<{
8
+ swim: "swim";
9
+ bike: "bike";
10
+ run: "run";
11
+ strength: "strength";
12
+ brick: "brick";
13
+ race: "race";
14
+ rest: "rest";
15
+ }>;
16
+ export declare const FirstDayOfWeekSchema: z.ZodEnum<{
17
+ monday: "monday";
18
+ sunday: "sunday";
19
+ }>;
20
+ export declare const DistanceUnitSchema: z.ZodEnum<{
21
+ km: "km";
22
+ mi: "mi";
23
+ }>;
24
+ export declare const DayOfWeekSchema: z.ZodEnum<{
25
+ Mon: "Mon";
26
+ Tue: "Tue";
27
+ Wed: "Wed";
28
+ Thu: "Thu";
29
+ Fri: "Fri";
30
+ Sat: "Sat";
31
+ Sun: "Sun";
32
+ }>;
33
+ /**
34
+ * Pace string validation - formats like "5:30/km", "8:00/mi", "1:45/100m"
35
+ */
36
+ export declare const PaceStringSchema: z.ZodString;
37
+ /**
38
+ * Duration string validation - formats like "30", "30min", "1:30", "90s"
39
+ */
40
+ export declare const DurationStringSchema: z.ZodString;
41
+ export declare const AthletePacesSchema: z.ZodObject<{
42
+ easy: z.ZodOptional<z.ZodString>;
43
+ long: z.ZodOptional<z.ZodString>;
44
+ tempo: z.ZodOptional<z.ZodString>;
45
+ threshold: z.ZodOptional<z.ZodString>;
46
+ marathon: z.ZodOptional<z.ZodString>;
47
+ halfMarathon: z.ZodOptional<z.ZodString>;
48
+ interval: z.ZodOptional<z.ZodString>;
49
+ r200: z.ZodOptional<z.ZodString>;
50
+ r400: z.ZodOptional<z.ZodString>;
51
+ r800: z.ZodOptional<z.ZodString>;
52
+ r1k: z.ZodOptional<z.ZodString>;
53
+ rMile: z.ZodOptional<z.ZodString>;
54
+ bikeFtp: z.ZodOptional<z.ZodNumber>;
55
+ bikeEasy: z.ZodOptional<z.ZodString>;
56
+ bikeTempo: z.ZodOptional<z.ZodString>;
57
+ bikeThreshold: z.ZodOptional<z.ZodString>;
58
+ swimCss: z.ZodOptional<z.ZodString>;
59
+ swimEasy: z.ZodOptional<z.ZodString>;
60
+ swimTempo: z.ZodOptional<z.ZodString>;
61
+ }, z.core.$strip>;
62
+ export declare const HRZoneConfigSchema: z.ZodObject<{
63
+ lthr: z.ZodNumber;
64
+ maxHR: z.ZodOptional<z.ZodNumber>;
65
+ restingHR: z.ZodOptional<z.ZodNumber>;
66
+ }, z.core.$strip>;
67
+ export declare const AthleteZonesSchema: z.ZodObject<{
68
+ hr: z.ZodOptional<z.ZodObject<{
69
+ lthr: z.ZodNumber;
70
+ maxHR: z.ZodOptional<z.ZodNumber>;
71
+ restingHR: z.ZodOptional<z.ZodNumber>;
72
+ }, z.core.$strip>>;
73
+ }, z.core.$strip>;
74
+ export declare const AthleteConstraintsSchema: z.ZodObject<{
75
+ daysPerWeek: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
76
+ preferredDays: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
+ maxLongRunHours: z.ZodOptional<z.ZodNumber>;
78
+ maxLongBikeHours: z.ZodOptional<z.ZodNumber>;
79
+ notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
+ }, z.core.$strip>;
81
+ export declare const CompactAthleteSchema: z.ZodObject<{
82
+ name: z.ZodString;
83
+ event: z.ZodString;
84
+ eventDate: z.ZodString;
85
+ paces: z.ZodObject<{
86
+ easy: z.ZodOptional<z.ZodString>;
87
+ long: z.ZodOptional<z.ZodString>;
88
+ tempo: z.ZodOptional<z.ZodString>;
89
+ threshold: z.ZodOptional<z.ZodString>;
90
+ marathon: z.ZodOptional<z.ZodString>;
91
+ halfMarathon: z.ZodOptional<z.ZodString>;
92
+ interval: z.ZodOptional<z.ZodString>;
93
+ r200: z.ZodOptional<z.ZodString>;
94
+ r400: z.ZodOptional<z.ZodString>;
95
+ r800: z.ZodOptional<z.ZodString>;
96
+ r1k: z.ZodOptional<z.ZodString>;
97
+ rMile: z.ZodOptional<z.ZodString>;
98
+ bikeFtp: z.ZodOptional<z.ZodNumber>;
99
+ bikeEasy: z.ZodOptional<z.ZodString>;
100
+ bikeTempo: z.ZodOptional<z.ZodString>;
101
+ bikeThreshold: z.ZodOptional<z.ZodString>;
102
+ swimCss: z.ZodOptional<z.ZodString>;
103
+ swimEasy: z.ZodOptional<z.ZodString>;
104
+ swimTempo: z.ZodOptional<z.ZodString>;
105
+ }, z.core.$strip>;
106
+ zones: z.ZodOptional<z.ZodObject<{
107
+ hr: z.ZodOptional<z.ZodObject<{
108
+ lthr: z.ZodNumber;
109
+ maxHR: z.ZodOptional<z.ZodNumber>;
110
+ restingHR: z.ZodOptional<z.ZodNumber>;
111
+ }, z.core.$strip>>;
112
+ }, z.core.$strip>>;
113
+ constraints: z.ZodOptional<z.ZodObject<{
114
+ daysPerWeek: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
115
+ preferredDays: z.ZodOptional<z.ZodArray<z.ZodString>>;
116
+ maxLongRunHours: z.ZodOptional<z.ZodNumber>;
117
+ maxLongBikeHours: z.ZodOptional<z.ZodNumber>;
118
+ notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
119
+ }, z.core.$strip>>;
120
+ unit: z.ZodDefault<z.ZodEnum<{
121
+ km: "km";
122
+ mi: "mi";
123
+ }>>;
124
+ firstDayOfWeek: z.ZodDefault<z.ZodEnum<{
125
+ monday: "monday";
126
+ sunday: "sunday";
127
+ }>>;
128
+ }, z.core.$strip>;
129
+ /**
130
+ * Week range can be "1-3" or [1, 2, 3]
131
+ */
132
+ export declare const WeekRangeSchema: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodNumber>]>;
133
+ export declare const CompactPhaseSchema: z.ZodObject<{
134
+ name: z.ZodString;
135
+ weeks: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodNumber>]>;
136
+ focus: z.ZodString;
137
+ keyWorkouts: z.ZodOptional<z.ZodArray<z.ZodString>>;
138
+ }, z.core.$strip>;
139
+ /**
140
+ * Workout reference validation.
141
+ * Format: "template.id" or "template.id(param)" or "template.id(param1, param2)"
142
+ */
143
+ export declare const WorkoutRefSchema: z.ZodString;
144
+ /**
145
+ * A day can have a single workout or multiple (for brick days).
146
+ */
147
+ export declare const DayWorkoutSchema: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>;
148
+ /**
149
+ * Week schedule maps days to workouts.
150
+ */
151
+ export declare const CompactWeekScheduleSchema: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
152
+ export declare const CompactWeekSchema: z.ZodObject<{
153
+ week: z.ZodNumber;
154
+ phase: z.ZodString;
155
+ focus: z.ZodOptional<z.ZodString>;
156
+ isRecoveryWeek: z.ZodOptional<z.ZodBoolean>;
157
+ targetHours: z.ZodOptional<z.ZodNumber>;
158
+ workouts: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
159
+ }, z.core.$strip>;
160
+ export declare const CompactRaceStrategySchema: z.ZodObject<{
161
+ goalTime: z.ZodOptional<z.ZodString>;
162
+ pacing: z.ZodOptional<z.ZodObject<{
163
+ swim: z.ZodOptional<z.ZodString>;
164
+ bike: z.ZodOptional<z.ZodString>;
165
+ run: z.ZodOptional<z.ZodString>;
166
+ }, z.core.$strip>>;
167
+ nutrition: z.ZodOptional<z.ZodString>;
168
+ notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
169
+ }, z.core.$strip>;
170
+ export declare const CompactPlanSchema: z.ZodObject<{
171
+ version: z.ZodLiteral<"2.0">;
172
+ athlete: z.ZodObject<{
173
+ name: z.ZodString;
174
+ event: z.ZodString;
175
+ eventDate: z.ZodString;
176
+ paces: z.ZodObject<{
177
+ easy: z.ZodOptional<z.ZodString>;
178
+ long: z.ZodOptional<z.ZodString>;
179
+ tempo: z.ZodOptional<z.ZodString>;
180
+ threshold: z.ZodOptional<z.ZodString>;
181
+ marathon: z.ZodOptional<z.ZodString>;
182
+ halfMarathon: z.ZodOptional<z.ZodString>;
183
+ interval: z.ZodOptional<z.ZodString>;
184
+ r200: z.ZodOptional<z.ZodString>;
185
+ r400: z.ZodOptional<z.ZodString>;
186
+ r800: z.ZodOptional<z.ZodString>;
187
+ r1k: z.ZodOptional<z.ZodString>;
188
+ rMile: z.ZodOptional<z.ZodString>;
189
+ bikeFtp: z.ZodOptional<z.ZodNumber>;
190
+ bikeEasy: z.ZodOptional<z.ZodString>;
191
+ bikeTempo: z.ZodOptional<z.ZodString>;
192
+ bikeThreshold: z.ZodOptional<z.ZodString>;
193
+ swimCss: z.ZodOptional<z.ZodString>;
194
+ swimEasy: z.ZodOptional<z.ZodString>;
195
+ swimTempo: z.ZodOptional<z.ZodString>;
196
+ }, z.core.$strip>;
197
+ zones: z.ZodOptional<z.ZodObject<{
198
+ hr: z.ZodOptional<z.ZodObject<{
199
+ lthr: z.ZodNumber;
200
+ maxHR: z.ZodOptional<z.ZodNumber>;
201
+ restingHR: z.ZodOptional<z.ZodNumber>;
202
+ }, z.core.$strip>>;
203
+ }, z.core.$strip>>;
204
+ constraints: z.ZodOptional<z.ZodObject<{
205
+ daysPerWeek: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
206
+ preferredDays: z.ZodOptional<z.ZodArray<z.ZodString>>;
207
+ maxLongRunHours: z.ZodOptional<z.ZodNumber>;
208
+ maxLongBikeHours: z.ZodOptional<z.ZodNumber>;
209
+ notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
210
+ }, z.core.$strip>>;
211
+ unit: z.ZodDefault<z.ZodEnum<{
212
+ km: "km";
213
+ mi: "mi";
214
+ }>>;
215
+ firstDayOfWeek: z.ZodDefault<z.ZodEnum<{
216
+ monday: "monday";
217
+ sunday: "sunday";
218
+ }>>;
219
+ }, z.core.$strip>;
220
+ phases: z.ZodArray<z.ZodObject<{
221
+ name: z.ZodString;
222
+ weeks: z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodNumber>]>;
223
+ focus: z.ZodString;
224
+ keyWorkouts: z.ZodOptional<z.ZodArray<z.ZodString>>;
225
+ }, z.core.$strip>>;
226
+ weeks: z.ZodArray<z.ZodObject<{
227
+ week: z.ZodNumber;
228
+ phase: z.ZodString;
229
+ focus: z.ZodOptional<z.ZodString>;
230
+ isRecoveryWeek: z.ZodOptional<z.ZodBoolean>;
231
+ targetHours: z.ZodOptional<z.ZodNumber>;
232
+ workouts: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
233
+ }, z.core.$strip>>;
234
+ raceStrategy: z.ZodOptional<z.ZodObject<{
235
+ goalTime: z.ZodOptional<z.ZodString>;
236
+ pacing: z.ZodOptional<z.ZodObject<{
237
+ swim: z.ZodOptional<z.ZodString>;
238
+ bike: z.ZodOptional<z.ZodString>;
239
+ run: z.ZodOptional<z.ZodString>;
240
+ }, z.core.$strip>>;
241
+ nutrition: z.ZodOptional<z.ZodString>;
242
+ notes: z.ZodOptional<z.ZodArray<z.ZodString>>;
243
+ }, z.core.$strip>>;
244
+ }, z.core.$strip>;
245
+ export type CompactValidationResult = {
246
+ success: true;
247
+ data: z.infer<typeof CompactPlanSchema>;
248
+ } | {
249
+ success: false;
250
+ errors: CompactValidationError[];
251
+ };
252
+ export interface CompactValidationError {
253
+ path: string;
254
+ message: string;
255
+ code: string;
256
+ }
257
+ /**
258
+ * Validate a compact training plan against the schema.
259
+ */
260
+ export declare function validateCompactPlan(data: unknown): CompactValidationResult;
261
+ /**
262
+ * Validate a compact plan and throw an error if invalid.
263
+ */
264
+ export declare function validateCompactPlanOrThrow(data: unknown): z.infer<typeof CompactPlanSchema>;
265
+ /**
266
+ * Format validation errors into a human-readable string.
267
+ */
268
+ export declare function formatCompactValidationErrors(errors: CompactValidationError[]): string;
269
+ export type CompactPlan = z.infer<typeof CompactPlanSchema>;
270
+ export type CompactAthlete = z.infer<typeof CompactAthleteSchema>;
271
+ export type CompactPhase = z.infer<typeof CompactPhaseSchema>;
272
+ export type CompactWeek = z.infer<typeof CompactWeekSchema>;
273
+ export type CompactWeekSchedule = z.infer<typeof CompactWeekScheduleSchema>;
274
+ export type CompactRaceStrategy = z.infer<typeof CompactRaceStrategySchema>;
275
+ export type AthletePaces = z.infer<typeof AthletePacesSchema>;
276
+ export type AthleteZones = z.infer<typeof AthleteZonesSchema>;
277
+ export type HRZoneConfig = z.infer<typeof HRZoneConfigSchema>;
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Compact Training Plan Zod Schema
3
+ *
4
+ * Runtime validation for compact training plans generated by AI models.
5
+ */
6
+ import { z } from "zod";
7
+ // ============================================================================
8
+ // Core Types
9
+ // ============================================================================
10
+ export const SportSchema = z.enum(["swim", "bike", "run", "strength", "brick", "race", "rest"]);
11
+ export const FirstDayOfWeekSchema = z.enum(["monday", "sunday"]);
12
+ export const DistanceUnitSchema = z.enum(["km", "mi"]);
13
+ export const DayOfWeekSchema = z.enum(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]);
14
+ // ============================================================================
15
+ // Athlete Configuration
16
+ // ============================================================================
17
+ /**
18
+ * Pace string validation - formats like "5:30/km", "8:00/mi", "1:45/100m"
19
+ */
20
+ export const PaceStringSchema = z
21
+ .string()
22
+ .regex(/^\d{1,2}:\d{2}(\/(?:km|mi|100[my]))?$/, "Pace must be in format MM:SS or MM:SS/unit (e.g., '5:30/km', '8:00/mi', '1:45/100m')");
23
+ /**
24
+ * Duration string validation - formats like "30", "30min", "1:30", "90s"
25
+ */
26
+ export const DurationStringSchema = z
27
+ .string()
28
+ .regex(/^(\d+(?::\d{2})?(?:min|s|h)?|\d+)$/, "Duration must be a number or format like '30min', '1:30', '90s'");
29
+ export const AthletePacesSchema = z
30
+ .object({
31
+ // Running paces
32
+ easy: PaceStringSchema.optional(),
33
+ long: PaceStringSchema.optional(),
34
+ tempo: PaceStringSchema.optional(),
35
+ threshold: PaceStringSchema.optional(),
36
+ marathon: PaceStringSchema.optional(),
37
+ halfMarathon: PaceStringSchema.optional(),
38
+ interval: PaceStringSchema.optional(),
39
+ // Interval-specific paces
40
+ r200: PaceStringSchema.optional(),
41
+ r400: PaceStringSchema.optional(),
42
+ r800: PaceStringSchema.optional(),
43
+ r1k: PaceStringSchema.optional(),
44
+ rMile: PaceStringSchema.optional(),
45
+ // Cycling
46
+ bikeFtp: z.number().positive().optional(),
47
+ bikeEasy: z.string().optional(),
48
+ bikeTempo: z.string().optional(),
49
+ bikeThreshold: z.string().optional(),
50
+ // Swimming
51
+ swimCss: PaceStringSchema.optional(),
52
+ swimEasy: PaceStringSchema.optional(),
53
+ swimTempo: PaceStringSchema.optional(),
54
+ })
55
+ .refine((paces) => Object.values(paces).some((v) => v !== undefined), "At least one pace value must be provided");
56
+ export const HRZoneConfigSchema = z.object({
57
+ lthr: z.number().int().min(100).max(220),
58
+ maxHR: z.number().int().min(120).max(230).optional(),
59
+ restingHR: z.number().int().min(30).max(100).optional(),
60
+ });
61
+ export const AthleteZonesSchema = z.object({
62
+ hr: HRZoneConfigSchema.optional(),
63
+ });
64
+ export const AthleteConstraintsSchema = z.object({
65
+ daysPerWeek: z.union([z.number().int().min(1).max(7), z.string()]).optional(),
66
+ preferredDays: z.array(z.string()).optional(),
67
+ maxLongRunHours: z.number().positive().optional(),
68
+ maxLongBikeHours: z.number().positive().optional(),
69
+ notes: z.array(z.string()).optional(),
70
+ });
71
+ export const CompactAthleteSchema = z.object({
72
+ name: z.string().min(1),
73
+ event: z.string().min(1),
74
+ eventDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in ISO format: YYYY-MM-DD"),
75
+ paces: AthletePacesSchema,
76
+ zones: AthleteZonesSchema.optional(),
77
+ constraints: AthleteConstraintsSchema.optional(),
78
+ unit: DistanceUnitSchema.default("km"),
79
+ firstDayOfWeek: FirstDayOfWeekSchema.default("monday"),
80
+ });
81
+ // ============================================================================
82
+ // Training Phases
83
+ // ============================================================================
84
+ /**
85
+ * Week range can be "1-3" or [1, 2, 3]
86
+ */
87
+ export const WeekRangeSchema = z.union([
88
+ z.string().regex(/^\d+-\d+$/, "Week range must be in format 'start-end' (e.g., '1-3')"),
89
+ z.array(z.number().int().positive()),
90
+ ]);
91
+ export const CompactPhaseSchema = z.object({
92
+ name: z.string().min(1),
93
+ weeks: WeekRangeSchema,
94
+ focus: z.string().min(1),
95
+ keyWorkouts: z.array(z.string()).optional(),
96
+ });
97
+ // ============================================================================
98
+ // Weekly Schedule
99
+ // ============================================================================
100
+ /**
101
+ * Workout reference validation.
102
+ * Format: "template.id" or "template.id(param)" or "template.id(param1, param2)"
103
+ */
104
+ export const WorkoutRefSchema = z
105
+ .string()
106
+ .regex(/^[a-zA-Z0-9_.]+(?:\([^)]*\))?$/, "Workout reference must be in format 'template' or 'template(params)' (e.g., 'easy(30)', 'intervals.400(6)')");
107
+ /**
108
+ * A day can have a single workout or multiple (for brick days).
109
+ */
110
+ export const DayWorkoutSchema = z.union([WorkoutRefSchema, z.array(WorkoutRefSchema)]);
111
+ /**
112
+ * Week schedule maps days to workouts.
113
+ */
114
+ export const CompactWeekScheduleSchema = z.record(z.string(), // Day key: Mon, Tue, etc.
115
+ DayWorkoutSchema);
116
+ export const CompactWeekSchema = z.object({
117
+ week: z.number().int().positive(),
118
+ phase: z.string().min(1),
119
+ focus: z.string().optional(),
120
+ isRecoveryWeek: z.boolean().optional(),
121
+ targetHours: z.number().positive().optional(),
122
+ workouts: CompactWeekScheduleSchema,
123
+ });
124
+ // ============================================================================
125
+ // Race Strategy
126
+ // ============================================================================
127
+ export const CompactRaceStrategySchema = z.object({
128
+ goalTime: z.string().optional(),
129
+ pacing: z
130
+ .object({
131
+ swim: z.string().optional(),
132
+ bike: z.string().optional(),
133
+ run: z.string().optional(),
134
+ })
135
+ .optional(),
136
+ nutrition: z.string().optional(),
137
+ notes: z.array(z.string()).optional(),
138
+ });
139
+ // ============================================================================
140
+ // Complete Compact Plan
141
+ // ============================================================================
142
+ export const CompactPlanSchema = z
143
+ .object({
144
+ version: z.literal("2.0"),
145
+ athlete: CompactAthleteSchema,
146
+ phases: z.array(CompactPhaseSchema).min(1),
147
+ weeks: z.array(CompactWeekSchema).min(1),
148
+ raceStrategy: CompactRaceStrategySchema.optional(),
149
+ })
150
+ .refine((plan) => {
151
+ // Validate that all weeks reference valid phases
152
+ const phaseNames = new Set(plan.phases.map((p) => p.name));
153
+ return plan.weeks.every((w) => phaseNames.has(w.phase));
154
+ }, {
155
+ message: "All weeks must reference a valid phase name",
156
+ path: ["weeks"],
157
+ })
158
+ .refine((plan) => {
159
+ // Validate week numbers are sequential and start from 1
160
+ const weekNumbers = plan.weeks
161
+ .map((w) => w.week)
162
+ .sort((a, b) => a - b);
163
+ for (let i = 0; i < weekNumbers.length; i++) {
164
+ if (weekNumbers[i] !== i + 1) {
165
+ return false;
166
+ }
167
+ }
168
+ return true;
169
+ }, {
170
+ message: "Week numbers must be sequential starting from 1",
171
+ path: ["weeks"],
172
+ });
173
+ /**
174
+ * Validate a compact training plan against the schema.
175
+ */
176
+ export function validateCompactPlan(data) {
177
+ const result = CompactPlanSchema.safeParse(data);
178
+ if (result.success) {
179
+ return { success: true, data: result.data };
180
+ }
181
+ const errors = result.error.issues.map((issue) => ({
182
+ path: issue.path.join("."),
183
+ message: issue.message,
184
+ code: issue.code,
185
+ }));
186
+ return { success: false, errors };
187
+ }
188
+ /**
189
+ * Validate a compact plan and throw an error if invalid.
190
+ */
191
+ export function validateCompactPlanOrThrow(data) {
192
+ return CompactPlanSchema.parse(data);
193
+ }
194
+ /**
195
+ * Format validation errors into a human-readable string.
196
+ */
197
+ export function formatCompactValidationErrors(errors) {
198
+ if (errors.length === 0)
199
+ return "No errors";
200
+ const lines = errors.map((e, i) => {
201
+ const path = e.path || "(root)";
202
+ return ` ${i + 1}. ${path}: ${e.message}`;
203
+ });
204
+ return `Validation failed with ${errors.length} error(s):\n${lines.join("\n")}`;
205
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Templates Module
3
+ *
4
+ * Re-exports all template-related types and functions.
5
+ */
6
+ export type { WorkoutTemplate, TemplateParam, TemplateParams, TemplateStep, TemplateIntervalSet, TemplateStructure, TemplateRegistry, InterpolationContext, ExpandedWorkout, ParamType, StepType, WorkoutCategory, MainSetElement, } from "./template.types.js";
7
+ export { WorkoutTemplateSchema, TemplateParamSchema, TemplateParamsSchema, TemplateStepSchema, TemplateStructureSchema, validateTemplate, validateTemplateOrThrow, } from "./template.schema.js";
8
+ export { loadTemplates, loadTemplatesFromArray, getTemplatesPath } from "./loader.js";
9
+ export { parse as parseYaml, stringify as stringifyYaml } from "./yaml-parser.js";
10
+ export { interpolate, interpolateObject, evaluateExpression, createContext, hasInterpolation, extractVariables, } from "./interpolate.js";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Templates Module
3
+ *
4
+ * Re-exports all template-related types and functions.
5
+ */
6
+ // Schema validation
7
+ export { WorkoutTemplateSchema, TemplateParamSchema, TemplateParamsSchema, TemplateStepSchema, TemplateStructureSchema, validateTemplate, validateTemplateOrThrow, } from "./template.schema.js";
8
+ // Loader
9
+ export { loadTemplates, loadTemplatesFromArray, getTemplatesPath } from "./loader.js";
10
+ // YAML utilities
11
+ export { parse as parseYaml, stringify as stringifyYaml } from "./yaml-parser.js";
12
+ // Interpolation
13
+ export { interpolate, interpolateObject, evaluateExpression, createContext, hasInterpolation, extractVariables, } from "./interpolate.js";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Variable Interpolation Engine
3
+ *
4
+ * Replaces ${variable} patterns in template strings with actual values.
5
+ * Supports:
6
+ * - Simple variables: ${paces.easy} → "5:30/km"
7
+ * - Math expressions: ${10 + (reps * 3)} → "22"
8
+ * - Nested access: ${zones.hr.lthr} → "170"
9
+ */
10
+ import type { InterpolationContext } from "./template.types.js";
11
+ /**
12
+ * Evaluate a simple math expression with variable substitution.
13
+ *
14
+ * This uses a safe evaluation approach that only allows basic math operations.
15
+ * Variables in the expression are first replaced with their values from context.
16
+ *
17
+ * @example
18
+ * evaluateExpression("10 + (reps * 3)", { reps: 4 }) // 22
19
+ */
20
+ export declare function evaluateExpression(expr: string, context: InterpolationContext): string | number;
21
+ /**
22
+ * Interpolate all ${variable} patterns in a string.
23
+ *
24
+ * @example
25
+ * interpolate("Run ${duration} min @ ${paces.easy}", {
26
+ * duration: 30,
27
+ * paces: { easy: "5:30/km" }
28
+ * })
29
+ * // "Run 30 min @ 5:30/km"
30
+ */
31
+ export declare function interpolate(template: string, context: InterpolationContext): string;
32
+ /**
33
+ * Interpolate all string values in an object recursively.
34
+ */
35
+ export declare function interpolateObject<T>(obj: T, context: InterpolationContext): T;
36
+ /**
37
+ * Create an interpolation context from a compact plan's athlete data.
38
+ */
39
+ export declare function createContext(paces: Record<string, string | undefined>, zones?: InterpolationContext["zones"], params?: Record<string, unknown>): InterpolationContext;
40
+ /**
41
+ * Check if a string contains any interpolation markers.
42
+ */
43
+ export declare function hasInterpolation(str: string): boolean;
44
+ /**
45
+ * Extract all variable names from a template string.
46
+ *
47
+ * @example
48
+ * extractVariables("Run ${duration} min @ ${paces.easy}")
49
+ * // ["duration", "paces.easy"]
50
+ */
51
+ export declare function extractVariables(template: string): string[];