@garmin/fitsdk 21.168.0 → 21.169.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.
@@ -1,175 +1,175 @@
1
- /////////////////////////////////////////////////////////////////////////////////////////////
2
- // Copyright 2025 Garmin International, Inc.
3
- // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
- // may not use this file except in compliance with the Flexible and Interoperable Data
5
- // Transfer (FIT) Protocol License.
6
- /////////////////////////////////////////////////////////////////////////////////////////////
7
- // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
- // Profile Version = 21.168.0Release
9
- // Tag = production/release/21.168.0-0-gb831b31
10
- /////////////////////////////////////////////////////////////////////////////////////////////
11
-
12
-
13
- import Utils from "./utils.js";
14
-
15
- const mergeHeartRates = (hrMesgs, recordMesgs) => {
16
-
17
- if (hrMesgs == null || recordMesgs == null ||
18
- hrMesgs.length == 0 || recordMesgs.length == 0) {
19
- return;
20
- }
21
-
22
- const heartrates = expandHeartRates(hrMesgs);
23
-
24
- let heartrateIndex = 0;
25
- let recordRangeStartTime = null;
26
-
27
- for (let i = 0; i < recordMesgs.length; ++i) {
28
- const recordMesg = recordMesgs[i];
29
-
30
- let hrSum = 0;
31
- let hrSumCount = 0;
32
-
33
- const recordRangeEndTime = secondsSinceFitEpoch(recordMesg.timestamp);
34
-
35
- if (recordRangeStartTime == null) {
36
- recordRangeStartTime = recordRangeEndTime;
37
- }
38
-
39
- if (recordRangeStartTime === recordRangeEndTime) {
40
- recordRangeStartTime--;
41
- heartrateIndex = (heartrateIndex >= 1) ? heartrateIndex - 1 : 0;
42
- }
43
-
44
- let findingInRangeHrMesgs = true;
45
- while (findingInRangeHrMesgs && (heartrateIndex < heartrates.length)) {
46
-
47
- const heartrate = heartrates[heartrateIndex];
48
-
49
- // Check if the heartrate timestamp is gt record start time
50
- // and if the heartrate timestamp is lte to record end time
51
- if (heartrate.timestamp > recordRangeStartTime
52
- && heartrate.timestamp <= recordRangeEndTime) {
53
- hrSum += heartrate.heartRate;
54
- hrSumCount++;
55
- }
56
- // Check if the heartrate timestamp exceeds the record time
57
- else if (heartrate.timestamp > recordRangeEndTime) {
58
- findingInRangeHrMesgs = false;
59
-
60
- if (hrSumCount > 0) {
61
- // Update record's heart rate value
62
- const avgHR = Math.round(hrSum / hrSumCount);
63
- recordMesg.heartRate = avgHR;
64
-
65
- }
66
- // Reset HR average accumulators
67
- hrSum = 0;
68
- hrSumCount = 0;
69
-
70
- recordRangeStartTime = recordRangeEndTime;
71
-
72
- // Breaks out of findingInRangeHrMesgs while loop w/o incrementing heartrateIndex
73
- break;
74
- }
75
-
76
- heartrateIndex++;
77
- }
78
- }
79
- }
80
-
81
- const expandHeartRates = (hrMesgs) => {
82
- const GAP_INCREMENT_MILLISECONDS = 250;
83
- const GAP_INCREMENT_SECONDS = GAP_INCREMENT_MILLISECONDS / 1000.0;
84
- const GAP_MAX_MILLISECONDS = 5000;
85
- const GAP_MAX_STEPS = GAP_MAX_MILLISECONDS / GAP_INCREMENT_MILLISECONDS;
86
-
87
- if (hrMesgs == null || hrMesgs.length == 0) {
88
- return [];
89
- }
90
-
91
- let anchorEventTimestamp = 0.0;
92
- let anchorTimestamp = null;
93
-
94
- const heartrates = [];
95
- hrMesgs.forEach(hrMesg => {
96
- if (hrMesg == null) {
97
- throwError("HR mesg must not be null");
98
- }
99
-
100
- const eventTimestamps = Array.isArray(hrMesg.eventTimestamp) ? hrMesg.eventTimestamp : [hrMesg.eventTimestamp];
101
- const filteredBpms = Array.isArray(hrMesg.filteredBpm) ? hrMesg.filteredBpm : [hrMesg.filteredBpm];
102
-
103
- // Update HR timestamp anchor, if present
104
- if (hrMesg.timestamp != null) {
105
- anchorTimestamp = secondsSinceFitEpoch(hrMesg.timestamp);
106
-
107
- if (hrMesg.fractionalTimestamp != null) {
108
- anchorTimestamp += hrMesg.fractionalTimestamp;
109
- }
110
-
111
- if (eventTimestamps.length == 1) {
112
- anchorEventTimestamp = eventTimestamps[0];
113
- } else {
114
- throwError("anchor HR mesg must have 1 event_timestamp");
115
- }
116
- }
117
-
118
- if (anchorTimestamp == null || anchorEventTimestamp == null) {
119
- // We cannot process any HR messages if we have not received a timestamp anchor
120
- throwError("no anchor timestamp received in a HR mesg before delta HR mesgs");
121
- } else if (eventTimestamps.length != filteredBpms.length) {
122
- throwError("HR mesg with mismatching event timestamp and filtered bpm");
123
- }
124
-
125
- for (let i = 0; i < eventTimestamps.length; i++) {
126
- let eventTimestamp = eventTimestamps[i];
127
-
128
- // Check to see if the event timestamp rolled over
129
- if (eventTimestamp < anchorEventTimestamp) {
130
- if ((anchorEventTimestamp - eventTimestamp) > (0x400000)) {
131
- eventTimestamp += (0x400000);
132
- } else {
133
- throwError("anchor event_timestamp is greater than subsequent event_timestamp. This does not allow for correct delta calculation.");
134
- }
135
- }
136
-
137
- const currentHr = { timestamp: anchorTimestamp, heartRate: filteredBpms[i] };
138
- currentHr.timestamp += (eventTimestamp - anchorEventTimestamp);
139
-
140
- // Carry the previous HR value forward across the gap to the current
141
- // HR value for up to 5 Seconds (5000ms) in 250ms increments
142
- if (heartrates.length > 0) {
143
- const previousHR = heartrates[heartrates.length - 1];
144
- let gapInMilliseconds = Math.abs(currentHr.timestamp - previousHR.timestamp) * 1000;
145
- let step = 1;
146
- while (gapInMilliseconds > GAP_INCREMENT_MILLISECONDS && step <= GAP_MAX_STEPS) {
147
- const gapHR = { timestamp: previousHR.timestamp, heartRate: previousHR.heartRate };
148
- gapHR.timestamp += (GAP_INCREMENT_SECONDS * step);
149
- heartrates.push(gapHR);
150
-
151
- gapInMilliseconds -= GAP_INCREMENT_MILLISECONDS;
152
- step++;
153
- }
154
- }
155
-
156
- heartrates.push(currentHr);
157
- }
158
- });
159
-
160
- return heartrates;
161
- }
162
-
163
- const secondsSinceFitEpoch = (timestamp) => {
164
- if (timestamp instanceof Date) {
165
- return (timestamp.getTime() - Utils.FIT_EPOCH_MS) / 1000;
166
- }
167
-
168
- return timestamp;
169
- }
170
-
171
- const throwError = (error = "") => {
172
- throw Error(`FIT Runtime Error ${error}`.trimEnd());
173
- }
174
-
1
+ /////////////////////////////////////////////////////////////////////////////////////////////
2
+ // Copyright 2025 Garmin International, Inc.
3
+ // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
+ // may not use this file except in compliance with the Flexible and Interoperable Data
5
+ // Transfer (FIT) Protocol License.
6
+ /////////////////////////////////////////////////////////////////////////////////////////////
7
+ // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
+ // Profile Version = 21.169.0Release
9
+ // Tag = production/release/21.169.0-0-g7105132
10
+ /////////////////////////////////////////////////////////////////////////////////////////////
11
+
12
+
13
+ import Utils from "./utils.js";
14
+
15
+ const mergeHeartRates = (hrMesgs, recordMesgs) => {
16
+
17
+ if (hrMesgs == null || recordMesgs == null ||
18
+ hrMesgs.length == 0 || recordMesgs.length == 0) {
19
+ return;
20
+ }
21
+
22
+ const heartrates = expandHeartRates(hrMesgs);
23
+
24
+ let heartrateIndex = 0;
25
+ let recordRangeStartTime = null;
26
+
27
+ for (let i = 0; i < recordMesgs.length; ++i) {
28
+ const recordMesg = recordMesgs[i];
29
+
30
+ let hrSum = 0;
31
+ let hrSumCount = 0;
32
+
33
+ const recordRangeEndTime = secondsSinceFitEpoch(recordMesg.timestamp);
34
+
35
+ if (recordRangeStartTime == null) {
36
+ recordRangeStartTime = recordRangeEndTime;
37
+ }
38
+
39
+ if (recordRangeStartTime === recordRangeEndTime) {
40
+ recordRangeStartTime--;
41
+ heartrateIndex = (heartrateIndex >= 1) ? heartrateIndex - 1 : 0;
42
+ }
43
+
44
+ let findingInRangeHrMesgs = true;
45
+ while (findingInRangeHrMesgs && (heartrateIndex < heartrates.length)) {
46
+
47
+ const heartrate = heartrates[heartrateIndex];
48
+
49
+ // Check if the heartrate timestamp is gt record start time
50
+ // and if the heartrate timestamp is lte to record end time
51
+ if (heartrate.timestamp > recordRangeStartTime
52
+ && heartrate.timestamp <= recordRangeEndTime) {
53
+ hrSum += heartrate.heartRate;
54
+ hrSumCount++;
55
+ }
56
+ // Check if the heartrate timestamp exceeds the record time
57
+ else if (heartrate.timestamp > recordRangeEndTime) {
58
+ findingInRangeHrMesgs = false;
59
+
60
+ if (hrSumCount > 0) {
61
+ // Update record's heart rate value
62
+ const avgHR = Math.round(hrSum / hrSumCount);
63
+ recordMesg.heartRate = avgHR;
64
+
65
+ }
66
+ // Reset HR average accumulators
67
+ hrSum = 0;
68
+ hrSumCount = 0;
69
+
70
+ recordRangeStartTime = recordRangeEndTime;
71
+
72
+ // Breaks out of findingInRangeHrMesgs while loop w/o incrementing heartrateIndex
73
+ break;
74
+ }
75
+
76
+ heartrateIndex++;
77
+ }
78
+ }
79
+ }
80
+
81
+ const expandHeartRates = (hrMesgs) => {
82
+ const GAP_INCREMENT_MILLISECONDS = 250;
83
+ const GAP_INCREMENT_SECONDS = GAP_INCREMENT_MILLISECONDS / 1000.0;
84
+ const GAP_MAX_MILLISECONDS = 5000;
85
+ const GAP_MAX_STEPS = GAP_MAX_MILLISECONDS / GAP_INCREMENT_MILLISECONDS;
86
+
87
+ if (hrMesgs == null || hrMesgs.length == 0) {
88
+ return [];
89
+ }
90
+
91
+ let anchorEventTimestamp = 0.0;
92
+ let anchorTimestamp = null;
93
+
94
+ const heartrates = [];
95
+ hrMesgs.forEach(hrMesg => {
96
+ if (hrMesg == null) {
97
+ throwError("HR mesg must not be null");
98
+ }
99
+
100
+ const eventTimestamps = Array.isArray(hrMesg.eventTimestamp) ? hrMesg.eventTimestamp : [hrMesg.eventTimestamp];
101
+ const filteredBpms = Array.isArray(hrMesg.filteredBpm) ? hrMesg.filteredBpm : [hrMesg.filteredBpm];
102
+
103
+ // Update HR timestamp anchor, if present
104
+ if (hrMesg.timestamp != null) {
105
+ anchorTimestamp = secondsSinceFitEpoch(hrMesg.timestamp);
106
+
107
+ if (hrMesg.fractionalTimestamp != null) {
108
+ anchorTimestamp += hrMesg.fractionalTimestamp;
109
+ }
110
+
111
+ if (eventTimestamps.length == 1) {
112
+ anchorEventTimestamp = eventTimestamps[0];
113
+ } else {
114
+ throwError("anchor HR mesg must have 1 event_timestamp");
115
+ }
116
+ }
117
+
118
+ if (anchorTimestamp == null || anchorEventTimestamp == null) {
119
+ // We cannot process any HR messages if we have not received a timestamp anchor
120
+ throwError("no anchor timestamp received in a HR mesg before delta HR mesgs");
121
+ } else if (eventTimestamps.length != filteredBpms.length) {
122
+ throwError("HR mesg with mismatching event timestamp and filtered bpm");
123
+ }
124
+
125
+ for (let i = 0; i < eventTimestamps.length; i++) {
126
+ let eventTimestamp = eventTimestamps[i];
127
+
128
+ // Check to see if the event timestamp rolled over
129
+ if (eventTimestamp < anchorEventTimestamp) {
130
+ if ((anchorEventTimestamp - eventTimestamp) > (0x400000)) {
131
+ eventTimestamp += (0x400000);
132
+ } else {
133
+ throwError("anchor event_timestamp is greater than subsequent event_timestamp. This does not allow for correct delta calculation.");
134
+ }
135
+ }
136
+
137
+ const currentHr = { timestamp: anchorTimestamp, heartRate: filteredBpms[i] };
138
+ currentHr.timestamp += (eventTimestamp - anchorEventTimestamp);
139
+
140
+ // Carry the previous HR value forward across the gap to the current
141
+ // HR value for up to 5 Seconds (5000ms) in 250ms increments
142
+ if (heartrates.length > 0) {
143
+ const previousHR = heartrates[heartrates.length - 1];
144
+ let gapInMilliseconds = Math.abs(currentHr.timestamp - previousHR.timestamp) * 1000;
145
+ let step = 1;
146
+ while (gapInMilliseconds > GAP_INCREMENT_MILLISECONDS && step <= GAP_MAX_STEPS) {
147
+ const gapHR = { timestamp: previousHR.timestamp, heartRate: previousHR.heartRate };
148
+ gapHR.timestamp += (GAP_INCREMENT_SECONDS * step);
149
+ heartrates.push(gapHR);
150
+
151
+ gapInMilliseconds -= GAP_INCREMENT_MILLISECONDS;
152
+ step++;
153
+ }
154
+ }
155
+
156
+ heartrates.push(currentHr);
157
+ }
158
+ });
159
+
160
+ return heartrates;
161
+ }
162
+
163
+ const secondsSinceFitEpoch = (timestamp) => {
164
+ if (timestamp instanceof Date) {
165
+ return (timestamp.getTime() - Utils.FIT_EPOCH_MS) / 1000;
166
+ }
167
+
168
+ return timestamp;
169
+ }
170
+
171
+ const throwError = (error = "") => {
172
+ throw Error(`FIT Runtime Error ${error}`.trimEnd());
173
+ }
174
+
175
175
  export default { mergeHeartRates, expandHeartRates };
