hevy-shared 1.0.844 → 1.0.845

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,6 +569,9 @@ 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
+ }
572
575
  export interface PublicUserProfile {
573
576
  private_profile: boolean;
574
577
  username: string;
@@ -1,5 +1,9 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const dayjs_1 = __importDefault(require("dayjs"));
3
7
  const utils_1 = require("../utils");
4
8
  describe('utils', () => {
5
9
  describe('isValidUsername', () => {
@@ -639,4 +643,60 @@ describe('utils', () => {
639
643
  expect((0, utils_1.getYoutubeVideoId)('https://www.youtube.com/watch?v=dQw4w9WgXcQ&ab_channel=RickAstleyVEVO')).toBe('dQw4w9WgXcQ');
640
644
  });
641
645
  });
646
+ describe('calculateCurrentWeekStreak', () => {
647
+ it("Doesn't include gaps in the streak count", () => {
648
+ const workouts = [
649
+ { start_time: (0, dayjs_1.default)('2025-11-05').unix() },
650
+ { start_time: (0, dayjs_1.default)('2025-11-02').unix() },
651
+ { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
652
+ { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
653
+ { start_time: (0, dayjs_1.default)('2025-10-10').unix() },
654
+ ];
655
+ const firstWeekday = 'sunday';
656
+ const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
657
+ expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(3);
658
+ });
659
+ it("Calculates a potentially non-zero streak when the user hasn't worked out this week yet", () => {
660
+ const workouts = [
661
+ { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
662
+ { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
663
+ { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
664
+ { start_time: (0, dayjs_1.default)('2025-10-14').unix() + 1 },
665
+ { start_time: (0, dayjs_1.default)('2025-10-12').unix() },
666
+ ];
667
+ const firstWeekday = 'monday';
668
+ const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
669
+ expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(4);
670
+ });
671
+ it('Includes this week in the streak count if the user worked out this week', () => {
672
+ const workouts = [
673
+ { start_time: (0, dayjs_1.default)('2025-11-05').unix() },
674
+ { start_time: (0, dayjs_1.default)('2025-10-31').unix() },
675
+ { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
676
+ { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
677
+ { start_time: (0, dayjs_1.default)('2025-10-14').unix() },
678
+ { start_time: (0, dayjs_1.default)('2025-10-07').unix() },
679
+ ];
680
+ const firstWeekday = 'monday';
681
+ const untilUnix = (0, dayjs_1.default)('2025-11-06').unix();
682
+ expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(5);
683
+ });
684
+ it("Returns 0 if the user hasn't worked out", () => {
685
+ const workouts = [];
686
+ const firstWeekday = 'friday';
687
+ const untilUnix = (0, dayjs_1.default)('2025-10-10').unix();
688
+ expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(0);
689
+ });
690
+ it('Returns 0 if the user has not worked out this week and last week', () => {
691
+ const workouts = [
692
+ { start_time: (0, dayjs_1.default)('2025-10-23').unix() },
693
+ { start_time: (0, dayjs_1.default)('2025-10-20').unix() },
694
+ { start_time: (0, dayjs_1.default)('2025-10-13').unix() },
695
+ { start_time: (0, dayjs_1.default)('2025-10-07').unix() },
696
+ ];
697
+ const firstWeekday = 'sunday';
698
+ const untilUnix = (0, dayjs_1.default)('2025-11-05').unix();
699
+ expect((0, utils_1.calculateCurrentWeekStreak)(workouts, firstWeekday, untilUnix)).toBe(0);
700
+ });
701
+ });
642
702
  });
package/built/utils.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { BaseExerciseTemplate, ExerciseType, FormatAtText, Result, SetType, StrengthLevel, UserExerciseSet, UserFacingSetIndicator } from '.';
1
+ import { Dayjs } from 'dayjs';
2
+ import { BaseExerciseTemplate, ExerciseType, FormatAtText, Result, SetType, StrengthLevel, UserExerciseSet, UserFacingSetIndicator, Weekday } from '.';
2
3
  /**
3
4
  * Doesn't matter what you throw in the function it'll
4
5
  * always return a number. Non number values will return
@@ -240,4 +241,12 @@ export declare const isVersionAGreaterOrEqualToVersionB: (versionA: string, vers
240
241
  export declare const splitAtUsernamesAndLinks: (text: string) => FormatAtText[];
241
242
  export declare const validateYoutubeUrl: (url: string) => boolean;
242
243
  export declare const getYoutubeVideoId: (url: string) => string | undefined;
244
+ /**@param workouts must be sorted descending by start_time */
245
+ export declare const calculateCurrentWeekStreak: (workouts: {
246
+ start_time: number;
247
+ }[], firstWeekday: Weekday, untilUnix?: number) => number;
248
+ export declare const startOfWeek: (d: Dayjs, firstDayOfWeek: Weekday) => Dayjs;
249
+ export declare const weekdayNumberMap: {
250
+ [key in Weekday]: number;
251
+ };
243
252
  export {};
package/built/utils.js CHANGED
@@ -1,6 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
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;
6
+ 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.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;
7
+ exports.weekdayNumberMap = void 0;
8
+ const dayjs_1 = __importDefault(require("dayjs"));
4
9
  /**
5
10
  * Doesn't matter what you throw in the function it'll
6
11
  * always return a number. Non number values will return
@@ -716,3 +721,57 @@ const getYoutubeVideoId = (url) => {
716
721
  return match && match[1] && match[1].length === 11 ? match[1] : undefined;
717
722
  };
718
723
  exports.getYoutubeVideoId = getYoutubeVideoId;
724
+ /**@param workouts must be sorted descending by start_time */
725
+ const calculateCurrentWeekStreak = (workouts, firstWeekday, untilUnix = (0, dayjs_1.default)().unix()) => {
726
+ let streakCount = 0;
727
+ let weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday).subtract(1, 'week');
728
+ let weekEnd = weekStart.add(1, 'week');
729
+ let i = 0;
730
+ /** Iterate through the workouts from most recent to least and keep track of the week-long
731
+ * sliding window in which there needs to be a workout present in order for the streak to count.
732
+ * If there is a workout present during this window, move the window into the past by a week
733
+ * and keep iterating through the workouts. If there are no workouts during this window,
734
+ * the streak count is not incremented.
735
+ */
736
+ while (i < workouts.length) {
737
+ const workoutStart = workouts[i].start_time;
738
+ if (workoutStart >= weekStart.unix() && workoutStart <= weekEnd.unix()) {
739
+ streakCount += 1;
740
+ weekStart = weekStart.subtract(1, 'week');
741
+ weekEnd = weekEnd.subtract(1, 'week');
742
+ }
743
+ else if (workoutStart < weekStart.unix()) {
744
+ /** This workout is before the sliding window. We don't get to this point unless
745
+ * the user didn't workout during the window. So that means the streak is over
746
+ */
747
+ break;
748
+ }
749
+ i += 1;
750
+ }
751
+ // check if the current week has any workouts
752
+ weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday);
753
+ weekEnd = weekStart.add(1, 'week');
754
+ if (workouts.find(({ start_time }) => start_time >= weekStart.unix() && start_time <= weekEnd.unix())) {
755
+ streakCount += 1;
756
+ }
757
+ return streakCount;
758
+ };
759
+ exports.calculateCurrentWeekStreak = calculateCurrentWeekStreak;
760
+ const startOfWeek = (d, firstDayOfWeek) => {
761
+ const firstDayOfWeekIndex = exports.weekdayNumberMap[firstDayOfWeek];
762
+ let testDay = d;
763
+ while (testDay.day() !== firstDayOfWeekIndex) {
764
+ testDay = testDay.subtract(1, 'day');
765
+ }
766
+ return testDay.startOf('day');
767
+ };
768
+ exports.startOfWeek = startOfWeek;
769
+ exports.weekdayNumberMap = {
770
+ sunday: 0,
771
+ monday: 1,
772
+ tuesday: 2,
773
+ wednesday: 3,
774
+ thursday: 4,
775
+ friday: 5,
776
+ saturday: 6,
777
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.844",
3
+ "version": "1.0.845",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",
@@ -46,6 +46,7 @@
46
46
  "typescript": "5.8.2"
47
47
  },
48
48
  "dependencies": {
49
+ "dayjs": "^1.11.19",
49
50
  "lodash": "^4.17.21"
50
51
  }
51
52
  }