hevy-shared 1.0.960 → 1.0.962

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 (88) hide show
  1. package/.eslintignore +2 -0
  2. package/.eslintrc +21 -0
  3. package/.github/workflows/ci.yml +15 -0
  4. package/.github/workflows/npm-publish.yml +59 -0
  5. package/.github/workflows/pr-auto-assign.yml +15 -0
  6. package/.prettierrc.js +5 -0
  7. package/README.md +2 -17
  8. package/built/chat.d.ts +23 -25
  9. package/built/coachPlans.d.ts +1 -2
  10. package/built/coachPlans.js +2 -2
  11. package/built/filterExercises.d.ts +3 -19
  12. package/built/filterExercises.js +60 -72
  13. package/built/index.d.ts +304 -1140
  14. package/built/index.js +75 -269
  15. package/built/setIndicatorUtils.d.ts +3 -4
  16. package/built/setIndicatorUtils.js +1 -15
  17. package/built/tests/utils.test.js +0 -748
  18. package/built/tests/workoutVolume.test.js +49 -165
  19. package/built/units.d.ts +7 -14
  20. package/built/units.js +14 -24
  21. package/built/utils.d.ts +5 -192
  22. package/built/utils.js +85 -598
  23. package/built/websocket.d.ts +2 -14
  24. package/built/workoutVolume.d.ts +5 -24
  25. package/built/workoutVolume.js +34 -25
  26. package/jest.config.js +4 -0
  27. package/package.json +10 -32
  28. package/src/chat.ts +130 -0
  29. package/src/coachPlans.ts +57 -0
  30. package/src/constants.ts +14 -0
  31. package/src/filterExercises.ts +222 -0
  32. package/src/index.ts +1576 -0
  33. package/src/setIndicatorUtils.ts +137 -0
  34. package/src/tests/utils.test.ts +156 -0
  35. package/src/tests/workoutVolume.test.ts +93 -0
  36. package/src/units.ts +41 -0
  37. package/src/utils.ts +516 -0
  38. package/src/websocket.ts +36 -0
  39. package/src/workoutVolume.ts +175 -0
  40. package/tsconfig.json +70 -0
  41. package/built/API/APIClient.d.ts +0 -157
  42. package/built/API/APIClient.js +0 -381
  43. package/built/API/index.d.ts +0 -2
  44. package/built/API/index.js +0 -18
  45. package/built/API/types.d.ts +0 -38
  46. package/built/API/types.js +0 -18
  47. package/built/adjustEventTokens.d.ts +0 -16
  48. package/built/adjustEventTokens.js +0 -18
  49. package/built/adminPermissions.d.ts +0 -4
  50. package/built/adminPermissions.js +0 -22
  51. package/built/async.d.ts +0 -50
  52. package/built/async.js +0 -170
  53. package/built/cue.d.ts +0 -12
  54. package/built/cue.js +0 -22
  55. package/built/exerciseLocaleUtils.d.ts +0 -17
  56. package/built/exerciseLocaleUtils.js +0 -62
  57. package/built/hevyTrainer.d.ts +0 -250
  58. package/built/hevyTrainer.js +0 -676
  59. package/built/muscleHeatmaps.d.ts +0 -31
  60. package/built/muscleHeatmaps.js +0 -68
  61. package/built/muscleSplits.d.ts +0 -36
  62. package/built/muscleSplits.js +0 -100
  63. package/built/normalizedWorkoutUtils.d.ts +0 -88
  64. package/built/normalizedWorkoutUtils.js +0 -112
  65. package/built/notifications.d.ts +0 -215
  66. package/built/notifications.js +0 -9
  67. package/built/routineUtils.d.ts +0 -14
  68. package/built/routineUtils.js +0 -186
  69. package/built/schemas.d.ts +0 -6
  70. package/built/schemas.js +0 -12
  71. package/built/tests/async.test.d.ts +0 -1
  72. package/built/tests/async.test.js +0 -49
  73. package/built/tests/hevyTrainer.test.d.ts +0 -1
  74. package/built/tests/hevyTrainer.test.js +0 -1199
  75. package/built/tests/muscleSplit.test.d.ts +0 -1
  76. package/built/tests/muscleSplit.test.js +0 -153
  77. package/built/tests/routineUtils.test.d.ts +0 -1
  78. package/built/tests/routineUtils.test.js +0 -745
  79. package/built/tests/testUtils.d.ts +0 -85
  80. package/built/tests/testUtils.js +0 -319
  81. package/built/translations/index.d.ts +0 -2
  82. package/built/translations/index.js +0 -18
  83. package/built/translations/translationUtils.d.ts +0 -2
  84. package/built/translations/translationUtils.js +0 -61
  85. package/built/translations/types.d.ts +0 -8
  86. package/built/translations/types.js +0 -20
  87. package/built/typeUtils.d.ts +0 -70
  88. package/built/typeUtils.js +0 -55