@@ -1,53 +1,53 @@
1
- /////////////////////////////////////////////////////////////////////////////////////////////
2
- // Copyright 2025 Garmin International, Inc.
3
- // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
- // may not use this file except in compliance with the Flexible and Interoperable Data
5
- // Transfer (FIT) Protocol License.
6
- /////////////////////////////////////////////////////////////////////////////////////////////
7
- // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
- // Profile Version = 21.168.0Release
9
- // Tag = production/release/21.168.0-0-gb831b31
10
- /////////////////////////////////////////////////////////////////////////////////////////////
11
-
12
-
13
- const sanitizeValues = (values) => {
14
- if (onlyNullValues(values)) {
15
- return null;
16
- }
17
-
18
- return values.length === 1 ? values[0] : values;
19
- }
20
-
21
- const trimStringTrailingNulls = (string) => {
22
- if (string == null) {
23
- return;
24
- }
25
-
26
- let strings = string.split("\u0000")
27
-
28
- while (strings[strings.length - 1] === "") {
29
- strings.pop();
30
- if (strings.length === 0) {
31
- return "";
32
- }
33
- }
34
-
35
- return strings.length === 1 ? strings[0] : strings;
36
- }
37
-
38
- const onlyNullValues = (values) => values.reduce((state, value) => value != null ? false : state, true);
39
-
40
- const onlyInvalidValues = (rawFieldValue, invalidValue) => {
41
- if (Array.isArray(rawFieldValue)) {
42
- return rawFieldValue.reduce((state, value) => value != invalidValue ? false : state, true);
43
- }
44
-
45
- return rawFieldValue === invalidValue;
46
- }
47
-
48
- export default {
49
- sanitizeValues,
50
- trimStringTrailingNulls,
51
- onlyNullValues,
52
- onlyInvalidValues
53
- };
1
+ /////////////////////////////////////////////////////////////////////////////////////////////
2
+ // Copyright 2025 Garmin International, Inc.
3
+ // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
+ // may not use this file except in compliance with the Flexible and Interoperable Data
5
+ // Transfer (FIT) Protocol License.
6
+ /////////////////////////////////////////////////////////////////////////////////////////////
7
+ // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
+ // Profile Version = 21.169.0Release
9
+ // Tag = production/release/21.169.0-0-g7105132
10
+ /////////////////////////////////////////////////////////////////////////////////////////////
11
+
12
+
13
+ const sanitizeValues = (values) => {
14
+ if (onlyNullValues(values)) {
15
+ return null;
16
+ }
17
+
18
+ return values.length === 1 ? values[0] : values;
19
+ }
20
+
21
+ const trimStringTrailingNulls = (string) => {
22
+ if (string == null) {
23
+ return;
24
+ }
25
+
26
+ let strings = string.split("\u0000")
27
+
28
+ while (strings[strings.length - 1] === "") {
29
+ strings.pop();
30
+ if (strings.length === 0) {
31
+ return "";
32
+ }
33
+ }
34
+
35
+ return strings.length === 1 ? strings[0] : strings;
36
+ }
37
+
38
+ const onlyNullValues = (values) => values.reduce((state, value) => value != null ? false : state, true);
39
+
40
+ const onlyInvalidValues = (rawFieldValue, invalidValue) => {
41
+ if (Array.isArray(rawFieldValue)) {
42
+ return rawFieldValue.reduce((state, value) => value != invalidValue ? false : state, true);
43
+ }
44
+
45
+ return rawFieldValue === invalidValue;
46
+ }
47
+
48
+ export default {
49
+ sanitizeValues,
50
+ trimStringTrailingNulls,
51
+ onlyNullValues,
52
+ onlyInvalidValues
53
+ };
@@ -1,65 +1,65 @@
1
- /////////////////////////////////////////////////////////////////////////////////////////////
2
- // Copyright 2025 Garmin International, Inc.
3
- // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
- // may not use this file except in compliance with the Flexible and Interoperable Data
5
- // Transfer (FIT) Protocol License.
6
- /////////////////////////////////////////////////////////////////////////////////////////////
7
- // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
- // Profile Version = 21.168.0Release
9
- // Tag = production/release/21.168.0-0-gb831b31
10
- /////////////////////////////////////////////////////////////////////////////////////////////
11
-
12
- import Profile from "./profile.js";
13
-
14
- const decodeMemoGlobs = (messages) => {
15
- if ((messages?.memoGlobMesgs?.length ?? 0) == 0) {
16
- return;
17
- }
18
-
19
- const memoGlobMesgs = messages.memoGlobMesgs;
20
-
21
- // Group memoGlob mesgs by mesgNum, parentIndex, and fieldNum
22
- const groupedMemoGlobs = Object.groupBy(memoGlobMesgs, ({ mesgNum, parentIndex, fieldNum }) => {
23
- return JSON.stringify({
24
- mesgNum,
25
- parentIndex,
26
- fieldNum
27
- });
28
- });
29
-
30
- Object.entries(groupedMemoGlobs).forEach(([key, memoGlobMesgs]) => {
31
- const { mesgNum, parentIndex, fieldNum } = JSON.parse(key);
32
-
33
- // Sort grouped memoGlob messages by part index
34
- memoGlobMesgs.sort((a, b) => a.partIndex - b.partIndex);
35
-
36
- const mesgProfile = Object.values(Profile.messages).find((mesgDefinition) => {
37
- return mesgDefinition.name == mesgNum || mesgDefinition.num == mesgNum
38
- });
39
-
40
- const targetMesg = messages[mesgProfile?.messagesKey ?? mesgNum]?.[parentIndex];
41
- if (targetMesg == null) {
42
- return;
43
- }
44
-
45
- const targetFieldKey = mesgProfile?.fields?.[fieldNum]?.name ?? fieldNum;
46
-
47
- const memoGlobBytes = memoGlobMesgs.reduce((accumluatedBytes, mesg) => {
48
- return accumluatedBytes.concat(mesg.data);
49
- }, []);
50
-
51
- targetMesg[targetFieldKey] = decodeMemoGlobBytesToUtf8(memoGlobBytes);
52
- });
53
- }
54
-
55
- const decodeMemoGlobBytesToUtf8 = (memoGlobBytes) => {
56
- let decoder = new TextDecoder('utf-8');
57
- let bytes = new Uint8Array(memoGlobBytes);
58
-
59
- return decoder.decode(bytes);
60
- }
61
-
62
- export default {
63
- decodeMemoGlobs,
64
- decodeMemoGlobBytesToUtf8
1
+ /////////////////////////////////////////////////////////////////////////////////////////////
2
+ // Copyright 2025 Garmin International, Inc.
3
+ // Licensed under the Flexible and Interoperable Data Transfer (FIT) Protocol License; you
4
+ // may not use this file except in compliance with the Flexible and Interoperable Data
5
+ // Transfer (FIT) Protocol License.
6
+ /////////////////////////////////////////////////////////////////////////////////////////////
7
+ // ****WARNING**** This file is auto-generated! Do NOT edit this file.
8
+ // Profile Version = 21.169.0Release
9
+ // Tag = production/release/21.169.0-0-g7105132
10
+ /////////////////////////////////////////////////////////////////////////////////////////////
11
+
12
+ import Profile from "./profile.js";
13
+
14
+ const decodeMemoGlobs = (messages) => {
15
+ if ((messages?.memoGlobMesgs?.length ?? 0) == 0) {
16
+ return;
17
+ }
18
+
19
+ const memoGlobMesgs = messages.memoGlobMesgs;
20
+
21
+ // Group memoGlob mesgs by mesgNum, parentIndex, and fieldNum
22
+ const groupedMemoGlobs = Object.groupBy(memoGlobMesgs, ({ mesgNum, parentIndex, fieldNum }) => {
23
+ return JSON.stringify({
24
+ mesgNum,
25
+ parentIndex,
26
+ fieldNum
27
+ });
28
+ });
29
+
30
+ Object.entries(groupedMemoGlobs).forEach(([key, memoGlobMesgs]) => {
31
+ const { mesgNum, parentIndex, fieldNum } = JSON.parse(key);
32
+
33
+ // Sort grouped memoGlob messages by part index
34
+ memoGlobMesgs.sort((a, b) => a.partIndex - b.partIndex);
35
+
36
+ const mesgProfile = Object.values(Profile.messages).find((mesgDefinition) => {
37
+ return mesgDefinition.name == mesgNum || mesgDefinition.num == mesgNum
38
+ });
39
+
40
+ const targetMesg = messages[mesgProfile?.messagesKey ?? mesgNum]?.[parentIndex];
41
+ if (targetMesg == null) {
42
+ return;
43
+ }
44
+
45
+ const targetFieldKey = mesgProfile?.fields?.[fieldNum]?.name ?? fieldNum;
46
+
47
+ const memoGlobBytes = memoGlobMesgs.reduce((accumluatedBytes, mesg) => {
48
+ return accumluatedBytes.concat(mesg.data);
49
+ }, []);
50
+
51
+ targetMesg[targetFieldKey] = decodeMemoGlobBytesToUtf8(memoGlobBytes);
52
+ });
53
+ }
54
+
55
+ const decodeMemoGlobBytesToUtf8 = (memoGlobBytes) => {
56
+ let decoder = new TextDecoder('utf-8');
57
+ let bytes = new Uint8Array(memoGlobBytes);
58
+
59
+ return decoder.decode(bytes);
60
+ }
61
+
62
+ export default {
63
+ decodeMemoGlobs,
64
+ decodeMemoGlobBytesToUtf8
65
65
  }