thryve-mcp-server 0.0.1

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 (197) hide show
  1. package/Overview.csv +351 -0
  2. package/README.md +229 -0
  3. package/dist/category-mappings.d.ts +34 -0
  4. package/dist/category-mappings.d.ts.map +1 -0
  5. package/dist/category-mappings.js +146 -0
  6. package/dist/category-mappings.js.map +1 -0
  7. package/dist/data-enrichment.d.ts +19 -0
  8. package/dist/data-enrichment.d.ts.map +1 -0
  9. package/dist/data-enrichment.js +105 -0
  10. package/dist/data-enrichment.js.map +1 -0
  11. package/dist/data-types-generated.d.ts +70 -0
  12. package/dist/data-types-generated.d.ts.map +1 -0
  13. package/dist/data-types-generated.js +2597 -0
  14. package/dist/data-types-generated.js.map +1 -0
  15. package/dist/data-types.d.ts +53 -0
  16. package/dist/data-types.d.ts.map +1 -0
  17. package/dist/data-types.js +138 -0
  18. package/dist/data-types.js.map +1 -0
  19. package/dist/debug-logger.d.ts +17 -0
  20. package/dist/debug-logger.d.ts.map +1 -0
  21. package/dist/debug-logger.js +35 -0
  22. package/dist/debug-logger.js.map +1 -0
  23. package/dist/debug-middleware.d.ts +12 -0
  24. package/dist/debug-middleware.d.ts.map +1 -0
  25. package/dist/debug-middleware.js +36 -0
  26. package/dist/debug-middleware.js.map +1 -0
  27. package/dist/helpers.d.ts +19 -0
  28. package/dist/helpers.d.ts.map +1 -0
  29. package/dist/helpers.js +166 -0
  30. package/dist/helpers.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +754 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/schemas.d.ts +557 -0
  36. package/dist/schemas.d.ts.map +1 -0
  37. package/dist/schemas.js +338 -0
  38. package/dist/schemas.js.map +1 -0
  39. package/dist/tool-loader.d.ts +34 -0
  40. package/dist/tool-loader.d.ts.map +1 -0
  41. package/dist/tool-loader.js +108 -0
  42. package/dist/tool-loader.js.map +1 -0
  43. package/dist/tool-registry.d.ts +44 -0
  44. package/dist/tool-registry.d.ts.map +1 -0
  45. package/dist/tool-registry.js +54 -0
  46. package/dist/tool-registry.js.map +1 -0
  47. package/dist/tools/baseCategoryHandler.d.ts +28 -0
  48. package/dist/tools/baseCategoryHandler.d.ts.map +1 -0
  49. package/dist/tools/baseCategoryHandler.js +184 -0
  50. package/dist/tools/baseCategoryHandler.js.map +1 -0
  51. package/dist/tools/categories/getActivity.d.ts +10 -0
  52. package/dist/tools/categories/getActivity.d.ts.map +1 -0
  53. package/dist/tools/categories/getActivity.js +5 -0
  54. package/dist/tools/categories/getActivity.js.map +1 -0
  55. package/dist/tools/categories/getAudioAndHearing.d.ts +10 -0
  56. package/dist/tools/categories/getAudioAndHearing.d.ts.map +1 -0
  57. package/dist/tools/categories/getAudioAndHearing.js +5 -0
  58. package/dist/tools/categories/getAudioAndHearing.js.map +1 -0
  59. package/dist/tools/categories/getBloodGlucose.d.ts +10 -0
  60. package/dist/tools/categories/getBloodGlucose.d.ts.map +1 -0
  61. package/dist/tools/categories/getBloodGlucose.js +5 -0
  62. package/dist/tools/categories/getBloodGlucose.js.map +1 -0
  63. package/dist/tools/categories/getBodyComposition.d.ts +10 -0
  64. package/dist/tools/categories/getBodyComposition.d.ts.map +1 -0
  65. package/dist/tools/categories/getBodyComposition.js +5 -0
  66. package/dist/tools/categories/getBodyComposition.js.map +1 -0
  67. package/dist/tools/categories/getCardiovascular.d.ts +10 -0
  68. package/dist/tools/categories/getCardiovascular.d.ts.map +1 -0
  69. package/dist/tools/categories/getCardiovascular.js +5 -0
  70. package/dist/tools/categories/getCardiovascular.js.map +1 -0
  71. package/dist/tools/categories/getHeartRate.d.ts +10 -0
  72. package/dist/tools/categories/getHeartRate.d.ts.map +1 -0
  73. package/dist/tools/categories/getHeartRate.js +5 -0
  74. package/dist/tools/categories/getHeartRate.js.map +1 -0
  75. package/dist/tools/categories/getLocation.d.ts +10 -0
  76. package/dist/tools/categories/getLocation.d.ts.map +1 -0
  77. package/dist/tools/categories/getLocation.js +5 -0
  78. package/dist/tools/categories/getLocation.js.map +1 -0
  79. package/dist/tools/categories/getMicrobiome.d.ts +10 -0
  80. package/dist/tools/categories/getMicrobiome.d.ts.map +1 -0
  81. package/dist/tools/categories/getMicrobiome.js +5 -0
  82. package/dist/tools/categories/getMicrobiome.js.map +1 -0
  83. package/dist/tools/categories/getMovementAnalysis.d.ts +10 -0
  84. package/dist/tools/categories/getMovementAnalysis.d.ts.map +1 -0
  85. package/dist/tools/categories/getMovementAnalysis.js +5 -0
  86. package/dist/tools/categories/getMovementAnalysis.js.map +1 -0
  87. package/dist/tools/categories/getNutrition.d.ts +10 -0
  88. package/dist/tools/categories/getNutrition.d.ts.map +1 -0
  89. package/dist/tools/categories/getNutrition.js +5 -0
  90. package/dist/tools/categories/getNutrition.js.map +1 -0
  91. package/dist/tools/categories/getRespiratory.d.ts +10 -0
  92. package/dist/tools/categories/getRespiratory.d.ts.map +1 -0
  93. package/dist/tools/categories/getRespiratory.js +5 -0
  94. package/dist/tools/categories/getRespiratory.js.map +1 -0
  95. package/dist/tools/categories/getSleep.d.ts +10 -0
  96. package/dist/tools/categories/getSleep.d.ts.map +1 -0
  97. package/dist/tools/categories/getSleep.js +5 -0
  98. package/dist/tools/categories/getSleep.js.map +1 -0
  99. package/dist/tools/categories/getStressAndHRV.d.ts +10 -0
  100. package/dist/tools/categories/getStressAndHRV.d.ts.map +1 -0
  101. package/dist/tools/categories/getStressAndHRV.js +5 -0
  102. package/dist/tools/categories/getStressAndHRV.js.map +1 -0
  103. package/dist/tools/categories/getWellness.d.ts +10 -0
  104. package/dist/tools/categories/getWellness.d.ts.map +1 -0
  105. package/dist/tools/categories/getWellness.js +5 -0
  106. package/dist/tools/categories/getWellness.js.map +1 -0
  107. package/dist/tools/categories/getWomensHealth.d.ts +10 -0
  108. package/dist/tools/categories/getWomensHealth.d.ts.map +1 -0
  109. package/dist/tools/categories/getWomensHealth.js +5 -0
  110. package/dist/tools/categories/getWomensHealth.js.map +1 -0
  111. package/dist/tools/categories/getWorkouts.d.ts +10 -0
  112. package/dist/tools/categories/getWorkouts.d.ts.map +1 -0
  113. package/dist/tools/categories/getWorkouts.js +5 -0
  114. package/dist/tools/categories/getWorkouts.js.map +1 -0
  115. package/dist/tools/categoryTools.d.ts +100 -0
  116. package/dist/tools/categoryTools.d.ts.map +1 -0
  117. package/dist/tools/categoryTools.js +66 -0
  118. package/dist/tools/categoryTools.js.map +1 -0
  119. package/dist/tools/get.d.ts +39 -0
  120. package/dist/tools/get.d.ts.map +1 -0
  121. package/dist/tools/get.js +118 -0
  122. package/dist/tools/get.js.map +1 -0
  123. package/dist/tools/getAvailableDataTypes.d.ts +101 -0
  124. package/dist/tools/getAvailableDataTypes.d.ts.map +1 -0
  125. package/dist/tools/getAvailableDataTypes.js +120 -0
  126. package/dist/tools/getAvailableDataTypes.js.map +1 -0
  127. package/dist/tools/getConnectionWidgetUrl.d.ts +12 -0
  128. package/dist/tools/getConnectionWidgetUrl.d.ts.map +1 -0
  129. package/dist/tools/getConnectionWidgetUrl.js +26 -0
  130. package/dist/tools/getConnectionWidgetUrl.js.map +1 -0
  131. package/dist/tools/getDailyData.d.ts +30 -0
  132. package/dist/tools/getDailyData.d.ts.map +1 -0
  133. package/dist/tools/getDailyData.js +114 -0
  134. package/dist/tools/getDailyData.js.map +1 -0
  135. package/dist/tools/getEpochData.d.ts +30 -0
  136. package/dist/tools/getEpochData.d.ts.map +1 -0
  137. package/dist/tools/getEpochData.js +111 -0
  138. package/dist/tools/getEpochData.js.map +1 -0
  139. package/dist/tools/getHealthDataByCategory.d.ts +38 -0
  140. package/dist/tools/getHealthDataByCategory.d.ts.map +1 -0
  141. package/dist/tools/getHealthDataByCategory.js +231 -0
  142. package/dist/tools/getHealthDataByCategory.js.map +1 -0
  143. package/dist/tools/getUserInformation.d.ts +8 -0
  144. package/dist/tools/getUserInformation.d.ts.map +1 -0
  145. package/dist/tools/getUserInformation.js +24 -0
  146. package/dist/tools/getUserInformation.js.map +1 -0
  147. package/dist/tools/index.d.ts +7 -0
  148. package/dist/tools/index.d.ts.map +1 -0
  149. package/dist/tools/index.js +12 -0
  150. package/dist/tools/index.js.map +1 -0
  151. package/dist/tools/legacy.d.ts +37 -0
  152. package/dist/tools/legacy.d.ts.map +1 -0
  153. package/dist/tools/legacy.js +105 -0
  154. package/dist/tools/legacy.js.map +1 -0
  155. package/dist/tools/listConnectedSources.d.ts +11 -0
  156. package/dist/tools/listConnectedSources.d.ts.map +1 -0
  157. package/dist/tools/listConnectedSources.js +29 -0
  158. package/dist/tools/listConnectedSources.js.map +1 -0
  159. package/dist/tools/listDevices.d.ts +11 -0
  160. package/dist/tools/listDevices.d.ts.map +1 -0
  161. package/dist/tools/listDevices.js +29 -0
  162. package/dist/tools/listDevices.js.map +1 -0
  163. package/dist/tools/search-filters.d.ts +42 -0
  164. package/dist/tools/search-filters.d.ts.map +1 -0
  165. package/dist/tools/search-filters.js +210 -0
  166. package/dist/tools/search-filters.js.map +1 -0
  167. package/dist/tools/search-keyword.d.ts +32 -0
  168. package/dist/tools/search-keyword.d.ts.map +1 -0
  169. package/dist/tools/search-keyword.js +122 -0
  170. package/dist/tools/search-keyword.js.map +1 -0
  171. package/dist/tools/search-tools.d.ts +8 -0
  172. package/dist/tools/search-tools.d.ts.map +1 -0
  173. package/dist/tools/search-tools.js +273 -0
  174. package/dist/tools/search-tools.js.map +1 -0
  175. package/dist/types.d.ts +37 -0
  176. package/dist/types.d.ts.map +1 -0
  177. package/dist/types.js +2 -0
  178. package/dist/types.js.map +1 -0
  179. package/package.json +30 -0
  180. package/parse-csv.py +152 -0
  181. package/src/category-mappings.ts +181 -0
  182. package/src/data-enrichment.ts +125 -0
  183. package/src/data-types-generated.ts +2652 -0
  184. package/src/helpers.ts +198 -0
  185. package/src/index.ts +859 -0
  186. package/src/schemas.ts +372 -0
  187. package/src/tools/baseCategoryHandler.ts +243 -0
  188. package/src/tools/categoryTools.ts +101 -0
  189. package/src/tools/get.ts +147 -0
  190. package/src/tools/getAvailableDataTypes.ts +148 -0
  191. package/src/tools/index.ts +32 -0
  192. package/src/tools/listConnectedSources.ts +45 -0
  193. package/src/tools/listDevices.ts +45 -0
  194. package/src/tools/search-filters.ts +253 -0
  195. package/src/tools/search-keyword.ts +162 -0
  196. package/src/types.ts +44 -0
  197. package/tsconfig.json +20 -0