@@ -0,0 +1,137 @@
1
+ interface SetIndicatored {
2
+ indicator: 'warmup' | 'dropset' | 'failure' | 'normal' | any;
3
+ }
4
+
5
+ interface Indexed {
6
+ index: number;
7
+ }
8
+
9
+ /**
10
+ * Accepts any data structure like this:
11
+ * [
12
+ * {indicator: 'warmup', index: 0, ...},
13
+ * {indicator: 'normal', index: 1, ...},
14
+ * {indicator: 'warmup', index: 2, ...},
15
+ * {indicator: 'normal', index: 3, ...},
16
+ * {indicator: 'normal', index: 4, ...},
17
+ * {indicator: 'failure', index: 5, ...},
18
+ * {indicator: 'dropset', index: 6, ...},
19
+ * ]
20
+ *
21
+ *
22
+ * And outputs:
23
+ * ```
24
+ * [
25
+ * {indicator: 'warmup', index: 0, ...},
26
+ * {indicator: 'normal', index: 0, ...},
27
+ * {indicator: 'warmup', index: 1, ...},
28
+ * {indicator: 'normal', index: 2, ...},
29
+ * {indicator: 'normal', index: 3, ...},
30
+ * {indicator: 'failure', index: 4, ...},
31
+ * {indicator: 'dropset', index: 0, ...},
32
+ * ]
33
+ * ```
34
+ */
35
+
36
+ export const indexSetObjectsForUI = <T>(
37
+ indexedSet: SetIndicatored[],
38
+ ): Array<SetIndicatored & Indexed & T> => {
39
+ return indexedSet.reduce(
40
+ (accu: any, currentSet: SetIndicatored) => {
41
+ if (currentSet.indicator === 'warmup') {
42
+ const newSet = { ...currentSet, index: accu.warmupCount };
43
+ accu.warmupCount += 1;
44
+ accu.sets = [...accu.sets, newSet];
45
+ } else if (currentSet.indicator === 'dropset') {
46
+ const newSet = { ...currentSet, index: accu.dropsetCount };
47
+ accu.dropsetCount += 1;
48
+ accu.sets = [...accu.sets, newSet];
49
+ } else {
50
+ const newSet = { ...currentSet, index: accu.normalCount };
51
+ accu.normalCount += 1;
52
+ accu.sets = [...accu.sets, newSet];
53
+ }
54
+
55
+ return accu;
56
+ },
57
+ { warmupCount: 0, dropsetCount: 0, normalCount: 0, sets: [] },
58
+ ).sets;
59
+ };
60
+
61
+ /**
62
+ * This function is almost the exact same as `indexSetObjectsForUI`
63
+ * but this function will change failure sets to normal. Because when
64
+ * processing failure sets, they are often considered normal sets.
65
+ * For example when calculating a previous values, failure sets are
66
+ * considered normal sets.
67
+ *
68
+ * Accepts any data structure like this:
69
+ * [
70
+ * {indicator: 'warmup', index: 0, ...},
71
+ * {indicator: 'normal', index: 1, ...},
72
+ * {indicator: 'warmup', index: 2, ...},
73
+ * {indicator: 'normal', index: 3, ...},
74
+ * {indicator: 'normal', index: 4, ...},
75
+ * {indicator: 'failure', index: 5, ...},
76
+ * {indicator: 'dropset', index: 6, ...},
77
+ * ]
78
+ *
79
+ *
80
+ * And outputs:
81
+ * ```
82
+ * [
83
+ * {indicator: 'warmup', index: 0, ...},
84
+ * {indicator: 'normal', index: 0, ...},
85
+ * {indicator: 'warmup', index: 1, ...},
86
+ * {indicator: 'normal', index: 2, ...},
87
+ * {indicator: 'normal', index: 3, ...},
88
+ * {indicator: 'normal', index: 4, ...}, // Note the failure set was flipped to normal.
89
+ * {indicator: 'dropset', index: 0, ...},
90
+ * ]
91
+ * ```
92
+ */
93
+
94
+ export const indexSetObjectsForType = <T>(
95
+ indexedSet: SetIndicatored[],
96
+ ): Array<SetIndicatored & Indexed & T> => {
97
+ return indexedSet.reduce(
98
+ (accu: any, currentSet: SetIndicatored) => {
99
+ if (currentSet.indicator === 'warmup') {
100
+ const newSet = { ...currentSet, index: accu.warmupCount };
101
+ accu.warmupCount += 1;
102
+ accu.sets = [...accu.sets, newSet];
103
+ } else if (currentSet.indicator === 'dropset') {
104
+ const newSet = { ...currentSet, index: accu.dropsetCount };
105
+ accu.dropsetCount += 1;
106
+ accu.sets = [...accu.sets, newSet];
107
+ } else {
108
+ const newSet = {
109
+ ...currentSet,
110
+ indicator: 'normal',
111
+ index: accu.normalCount,
112
+ };
113
+ accu.normalCount += 1;
114
+ accu.sets = [...accu.sets, newSet];
115
+ }
116
+
117
+ return accu;
118
+ },
119
+ { warmupCount: 0, dropsetCount: 0, normalCount: 0, sets: [] },
120
+ ).sets;
121
+ };
122
+
123
+ export type UserFacingSetIndicator = 'dropset' | 'warmup' | 'failure' | number;
124
+
125
+ export const setObjectToUIFacingSetIndicator = (
126
+ set: SetIndicatored & Indexed,
127
+ ): UserFacingSetIndicator => {
128
+ if (
129
+ set.indicator === 'warmup' ||
130
+ set.indicator === 'dropset' ||
131
+ set.indicator === 'failure'
132
+ ) {
133
+ return set.indicator;
134
+ }
135
+
136
+ return set.index + 1;
137
+ };
@@ -0,0 +1,156 @@
1
+ import {
2
+ forceStringToNumber,
3
+ isValidEmail,
4
+ isValidUsername,
5
+ stringToNumber,
6
+ removeAccents,
7
+ numberWithCommas,
8
+ isValidWebUrl,
9
+ isValidFormattedTime,
10
+ isValidPhoneNumber,
11
+ } from '../utils';
12
+
13
+ describe('utils', () => {
14
+ describe('isValidUsername', () => {
15
+ it('returns true for valid usernames', () => {
16
+ expect(isValidUsername('desmond')).toBe(true);
17
+ expect(isValidUsername('desmond_mc')).toBe(true);
18
+ expect(isValidUsername('desmond_mc123')).toBe(true);
19
+ });
20
+
21
+ it('returns false for invalid usernames', () => {
22
+ expect(isValidUsername('ab')).toBe(false);
23
+ expect(isValidUsername('desmond_')).toBe(false);
24
+ expect(isValidUsername('desmond.mc')).toBe(false);
25
+ expect(isValidUsername('desmond-mc')).toBe(false);
26
+ expect(isValidUsername('.desmondmc')).toBe(false);
27
+ expect(isValidUsername('thisusernameistolongg')).toBe(false);
28
+ });
29
+ });
30
+
31
+ describe('isValidEmail', () => {
32
+ it('returns true for valid email', () => {
33
+ expect(isValidEmail('asd@asd.com')).toBe(true);
34
+ expect(isValidEmail('desmond@gmail.com')).toBe(true);
35
+ expect(isValidEmail('something@somethingelse.asd')).toBe(true);
36
+ });
37
+
38
+ it('returns false for invalid emails', () => {
39
+ expect(isValidEmail('asdasd')).toBe(false);
40
+ expect(isValidEmail('asd@asd')).toBe(false);
41
+ });
42
+ });
43
+
44
+ describe('isValidUrl', () => {
45
+ it('returns true for valid url', () => {
46
+ expect(isValidWebUrl('http://hevyapp.com')).toBe(true);
47
+ expect(isValidWebUrl('hevyapp.com')).toBe(true);
48
+ expect(isValidWebUrl('https://hevyapp.com')).toBe(true);
49
+ expect(isValidWebUrl('hevyapp.com/')).toBe(true);
50
+ expect(isValidWebUrl('hevyapp.com/about-us')).toBe(true);
51
+ expect(isValidWebUrl('www.hevyapp.com')).toBe(true);
52
+ expect(isValidWebUrl('something.xd')).toBe(true);
53
+ expect(isValidWebUrl('my-cool.website')).toBe(true);
54
+ });
55
+ it('returns false for invalid url', () => {
56
+ expect(isValidWebUrl('asdasd')).toBe(false);
57
+ expect(isValidWebUrl('ftp://hevyapp.com')).toBe(false);
58
+ expect(isValidWebUrl('mailto://me@me.com')).toBe(false);
59
+ expect(isValidWebUrl('http://')).toBe(false);
60
+ expect(isValidWebUrl('https://')).toBe(false);
61
+ expect(isValidWebUrl('hevyapp.c')).toBe(false);
62
+ expect(isValidWebUrl('hevyapp.')).toBe(false);
63
+ expect(isValidWebUrl('hevyapp')).toBe(false);
64
+ expect(isValidWebUrl('This is my link')).toBe(false);
65
+ expect(isValidWebUrl('my_impossible.website')).toBe(false);
66
+ });
67
+ });
68
+
69
+ describe('isValidPhoneNumber', () => {
70
+ it('returns true for a valid phone number', () => {
71
+ expect(isValidPhoneNumber('+123456789')).toBe(true);
72
+ expect(isValidPhoneNumber('+123456789012345')).toBe(true);
73
+ });
74
+
75
+ it('returns false for an invalid phone number', () => {
76
+ expect(isValidPhoneNumber('+12345678')).toBe(false);
77
+ expect(isValidPhoneNumber('+123456789.')).toBe(false);
78
+ expect(isValidPhoneNumber('+1 23456789')).toBe(false);
79
+ expect(isValidPhoneNumber('123456789')).toBe(false);
80
+ expect(isValidPhoneNumber('+1234567890123456')).toBe(false);
81
+ expect(isValidPhoneNumber('not a number')).toBe(false);
82
+ expect(isValidPhoneNumber('+')).toBe(false);
83
+ expect(isValidPhoneNumber('')).toBe(false);
84
+ });
85
+ });
86
+
87
+ describe('forceStringToNumber', () => {
88
+ it('Takes any string and outputs a number. No matter what.', () => {
89
+ expect(forceStringToNumber('10')).toBe(10);
90
+ expect(forceStringToNumber('10.1')).toBe(10.1);
91
+ expect(forceStringToNumber('10,1')).toBe(10.1);
92
+ expect(forceStringToNumber('10.1.1.1.1.1')).toBe(0);
93
+ expect(forceStringToNumber('asdasdasdqwe')).toBe(0);
94
+ expect(forceStringToNumber('-10')).toBe(-10);
95
+ expect(forceStringToNumber('-10.1')).toBe(-10.1);
96
+ expect(forceStringToNumber('-10,1')).toBe(-10.1);
97
+ expect(forceStringToNumber('10.10000000000')).toBe(10.1);
98
+ expect(forceStringToNumber(undefined)).toBe(0);
99
+ expect(forceStringToNumber('')).toBe(0);
100
+ });
101
+ });
102
+
103
+ describe('stringToNumber', () => {
104
+ it('Takes any string and outputs a number. No matter what.', () => {
105
+ expect(stringToNumber('10')).toBe(10);
106
+ expect(stringToNumber('10.1')).toBe(10.1);
107
+ expect(stringToNumber('10,1')).toBe(10.1);
108
+ expect(stringToNumber('10.1.1.1.1.1')).toBe(undefined);
109
+ expect(stringToNumber('asdasdasdqwe')).toBe(undefined);
110
+ expect(stringToNumber('-10')).toBe(-10);
111
+ expect(stringToNumber('-10.1')).toBe(-10.1);
112
+ expect(stringToNumber('-10,1')).toBe(-10.1);
113
+ expect(stringToNumber('10.10000000000')).toBe(10.1);
114
+ expect(stringToNumber(undefined)).toBe(undefined);
115
+ expect(stringToNumber('')).toBe(undefined);
116
+ });
117
+ });
118
+
119
+ describe('removeAccents', () => {
120
+ it('Takes an input string and returns a new string with accents removed', () => {
121
+ expect(removeAccents('dog')).toBe('dog');
122
+ expect(removeAccents('Crème Brulée')).toBe('Creme Brulee');
123
+ expect(removeAccents('Möchte')).toBe('Mochte');
124
+ expect(removeAccents('Développé Couché Décliné (Machine Smith)')).toBe(
125
+ 'Developpe Couche Decline (Machine Smith)',
126
+ );
127
+ expect(removeAccents('Français')).toBe('Francais');
128
+ });
129
+ });
130
+
131
+ describe('numberWithCommas', () => {
132
+ it('Takes a number and returns a string with commas in the right place', () => {
133
+ expect(numberWithCommas(10)).toBe('10');
134
+ expect(numberWithCommas(100)).toBe('100');
135
+ expect(numberWithCommas(1000)).toBe('1,000');
136
+ expect(numberWithCommas(10000)).toBe('10,000');
137
+ expect(numberWithCommas(100000)).toBe('100,000');
138
+ expect(numberWithCommas(1000000)).toBe('1,000,000');
139
+ expect(numberWithCommas(10000000)).toBe('10,000,000');
140
+ expect(numberWithCommas(100000000)).toBe('100,000,000');
141
+ });
142
+ });
143
+
144
+ describe('isValidFormattedTime', () => {
145
+ it('Returns true if the passed value is of format: NN:NN or NN:NN:NN', () => {
146
+ expect(isValidFormattedTime('abc')).toBe(false);
147
+ expect(isValidFormattedTime('asdfg')).toBe(false);
148
+ expect(isValidFormattedTime('0:00')).toBe(false);
149
+ expect(isValidFormattedTime('0:00:01')).toBe(false);
150
+
151
+ expect(isValidFormattedTime('99:99')).toBe(true);
152
+ expect(isValidFormattedTime('00:01')).toBe(true);
153
+ expect(isValidFormattedTime('10:00:01')).toBe(true);
154
+ });
155
+ });
156
+ });
@@ -0,0 +1,93 @@
1
+ import { workoutVolumeSetsVolumeKg, WorkoutVolumeSet } from '../workoutVolume';
2
+
3
+ describe('workoutVolume', () => {
4
+ describe('workoutVolumeSetsVolumeKg', () => {
5
+ it('calculated the correct workout volume', () => {
6
+ const weightRepsSet: WorkoutVolumeSet = {
7
+ type: 'weight_reps',
8
+ exerciseTemplateId: 'asd',
9
+ weightKg: 100,
10
+ reps: 10,
11
+ distanceMeters: 0,
12
+ };
13
+
14
+ const pullUpSet: WorkoutVolumeSet = {
15
+ type: 'reps_only',
16
+ exerciseTemplateId: '1B2B1E7C', // Pull Up
17
+ weightKg: 0,
18
+ reps: 10,
19
+ distanceMeters: 0,
20
+ };
21
+
22
+ const assistedPullUpSet: WorkoutVolumeSet = {
23
+ type: 'bodyweight_assisted_reps',
24
+ exerciseTemplateId: '2C37EC5E', // Pull Up (Assisted)
25
+ weightKg: 10,
26
+ reps: 10,
27
+ distanceMeters: 0,
28
+ };
29
+
30
+ const weightedPullUpSet: WorkoutVolumeSet = {
31
+ type: 'bodyweight_reps',
32
+ exerciseTemplateId: '729237D1', // Pull Up (Weighted)
33
+ weightKg: 10,
34
+ reps: 10,
35
+ distanceMeters: 0,
36
+ };
37
+
38
+ const shortDistanceWeight: WorkoutVolumeSet = {
39
+ type: 'short_distance_weight',
40
+ exerciseTemplateId: 'asd',
41
+ weightKg: 100,
42
+ reps: 0,
43
+ distanceMeters: 10,
44
+ };
45
+
46
+ expect(workoutVolumeSetsVolumeKg({ bodyweightKg: 100, sets: [] })).toBe(
47
+ 0,
48
+ );
49
+
50
+ expect(
51
+ workoutVolumeSetsVolumeKg({ bodyweightKg: 100, sets: [weightRepsSet] }),
52
+ ).toBe(1000);
53
+
54
+ expect(
55
+ workoutVolumeSetsVolumeKg({ bodyweightKg: 100, sets: [pullUpSet] }),
56
+ ).toBe(1000);
57
+
58
+ expect(
59
+ workoutVolumeSetsVolumeKg({
60
+ bodyweightKg: 100,
61
+ sets: [assistedPullUpSet],
62
+ }),
63
+ ).toBe(900);
64
+
65
+ expect(
66
+ workoutVolumeSetsVolumeKg({
67
+ bodyweightKg: 100,
68
+ sets: [weightedPullUpSet],
69
+ }),
70
+ ).toBe(1100);
71
+
72
+ expect(
73
+ workoutVolumeSetsVolumeKg({
74
+ bodyweightKg: 100,
75
+ sets: [shortDistanceWeight],
76
+ }),
77
+ ).toBe(1000);
78
+
79
+ expect(
80
+ workoutVolumeSetsVolumeKg({
81
+ bodyweightKg: 100,
82
+ sets: [
83
+ weightRepsSet,
84
+ pullUpSet,
85
+ assistedPullUpSet,
86
+ weightedPullUpSet,
87
+ shortDistanceWeight,
88
+ ],
89
+ }),
90
+ ).toBe(5000);
91
+ });
92
+ });
93
+ });
package/src/units.ts ADDED
@@ -0,0 +1,41 @@
1
+ export const POUNDS_IN_KG = 2.20462;
2
+ export const CM_IN_INCH = 2.54;
3
+ export const METERS_IN_YARD = 0.9144;
4
+ export const KILOMETERS_IN_MILE = 1.60934;
5
+ export const YARDS_IN_A_MILE = 1760;
6
+ export const METERS_IN_MILE = 1609.34;
7
+
8
+ export const roundToTwoDecimal = (value: number) =>
9
+ Math.round(value * 100) / 100;
10
+
11
+ export const roundToOneDecimal = (value: number) => Math.round(value * 10) / 10;
12
+
13
+ export const roundToWholeNumber = (value: number) => Math.round(value);
14
+
15
+ export const exactLbsToKg = (value: number) => {
16
+ return value / POUNDS_IN_KG;
17
+ };
18
+
19
+ export const exactKgtoLbs = (value: number) => {
20
+ return value * POUNDS_IN_KG;
21
+ };
22
+
23
+ export const exactInchtoCm = (value: number) => {
24
+ return value * CM_IN_INCH;
25
+ };
26
+
27
+ export const exactCmtoInch = (value: number) => {
28
+ return value / CM_IN_INCH;
29
+ };
30
+
31
+ export const exactMetersToYards = (value: number) => {
32
+ return value / METERS_IN_YARD;
33
+ };
34
+
35
+ export const exactMilesToMeters = (value: number) => {
36
+ return value * METERS_IN_MILE;
37
+ };
38
+
39
+ export const exactMetersToMiles = (meters: number) => {
40
+ return meters / METERS_IN_MILE;
41
+ };