hevy-shared 1.0.842 → 1.0.844

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/built/index.d.ts CHANGED
@@ -569,9 +569,6 @@ export interface UserProfile {
569
569
  weekly_workout_durations: WeeklyWorkoutDuration[];
570
570
  mutual_followers: PreviewMutualUser[];
571
571
  }
572
- export interface UserWorkoutStreakCount {
573
- streak_count: number;
574
- }
575
572
  export interface PublicUserProfile {
576
573
  private_profile: boolean;
577
574
  username: string;
@@ -1864,6 +1861,3 @@ export interface FolderLinkPreviewMetadataResponse extends LinkPreviewMetadataRe
1864
1861
  username: string;
1865
1862
  routine_count: number;
1866
1863
  }
1867
- export interface StartTimeableWorkout {
1868
- start_time: number;
1869
- }
@@ -1,9 +1,5 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const dayjs_1 = __importDefault(require("dayjs"));
7
3
  const utils_1 = require("../utils");
8
4
  describe('utils', () => {
9
5
  describe('isValidUsername', () => {
@@ -325,6 +321,139 @@ describe('utils', () => {
325
321
  expect((0, utils_1.formatDurationInput)('99:99')).toBe('99:99');
326
322
  });
327
323
  });
324
+ describe('getEstimatedExercisesDurationSeconds', () => {
325
+ const testCases = [
326
+ {
327
+ exercises: [
328
+ {
329
+ exercise_type: 'duration',
330
+ rest_seconds: 10,
331
+ sets: [{ duration_seconds: 50, indicator: 'normal' }],
332
+ },
333
+ ],
334
+ expectedDuration: 50 + 10,
335
+ },
336
+ {
337
+ exercises: [
338
+ {
339
+ exercise_type: 'floors_duration',
340
+ rest_seconds: 10,
341
+ sets: [{ duration_seconds: 0, indicator: 'normal' }],
342
+ },
343
+ ],
344
+ expectedDuration: 10,
345
+ },
346
+ {
347
+ exercises: [
348
+ {
349
+ exercise_type: 'weight_duration',
350
+ rest_seconds: null,
351
+ sets: [{ duration_seconds: 30, indicator: 'normal' }],
352
+ },
353
+ ],
354
+ expectedDuration: 30 + utils_1.ESTIMATED_REST_TIMER_DURATION,
355
+ },
356
+ {
357
+ exercises: [
358
+ {
359
+ exercise_type: 'distance_duration',
360
+ rest_seconds: 120,
361
+ sets: [{ duration_seconds: 40, indicator: 'normal' }],
362
+ },
363
+ ],
364
+ expectedDuration: 40 + 120,
365
+ },
366
+ {
367
+ exercises: [
368
+ {
369
+ exercise_type: 'weight_duration',
370
+ rest_seconds: 120,
371
+ sets: [{ duration_seconds: null, indicator: 'normal' }],
372
+ },
373
+ ],
374
+ expectedDuration: 120 + utils_1.ESTIMATED_SET_DURATION,
375
+ },
376
+ {
377
+ exercises: [
378
+ {
379
+ exercise_type: 'duration',
380
+ rest_seconds: 0,
381
+ sets: [{ duration_seconds: null, indicator: 'normal' }],
382
+ },
383
+ ],
384
+ expectedDuration: utils_1.ESTIMATED_SET_DURATION,
385
+ },
386
+ {
387
+ exercises: [
388
+ {
389
+ exercise_type: 'reps_only',
390
+ rest_seconds: 180,
391
+ sets: [{ duration_seconds: null, indicator: 'normal' }],
392
+ },
393
+ {
394
+ exercise_type: 'reps_only',
395
+ rest_seconds: 180,
396
+ sets: [
397
+ {
398
+ duration_seconds: 0,
399
+ indicator: 'normal',
400
+ },
401
+ {
402
+ duration_seconds: 0,
403
+ indicator: 'normal',
404
+ },
405
+ {
406
+ duration_seconds: 0,
407
+ indicator: 'normal',
408
+ },
409
+ {
410
+ duration_seconds: 0,
411
+ indicator: 'normal',
412
+ },
413
+ ],
414
+ },
415
+ ],
416
+ expectedDuration: (180 + utils_1.ESTIMATED_SET_DURATION) * 5,
417
+ },
418
+ {
419
+ exercises: [
420
+ {
421
+ exercise_type: 'reps_only',
422
+ rest_seconds: null,
423
+ sets: [{ duration_seconds: null, indicator: 'normal' }],
424
+ },
425
+ {
426
+ exercise_type: 'reps_only',
427
+ rest_seconds: null,
428
+ sets: [
429
+ {
430
+ duration_seconds: 0,
431
+ indicator: 'normal',
432
+ },
433
+ {
434
+ duration_seconds: 0,
435
+ indicator: 'dropset',
436
+ },
437
+ {
438
+ duration_seconds: 0,
439
+ indicator: 'normal',
440
+ },
441
+ {
442
+ duration_seconds: 0,
443
+ indicator: 'dropset',
444
+ },
445
+ ],
446
+ },
447
+ ],
448
+ expectedDuration: utils_1.ESTIMATED_REST_TIMER_DURATION * 3 + 5 * utils_1.ESTIMATED_SET_DURATION,
449
+ },
450
+ ];
451
+ testCases.forEach((testCase) => {
452
+ it(`getEstimatedExercisesDurationSeconds`, () => {
453
+ expect((0, utils_1.getEstimatedExercisesDurationSeconds)(testCase)).toBe(testCase.expectedDuration);
454
+ });
455
+ });
456
+ });
328
457
  describe('splitAtUsernamesAndLinks', () => {
329
458
  it('Takes a string and returns an array of FormatAtText', () => {
330
459
  const inputOutputs = [
@@ -510,60 +639,4 @@ describe('utils', () => {
510
639
  expect((0, utils_1.getYoutubeVideoId)('https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstleyVEVO')).toBe('dQw4w9WgXcQ');
511
640
  });
512
641
  });
513
- describe('calculateCurrentWeekStreak', () => {
514
- it("Doesn't include gaps in the streak count", () => {
515
- const workouts = [
516
- { start_time: (0, dayjs_1.default)('2025-11-05').unix() },
517
- { start_time: (0, dayjs_1.default)('2025-11-02').unix() },
518
- { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
519
- { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
520
- { start_time: (0, dayjs_1.default)('2025-10-10').unix() },
521
- ];
522
- const firstWeekday = 'sunday';
523
- const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
524
- expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(3);
525
- });
526
- it("Calculates a potentially non-zero streak when the user hasn't worked out this week yet", () => {
527
- const workouts = [
528
- { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
529
- { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
530
- { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
531
- { start_time: (0, dayjs_1.default)('2025-10-14').unix() + 1 },
532
- { start_time: (0, dayjs_1.default)('2025-10-12').unix() },
533
- ];
534
- const firstWeekday = 'monday';
535
- const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
536
- expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(4);
537
- });
538
- it('Includes this week in the streak count if the user worked out this week', () => {
539
- const workouts = [
540
- { start_time: (0, dayjs_1.default)('2025-11-05').unix() },
541
- { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
542
- { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
543
- { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
544
- { start_time: (0, dayjs_1.default)('2025-10-14').unix() },
545
- { start_time: (0, dayjs_1.default)('2025-10-07').unix() },
546
- ];
547
- const firstWeekday = 'monday';
548
- const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
549
- expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(5);
550
- });
551
- it("Returns 0 if the user hasn't worked out", () => {
552
- const workouts = [];
553
- const firstWeekday = 'friday';
554
- const untilUnix = (0, dayjs_1.default)('2025-10-10').unix();
555
- expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(0);
556
- });
557
- it('Returns 0 if the user has not worked out this week and last week', () => {
558
- const workouts = [
559
- { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
560
- { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
561
- { start_time: (0, dayjs_1.default)('2025-10-13').unix() },
562
- { start_time: (0, dayjs_1.default)('2025-10-07').unix() },
563
- ];
564
- const firstWeekday = 'sunday';
565
- const untilUnix = (0, dayjs_1.default)('2025-11-05').unix();
566
- expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(0);
567
- });
568
- });
569
642
  });
package/built/utils.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { Dayjs } from 'dayjs';
2
- import { BaseExerciseTemplate, FormatAtText, Result, StartTimeableWorkout, StrengthLevel, UserExerciseSet, Weekday } from '.';
1
+ import { BaseExerciseTemplate, ExerciseType, FormatAtText, Result, SetType, StrengthLevel, UserExerciseSet, UserFacingSetIndicator } from '.';
3
2
  /**
4
3
  * Doesn't matter what you throw in the function it'll
5
4
  * always return a number. Non number values will return
@@ -176,14 +175,19 @@ export interface TotalSetCountWorkout {
176
175
  */
177
176
  export declare const userExerciseSetWeight: (set: UserExerciseSet, exerciseStore: BaseExerciseTemplate[], hundredPercentBodyweightExercise: boolean) => number;
178
177
  export declare const workoutSetCount: (w: TotalSetCountWorkout) => number;
178
+ export declare const UserFacingIndicatorToSetIndicator: (indicator: UserFacingSetIndicator) => SetType;
179
179
  interface GetEstimatedExercisesDuration {
180
180
  exercises: {
181
181
  rest_seconds: number | null;
182
+ exercise_type: ExerciseType;
182
183
  sets: {
183
184
  duration_seconds?: number | null;
185
+ indicator: SetType;
184
186
  }[];
185
187
  }[];
186
188
  }
189
+ export declare const ESTIMATED_SET_DURATION = 45;
190
+ export declare const ESTIMATED_REST_TIMER_DURATION = 90;
187
191
  export declare const getEstimatedExercisesDurationSeconds: ({ exercises, }: GetEstimatedExercisesDuration) => number;
188
192
  export declare const oneRepMaxPercentageMap: {
189
193
  [s: number]: number;
@@ -236,10 +240,4 @@ export declare const isVersionAGreaterOrEqualToVersionB: (versionA: string, vers
236
240
  export declare const splitAtUsernamesAndLinks: (text: string) => FormatAtText[];
237
241
  export declare const validateYoutubeUrl: (url: string) => boolean;
238
242
  export declare const getYoutubeVideoId: (url: string) => string | undefined;
239
- /**@param workouts must be sorted descending by start_time */
240
- export declare const calculateCurrentWeekStreak: (workouts: StartTimeableWorkout[], firstWeekday: Weekday, untilUnix?: number) => number;
241
- export declare const startOfWeek: (d: Dayjs, firstDayOfWeek: Weekday) => Dayjs;
242
- export declare const weekdayNumberMap: {
243
- [key in Weekday]: number;
244
- };
245
243
  export {};
package/built/utils.js CHANGED
@@ -1,10 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.weekdayNumberMap = exports.startOfWeek = exports.calculateCurrentWeekStreak = exports.getYoutubeVideoId = exports.validateYoutubeUrl = exports.splitAtUsernamesAndLinks = exports.isVersionAGreaterOrEqualToVersionB = exports.generateUserGroupValue = exports.generateUserGroup = exports.isBaseExerciseTemplate = exports.getStrengthLevelFromPercentile = exports.numberToLocaleString = exports.numberWithCommas = exports.setVolume = exports.oneRepMax = exports.oneRepMaxPercentageMap = exports.getEstimatedExercisesDurationSeconds = exports.workoutSetCount = exports.userExerciseSetWeight = exports.workoutDistanceMeters = exports.workoutReps = exports.workoutDurationSeconds = exports.removeAccents = exports.getClosestDataPointAroundTargetDate = exports.getClosestDataPointBeforeTargetDate = exports.findMapped = exports.stringToNumber = exports.forceStringToNumber = exports.formatDurationInput = exports.isValidFormattedTime = exports.isWholeNumber = exports.isNumber = exports.isValidUuid = exports.isValidPhoneNumber = exports.isValidWebUrl = exports.URL_REGEX = exports.isValidEmail = exports.secondsToWordFormatMinutes = exports.secondsToWordFormat = exports.secondsToClockFormat = exports.secondsToClockParts = exports.isValidUsername = exports.roundToWholeNumber = exports.roundToOneDecimal = exports.roundToTwoDecimal = exports.divide = exports.clampNumber = exports.num = void 0;
7
- const dayjs_1 = __importDefault(require("dayjs"));
3
+ exports.getYoutubeVideoId = exports.validateYoutubeUrl = exports.splitAtUsernamesAndLinks = exports.isVersionAGreaterOrEqualToVersionB = exports.generateUserGroupValue = exports.generateUserGroup = exports.isBaseExerciseTemplate = exports.getStrengthLevelFromPercentile = exports.numberToLocaleString = exports.numberWithCommas = exports.setVolume = exports.oneRepMax = exports.oneRepMaxPercentageMap = exports.getEstimatedExercisesDurationSeconds = exports.ESTIMATED_REST_TIMER_DURATION = exports.ESTIMATED_SET_DURATION = exports.UserFacingIndicatorToSetIndicator = exports.workoutSetCount = exports.userExerciseSetWeight = exports.workoutDistanceMeters = exports.workoutReps = exports.workoutDurationSeconds = exports.removeAccents = exports.getClosestDataPointAroundTargetDate = exports.getClosestDataPointBeforeTargetDate = exports.findMapped = exports.stringToNumber = exports.forceStringToNumber = exports.formatDurationInput = exports.isValidFormattedTime = exports.isWholeNumber = exports.isNumber = exports.isValidUuid = exports.isValidPhoneNumber = exports.isValidWebUrl = exports.URL_REGEX = exports.isValidEmail = exports.secondsToWordFormatMinutes = exports.secondsToWordFormat = exports.secondsToClockFormat = exports.secondsToClockParts = exports.isValidUsername = exports.roundToWholeNumber = exports.roundToOneDecimal = exports.roundToTwoDecimal = exports.divide = exports.clampNumber = exports.num = void 0;
8
4
  /**
9
5
  * Doesn't matter what you throw in the function it'll
10
6
  * always return a number. Non number values will return
@@ -142,7 +138,7 @@ exports.URL_REGEX = RegExp([
142
138
  // followed by the top-level domain
143
139
  /[a-z]{2,10}/,
144
140
  // optionally followed by a path
145
- /(\/[-a-z0-9@:%_+.~#?&/=]*)?/,
141
+ /(\/[-a-z0-9@:%_+.~#?!&/=]*)?/,
146
142
  ]
147
143
  .map((r) => r.source)
148
144
  .join(''));
@@ -448,18 +444,45 @@ const workoutSetCount = (w) => {
448
444
  }, 0);
449
445
  };
450
446
  exports.workoutSetCount = workoutSetCount;
451
- const ESTIMATED_SET_DURATION = 45;
452
- const ESTIMATED_REST_TIMER_DURATION = 90;
447
+ const UserFacingIndicatorToSetIndicator = (indicator) => {
448
+ switch (indicator) {
449
+ case 'dropset':
450
+ return 'dropset';
451
+ case 'warmup':
452
+ return 'warmup';
453
+ case 'failure':
454
+ return 'failure';
455
+ default:
456
+ return 'normal';
457
+ }
458
+ };
459
+ exports.UserFacingIndicatorToSetIndicator = UserFacingIndicatorToSetIndicator;
460
+ exports.ESTIMATED_SET_DURATION = 45;
461
+ exports.ESTIMATED_REST_TIMER_DURATION = 90;
462
+ const isDurationExercise = (type) => {
463
+ return (type === 'duration' ||
464
+ type === 'weight_duration' ||
465
+ type === 'distance_duration' ||
466
+ type === 'floors_duration' ||
467
+ type === 'steps_duration');
468
+ };
453
469
  const getEstimatedExercisesDurationSeconds = ({ exercises, }) => {
454
470
  const totalSeconds = exercises.reduce((exercisesTotal, exercise) => {
455
471
  var _a;
456
- const restPerSet = (_a = exercise.rest_seconds) !== null && _a !== void 0 ? _a : ESTIMATED_REST_TIMER_DURATION;
457
- const exerciseTotal = exercise.sets.reduce((setTotal, set) => {
472
+ const restPerSet = (_a = exercise.rest_seconds) !== null && _a !== void 0 ? _a : exports.ESTIMATED_REST_TIMER_DURATION;
473
+ const setsTotal = exercise.sets.reduce((setTotal, set) => {
458
474
  var _a;
459
- const duration = (_a = set.duration_seconds) !== null && _a !== void 0 ? _a : ESTIMATED_SET_DURATION;
475
+ // Sometimes we get 0 values for duration on exercises that aren't duration.
476
+ // Added this check to prevent having a estimated duration of 0 for these.
477
+ const duration = isDurationExercise(exercise.exercise_type)
478
+ ? (_a = set.duration_seconds) !== null && _a !== void 0 ? _a : exports.ESTIMATED_SET_DURATION
479
+ : exports.ESTIMATED_SET_DURATION;
480
+ // if it is a dropset, we don't add the rest time
481
+ if (set.indicator === 'dropset')
482
+ return setTotal + duration;
460
483
  return setTotal + duration + restPerSet;
461
484
  }, 0);
462
- return exercisesTotal + exerciseTotal;
485
+ return exercisesTotal + setsTotal;
463
486
  }, 0);
464
487
  return totalSeconds;
465
488
  };
@@ -693,56 +716,3 @@ const getYoutubeVideoId = (url) => {
693
716
  return match && match[1] && match[1].length === 11 ? match[1] : undefined;
694
717
  };
695
718
  exports.getYoutubeVideoId = getYoutubeVideoId;
696
- /**@param workouts must be sorted descending by start_time */
697
- const calculateCurrentWeekStreak = (workouts, firstWeekday, untilUnix = (0, dayjs_1.default)().unix()) => {
698
- let streakCount = 0;
699
- let weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday).subtract(1, 'week');
700
- let weekEnd = weekStart.add(1, 'week');
701
- let i = 0;
702
- /** Iterate through the workouts from most recent to least and keep track of the week-long
703
- * sliding window in which there needs to be a workout present in order for the streak to count.
704
- * If there is a workout present during this window, move the window into the past by a week
705
- * and keep iterating through the workouts. If there are no workouts during this window,
706
- * the streak count is not incremented.
707
- */
708
- while (i < workouts.length) {
709
- const workoutStart = workouts[i].start_time;
710
- if (workoutStart >= weekStart.unix() && workoutStart <= weekEnd.unix()) {
711
- streakCount += 1;
712
- weekStart = weekStart.subtract(1, 'week');
713
- weekEnd = weekEnd.subtract(1, 'week');
714
- }
715
- else if (workoutStart < weekStart.unix()) {
716
- /** This workout is before the sliding window. We don't get to this point unless
717
- * the user didn't workout during the window. So that means the streak is over
718
- */
719
- break;
720
- }
721
- i += 1;
722
- }
723
- weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday);
724
- weekEnd = weekStart.add(1, 'week');
725
- if (workouts.find(({ start_time }) => start_time >= weekStart.unix() && start_time <= weekEnd.unix())) {
726
- streakCount += 1;
727
- }
728
- return streakCount;
729
- };
730
- exports.calculateCurrentWeekStreak = calculateCurrentWeekStreak;
731
- const startOfWeek = (d, firstDayOfWeek) => {
732
- const firstDayOfWeekIndex = exports.weekdayNumberMap[firstDayOfWeek];
733
- let testDay = d;
734
- while (testDay.day() !== firstDayOfWeekIndex) {
735
- testDay = testDay.subtract(1, 'day');
736
- }
737
- return testDay.startOf('day');
738
- };
739
- exports.startOfWeek = startOfWeek;
740
- exports.weekdayNumberMap = {
741
- sunday: 0,
742
- monday: 1,
743
- tuesday: 2,
744
- wednesday: 3,
745
- thursday: 4,
746
- friday: 5,
747
- saturday: 6,
748
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.842",
3
+ "version": "1.0.844",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",
@@ -46,7 +46,6 @@
46
46
  "typescript": "5.8.2"
47
47
  },
48
48
  "dependencies": {
49
- "dayjs": "^1.11.19",
50
49
  "lodash": "^4.17.21"
51
50
  }
52
51
  }