@@ -0,0 +1,253 @@
1
+ import type { Request } from "express";
2
+ import type { z } from "zod";
3
+ import type { SearchFiltersSchema } from "../schemas.js";
4
+ import type { HealthDataRecord } from "../types.js";
5
+ import {
6
+ extractThryveHeaders,
7
+ buildUrlEncodedBody,
8
+ getDefaultDateRange,
9
+ getDefaultTimestampRange,
10
+ THRYVE_API_BASE_URL,
11
+ sortHealthData,
12
+ paginateResults,
13
+ } from "../helpers.js";
14
+ import { getDataTypeIdsForCategories, CATEGORY_MAPPINGS, type HealthCategory } from "../category-mappings.js";
15
+ import { flattenThryveResponse, enrichWithDataTypes } from "../data-enrichment.js";
16
+
17
+ export async function searchFilters(req: Request, args: z.infer<typeof SearchFiltersSchema>) {
18
+ const { headers, authToken } = extractThryveHeaders(req);
19
+
20
+ // Determine date/timestamp ranges
21
+ let startDay: string;
22
+ let endDay: string;
23
+ let startTimestamp: string;
24
+ let endTimestamp: string;
25
+
26
+ // Prefer explicit timestamps if provided, otherwise use dates
27
+ if (args.startTimestamp || args.endTimestamp) {
28
+ const defaults = getDefaultTimestampRange();
29
+ startTimestamp = args.startTimestamp || defaults.startTimestamp;
30
+ endTimestamp = args.endTimestamp || defaults.endTimestamp;
31
+ // Extract dates from timestamps
32
+ startDay = startTimestamp.split('T')[0];
33
+ endDay = endTimestamp.split('T')[0];
34
+ } else if (args.startDate || args.endDate) {
35
+ const defaults = getDefaultDateRange();
36
+ startDay = args.startDate || defaults.startDay;
37
+ endDay = args.endDate || defaults.endDay;
38
+ startTimestamp = `${startDay}T00:00:00Z`;
39
+ endTimestamp = `${endDay}T23:59:59Z`;
40
+ } else {
41
+ const defaults = getDefaultDateRange();
42
+ startDay = defaults.startDay;
43
+ endDay = defaults.endDay;
44
+ startTimestamp = `${startDay}T00:00:00Z`;
45
+ endTimestamp = `${endDay}T23:59:59Z`;
46
+ }
47
+
48
+ // Convert dataSources array to comma-separated string
49
+ const dataSourcesParam = args.dataSources?.join(',');
50
+
51
+ // Determine resolution (default: "both")
52
+ const resolution = args.resolution || "both";
53
+
54
+ // Fetch data based on resolution
55
+ const fetchPromises: Promise<Response>[] = [];
56
+
57
+ if (resolution === "daily" || resolution === "both") {
58
+ const dailyBody = buildUrlEncodedBody({
59
+ authenticationToken: authToken,
60
+ startDay,
61
+ endDay,
62
+ dataSources: dataSourcesParam,
63
+ displayTypeName: "true",
64
+ });
65
+
66
+ fetchPromises.push(
67
+ fetch(`${THRYVE_API_BASE_URL}/v5/dailyDynamicValues`, {
68
+ method: "POST",
69
+ headers,
70
+ body: dailyBody,
71
+ })
72
+ );
73
+ }
74
+
75
+ if (resolution === "epoch" || resolution === "both") {
76
+ const epochBody = buildUrlEncodedBody({
77
+ authenticationToken: authToken,
78
+ startTimestamp,
79
+ endTimestamp,
80
+ dataSources: dataSourcesParam,
81
+ displayTypeName: "true",
82
+ });
83
+
84
+ fetchPromises.push(
85
+ fetch(`${THRYVE_API_BASE_URL}/v5/dynamicEpochValues`, {
86
+ method: "POST",
87
+ headers,
88
+ body: epochBody,
89
+ })
90
+ );
91
+ }
92
+
93
+ const responses = await Promise.all(fetchPromises);
94
+
95
+ // Check for errors
96
+ for (const response of responses) {
97
+ if (!response.ok) {
98
+ throw new Error(`Thryve API error: ${response.status} ${response.statusText}`);
99
+ }
100
+ }
101
+
102
+ // Parse responses
103
+ const jsonData = await Promise.all(responses.map(r => r.json()));
104
+
105
+ // Flatten and enrich data
106
+ let allData: HealthDataRecord[] = [];
107
+ for (const data of jsonData) {
108
+ const flattened = flattenThryveResponse(data);
109
+ enrichWithDataTypes(flattened);
110
+ allData = allData.concat(flattened);
111
+ }
112
+
113
+ // Apply filters
114
+ let filteredData = allData;
115
+
116
+ // Filter by categories (convert to data type IDs)
117
+ if (args.categories && args.categories.length > 0) {
118
+ const categoryDataTypeIds = getDataTypeIdsForCategories(args.categories as HealthCategory[]);
119
+ const dataTypeIdSet = new Set(categoryDataTypeIds);
120
+
121
+ filteredData = filteredData.filter((record) => {
122
+ const typeId = record.dataType || record.dailyDynamicValueType || record.dynamicEpochValueType;
123
+ return typeId && dataTypeIdSet.has(typeId);
124
+ });
125
+ }
126
+
127
+ // Filter by specific data type IDs
128
+ if (args.dataTypes && args.dataTypes.length > 0) {
129
+ const dataTypeIdSet = new Set(args.dataTypes);
130
+ filteredData = filteredData.filter((record) => {
131
+ const typeId = record.dataType || record.dailyDynamicValueType || record.dynamicEpochValueType;
132
+ return typeId !== undefined && dataTypeIdSet.has(typeId);
133
+ });
134
+ }
135
+
136
+ // Filter by value range
137
+ if (args.valueMin !== undefined || args.valueMax !== undefined) {
138
+ filteredData = filteredData.filter((record) => {
139
+ const value = typeof record.value === "string" ? parseFloat(record.value) : record.value;
140
+ if (isNaN(value)) return false;
141
+
142
+ if (args.valueMin !== undefined && value < args.valueMin) return false;
143
+ if (args.valueMax !== undefined && value > args.valueMax) return false;
144
+
145
+ return true;
146
+ });
147
+ }
148
+
149
+ // Apply limit if specified (before pagination/grouping)
150
+ if (args.limit) {
151
+ filteredData = filteredData.slice(0, args.limit);
152
+ }
153
+
154
+ // Check if we should group by category
155
+ if (args.groupByCategory && args.categories && args.categories.length > 0) {
156
+ // Group results by category (like getDailyData/getEpochData)
157
+ const groupedResults = groupByCategory(filteredData, args.categories as HealthCategory[]);
158
+
159
+ // Build metadata
160
+ const dataTypesFound = new Set<string>();
161
+ filteredData.forEach((record) => {
162
+ const typeName = record.dailyDynamicValueTypeName || record.dynamicEpochValueTypeName;
163
+ if (typeName) dataTypesFound.add(typeName);
164
+ });
165
+
166
+ const metadata = {
167
+ resolution,
168
+ categories: args.categories,
169
+ timeRange: {
170
+ start: startTimestamp,
171
+ end: endTimestamp,
172
+ },
173
+ totalRecords: filteredData.length,
174
+ dataTypesFound: Array.from(dataTypesFound).sort(),
175
+ dataSources: args.dataSources,
176
+ filters: {
177
+ dataTypes: args.dataTypes,
178
+ valueMin: args.valueMin,
179
+ valueMax: args.valueMax,
180
+ },
181
+ limitApplied: args.limit,
182
+ };
183
+
184
+ return {
185
+ results: groupedResults,
186
+ metadata,
187
+ };
188
+ } else {
189
+ // Return flat paginated results (original behavior)
190
+ const sortedData = sortHealthData(filteredData, args.sortBy, args.sortOrder);
191
+ const paginatedResult = paginateResults(sortedData, args.page, args.pageSize);
192
+
193
+ return {
194
+ ...paginatedResult,
195
+ resolution,
196
+ filters: {
197
+ dataTypes: args.dataTypes,
198
+ dataSources: args.dataSources,
199
+ categories: args.categories,
200
+ startDate: startDay,
201
+ endDate: endDay,
202
+ startTimestamp: args.startTimestamp,
203
+ endTimestamp: args.endTimestamp,
204
+ valueMin: args.valueMin,
205
+ valueMax: args.valueMax,
206
+ limitApplied: args.limit,
207
+ },
208
+ };
209
+ }
210
+ }
211
+
212
+ /**
213
+ * Group records by requested categories
214
+ * (Copied from getDailyData/getEpochData for compatibility)
215
+ */
216
+ function groupByCategory(
217
+ records: HealthDataRecord[],
218
+ requestedCategories: HealthCategory[]
219
+ ): Record<string, HealthDataRecord[]> {
220
+ const grouped: Record<string, HealthDataRecord[]> = {};
221
+
222
+ // Initialize structure for requested categories
223
+ for (const category of requestedCategories) {
224
+ grouped[category] = [];
225
+ }
226
+
227
+ // Get category data type IDs for matching
228
+ const categoryDataTypes = new Map<number, HealthCategory[]>();
229
+ for (const category of requestedCategories) {
230
+ const mapping = CATEGORY_MAPPINGS[category];
231
+ if (mapping) {
232
+ for (const dataTypeId of mapping.dataTypeIds) {
233
+ if (!categoryDataTypes.has(dataTypeId)) {
234
+ categoryDataTypes.set(dataTypeId, []);
235
+ }
236
+ categoryDataTypes.get(dataTypeId)!.push(category);
237
+ }
238
+ }
239
+ }
240
+
241
+ // Group records
242
+ for (const record of records) {
243
+ const typeId = record.dataType || record.dailyDynamicValueType || record.dynamicEpochValueType;
244
+ if (typeId && categoryDataTypes.has(typeId)) {
245
+ const categories = categoryDataTypes.get(typeId)!;
246
+ for (const category of categories) {
247
+ grouped[category].push(record);
248
+ }
249
+ }
250
+ }
251
+
252
+ return grouped;
253
+ }
@@ -0,0 +1,162 @@
1
+ import type { Request } from "express";
2
+ import type { z } from "zod";
3
+ import type { SearchKeywordSchema } from "../schemas.js";
4
+ import type { HealthDataRecord } from "../types.js";
5
+ import {
6
+ extractThryveHeaders,
7
+ buildUrlEncodedBody,
8
+ getDefaultDateRange,
9
+ THRYVE_API_BASE_URL,
10
+ sortHealthData,
11
+ paginateResults,
12
+ } from "../helpers.js";
13
+ import { ALL_DATA_TYPES } from "../data-types-generated.js";
14
+ import { flattenThryveResponse, enrichWithDataTypes } from "../data-enrichment.js";
15
+
16
+ export async function searchKeyword(req: Request, args: z.infer<typeof SearchKeywordSchema>) {
17
+ const { headers, authToken } = extractThryveHeaders(req);
18
+
19
+ // Step 1: Search for matching property names in ALL_DATA_TYPES
20
+ const lowerQuery = args.query.toLowerCase();
21
+ const matchingDataTypes = ALL_DATA_TYPES.filter((dataType) =>
22
+ dataType.name.toLowerCase().includes(lowerQuery)
23
+ );
24
+
25
+ if (matchingDataTypes.length === 0) {
26
+ return {
27
+ data: [],
28
+ totalRecords: 0,
29
+ page: args.page,
30
+ pageSize: args.pageSize,
31
+ totalPages: 0,
32
+ query: args.query,
33
+ matchingPropertyNames: [],
34
+ message: `No property names found matching "${args.query}"`,
35
+ };
36
+ }
37
+
38
+ // Step 2: Get the data type IDs for matching properties
39
+ const matchingDataTypeIds = new Set(matchingDataTypes.map((dt) => dt.id));
40
+ const matchingPropertyNames = matchingDataTypes.map((dt) => dt.name);
41
+
42
+ // Determine date range
43
+ let startDay: string;
44
+ let endDay: string;
45
+ let startTimestamp: string;
46
+ let endTimestamp: string;
47
+
48
+ if (args.startDate || args.endDate) {
49
+ const defaults = getDefaultDateRange();
50
+ startDay = args.startDate || defaults.startDay;
51
+ endDay = args.endDate || defaults.endDay;
52
+ startTimestamp = `${startDay}T00:00:00Z`;
53
+ endTimestamp = `${endDay}T23:59:59Z`;
54
+ } else {
55
+ const defaults = getDefaultDateRange();
56
+ startDay = defaults.startDay;
57
+ endDay = defaults.endDay;
58
+ startTimestamp = `${startDay}T00:00:00Z`;
59
+ endTimestamp = `${endDay}T23:59:59Z`;
60
+ }
61
+
62
+ // Step 3: Determine which endpoint(s) to fetch from based on temporal resolution
63
+ const needsDaily = matchingDataTypes.some((dt) =>
64
+ dt.temporalResolution === "Daily" || dt.temporalResolution === "Both"
65
+ );
66
+ const needsEpoch = matchingDataTypes.some((dt) =>
67
+ dt.temporalResolution === "Epoch" || dt.temporalResolution === "Both"
68
+ );
69
+
70
+ const fetchPromises = [];
71
+
72
+ // Fetch daily data if needed
73
+ if (needsDaily) {
74
+ const dailyBody = buildUrlEncodedBody({
75
+ authenticationToken: authToken,
76
+ startDay,
77
+ endDay,
78
+ dataSources: args.dataSource,
79
+ displayTypeName: "true",
80
+ });
81
+
82
+ fetchPromises.push(
83
+ fetch(`${THRYVE_API_BASE_URL}/v5/dailyDynamicValues`, {
84
+ method: "POST",
85
+ headers,
86
+ body: dailyBody,
87
+ })
88
+ );
89
+ }
90
+
91
+ // Fetch epoch data if needed
92
+ if (needsEpoch) {
93
+ const epochBody = buildUrlEncodedBody({
94
+ authenticationToken: authToken,
95
+ startTimestamp,
96
+ endTimestamp,
97
+ dataSources: args.dataSource,
98
+ displayTypeName: "true",
99
+ });
100
+
101
+ fetchPromises.push(
102
+ fetch(`${THRYVE_API_BASE_URL}/v5/dynamicEpochValues`, {
103
+ method: "POST",
104
+ headers,
105
+ body: epochBody,
106
+ })
107
+ );
108
+ }
109
+
110
+ const responses = await Promise.all(fetchPromises);
111
+
112
+ // Check for errors
113
+ for (const response of responses) {
114
+ if (!response.ok) {
115
+ throw new Error(`Thryve API error: ${response.status} ${response.statusText}`);
116
+ }
117
+ }
118
+
119
+ // Parse responses
120
+ const jsonData = await Promise.all(responses.map((r) => r.json()));
121
+
122
+ // Flatten and enrich data
123
+ let allData: HealthDataRecord[] = [];
124
+ for (const data of jsonData) {
125
+ const flattened = flattenThryveResponse(data);
126
+ enrichWithDataTypes(flattened);
127
+ allData = allData.concat(flattened);
128
+ }
129
+
130
+ // Step 4: Filter by matching data type IDs
131
+ let filteredData = allData.filter((record) => {
132
+ const typeId = record.dataType || record.dailyDynamicValueType || record.dynamicEpochValueType;
133
+ return typeId && matchingDataTypeIds.has(typeId);
134
+ });
135
+
136
+ // Optional: Filter by data type category if specified
137
+ if (args.dataType) {
138
+ const lowerDataType = args.dataType.toLowerCase();
139
+ filteredData = filteredData.filter((record) => {
140
+ const typeName = record.dailyDynamicValueTypeName || record.dynamicEpochValueTypeName || "";
141
+ return typeName.toLowerCase().includes(lowerDataType);
142
+ });
143
+ }
144
+
145
+ // Sort the results
146
+ const sortedData = sortHealthData(filteredData, args.sortBy, args.sortOrder);
147
+
148
+ // Paginate the results
149
+ const paginatedResult = paginateResults(sortedData, args.page, args.pageSize);
150
+
151
+ return {
152
+ ...paginatedResult,
153
+ query: args.query,
154
+ matchingPropertyNames,
155
+ filters: {
156
+ startDate: startDay,
157
+ endDate: endDay,
158
+ dataType: args.dataType,
159
+ dataSource: args.dataSource,
160
+ },
161
+ };
162
+ }
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ // Type for health data records (unified format for daily and epoch data)
2
+ export interface HealthDataRecord {
3
+ day?: string;
4
+ timestamp?: string;
5
+ value: string | number;
6
+ dataType?: number;
7
+ dataTypeName?: string;
8
+ dailyDynamicValueType?: number;
9
+ dynamicEpochValueType?: number;
10
+ dailyDynamicValueTypeName?: string;
11
+ dynamicEpochValueTypeName?: string;
12
+ dataSource?: number;
13
+ dataSourceName?: string;
14
+ unit?: string;
15
+ category?: string;
16
+ [key: string]: any;
17
+ }
18
+
19
+ // Pagination result structure
20
+ export interface PaginationMetadata {
21
+ currentPage: number;
22
+ pageSize: number;
23
+ totalResults: number;
24
+ totalPages: number;
25
+ }
26
+
27
+ export interface PaginatedResult<T> {
28
+ results: T[];
29
+ pagination: PaginationMetadata;
30
+ }
31
+
32
+ // Parsed record ID structure
33
+ export interface ParsedRecordId {
34
+ type: 'daily' | 'epoch';
35
+ dataType: string;
36
+ timeKey: string;
37
+ dataSource: string;
38
+ }
39
+
40
+ // Thryve API headers structure
41
+ export interface ThryveHeaders {
42
+ headers: Record<string, string>;
43
+ authToken: string;
44
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }