@depup/typesense 3.0.3-depup.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.
Files changed (281) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -0
  3. package/changes.json +10 -0
  4. package/dist/typesense.js +13031 -0
  5. package/dist/typesense.js.map +1 -0
  6. package/dist/typesense.min.js +3 -0
  7. package/dist/typesense.min.js.LICENSE.txt +1 -0
  8. package/dist/typesense.min.js.map +1 -0
  9. package/lib/Typesense/Alias.d.ts +10 -0
  10. package/lib/Typesense/Alias.js +30 -0
  11. package/lib/Typesense/Alias.js.map +1 -0
  12. package/lib/Typesense/Aliases.d.ts +18 -0
  13. package/lib/Typesense/Aliases.js +36 -0
  14. package/lib/Typesense/Aliases.js.map +1 -0
  15. package/lib/Typesense/Analytics.d.ts +15 -0
  16. package/lib/Typesense/Analytics.js +40 -0
  17. package/lib/Typesense/Analytics.js.map +1 -0
  18. package/lib/Typesense/AnalyticsEvent.d.ts +5 -0
  19. package/lib/Typesense/AnalyticsEvent.js +3 -0
  20. package/lib/Typesense/AnalyticsEvent.js.map +1 -0
  21. package/lib/Typesense/AnalyticsEvents.d.ts +26 -0
  22. package/lib/Typesense/AnalyticsEvents.js +37 -0
  23. package/lib/Typesense/AnalyticsEvents.js.map +1 -0
  24. package/lib/Typesense/AnalyticsRule.d.ts +47 -0
  25. package/lib/Typesense/AnalyticsRule.js +30 -0
  26. package/lib/Typesense/AnalyticsRule.js.map +1 -0
  27. package/lib/Typesense/AnalyticsRuleV1.d.ts +35 -0
  28. package/lib/Typesense/AnalyticsRuleV1.js +30 -0
  29. package/lib/Typesense/AnalyticsRuleV1.js.map +1 -0
  30. package/lib/Typesense/AnalyticsRules.d.ts +16 -0
  31. package/lib/Typesense/AnalyticsRules.js +49 -0
  32. package/lib/Typesense/AnalyticsRules.js.map +1 -0
  33. package/lib/Typesense/AnalyticsRulesV1.d.ts +13 -0
  34. package/lib/Typesense/AnalyticsRulesV1.js +37 -0
  35. package/lib/Typesense/AnalyticsRulesV1.js.map +1 -0
  36. package/lib/Typesense/AnalyticsV1.d.ts +19 -0
  37. package/lib/Typesense/AnalyticsV1.js +49 -0
  38. package/lib/Typesense/AnalyticsV1.js.map +1 -0
  39. package/lib/Typesense/ApiCall.d.ts +99 -0
  40. package/lib/Typesense/ApiCall.js +777 -0
  41. package/lib/Typesense/ApiCall.js.map +1 -0
  42. package/lib/Typesense/Client.d.ts +79 -0
  43. package/lib/Typesense/Client.js +170 -0
  44. package/lib/Typesense/Client.js.map +1 -0
  45. package/lib/Typesense/Collection.d.ts +63 -0
  46. package/lib/Typesense/Collection.js +109 -0
  47. package/lib/Typesense/Collection.js.map +1 -0
  48. package/lib/Typesense/Collections.d.ts +42 -0
  49. package/lib/Typesense/Collections.js +34 -0
  50. package/lib/Typesense/Collections.js.map +1 -0
  51. package/lib/Typesense/Configuration.d.ts +143 -0
  52. package/lib/Typesense/Configuration.js +113 -0
  53. package/lib/Typesense/Configuration.js.map +1 -0
  54. package/lib/Typesense/Conversation.d.ts +22 -0
  55. package/lib/Typesense/Conversation.js +37 -0
  56. package/lib/Typesense/Conversation.js.map +1 -0
  57. package/lib/Typesense/ConversationModel.d.ts +30 -0
  58. package/lib/Typesense/ConversationModel.js +37 -0
  59. package/lib/Typesense/ConversationModel.js.map +1 -0
  60. package/lib/Typesense/ConversationModels.d.ts +10 -0
  61. package/lib/Typesense/ConversationModels.js +37 -0
  62. package/lib/Typesense/ConversationModels.js.map +1 -0
  63. package/lib/Typesense/Conversations.d.ts +17 -0
  64. package/lib/Typesense/Conversations.js +42 -0
  65. package/lib/Typesense/Conversations.js.map +1 -0
  66. package/lib/Typesense/CurationSet.d.ts +17 -0
  67. package/lib/Typesense/CurationSet.js +52 -0
  68. package/lib/Typesense/CurationSet.js.map +1 -0
  69. package/lib/Typesense/CurationSetItem.d.ts +15 -0
  70. package/lib/Typesense/CurationSetItem.js +38 -0
  71. package/lib/Typesense/CurationSetItem.js.map +1 -0
  72. package/lib/Typesense/CurationSetItems.d.ts +9 -0
  73. package/lib/Typesense/CurationSetItems.js +23 -0
  74. package/lib/Typesense/CurationSetItems.js.map +1 -0
  75. package/lib/Typesense/CurationSets.d.ts +56 -0
  76. package/lib/Typesense/CurationSets.js +19 -0
  77. package/lib/Typesense/CurationSets.js.map +1 -0
  78. package/lib/Typesense/Debug.d.ts +10 -0
  79. package/lib/Typesense/Debug.js +19 -0
  80. package/lib/Typesense/Debug.js.map +1 -0
  81. package/lib/Typesense/Document.d.ts +12 -0
  82. package/lib/Typesense/Document.js +41 -0
  83. package/lib/Typesense/Document.js.map +1 -0
  84. package/lib/Typesense/Documents.d.ts +196 -0
  85. package/lib/Typesense/Documents.js +204 -0
  86. package/lib/Typesense/Documents.js.map +1 -0
  87. package/lib/Typesense/Errors/HTTPError.d.ts +3 -0
  88. package/lib/Typesense/Errors/HTTPError.js +13 -0
  89. package/lib/Typesense/Errors/HTTPError.js.map +1 -0
  90. package/lib/Typesense/Errors/ImportError.d.ts +15 -0
  91. package/lib/Typesense/Errors/ImportError.js +16 -0
  92. package/lib/Typesense/Errors/ImportError.js.map +1 -0
  93. package/lib/Typesense/Errors/MissingConfigurationError.d.ts +3 -0
  94. package/lib/Typesense/Errors/MissingConfigurationError.js +13 -0
  95. package/lib/Typesense/Errors/MissingConfigurationError.js.map +1 -0
  96. package/lib/Typesense/Errors/ObjectAlreadyExists.d.ts +3 -0
  97. package/lib/Typesense/Errors/ObjectAlreadyExists.js +13 -0
  98. package/lib/Typesense/Errors/ObjectAlreadyExists.js.map +1 -0
  99. package/lib/Typesense/Errors/ObjectNotFound.d.ts +3 -0
  100. package/lib/Typesense/Errors/ObjectNotFound.js +13 -0
  101. package/lib/Typesense/Errors/ObjectNotFound.js.map +1 -0
  102. package/lib/Typesense/Errors/ObjectUnprocessable.d.ts +3 -0
  103. package/lib/Typesense/Errors/ObjectUnprocessable.js +13 -0
  104. package/lib/Typesense/Errors/ObjectUnprocessable.js.map +1 -0
  105. package/lib/Typesense/Errors/RequestMalformed.d.ts +3 -0
  106. package/lib/Typesense/Errors/RequestMalformed.js +13 -0
  107. package/lib/Typesense/Errors/RequestMalformed.js.map +1 -0
  108. package/lib/Typesense/Errors/RequestUnauthorized.d.ts +3 -0
  109. package/lib/Typesense/Errors/RequestUnauthorized.js +13 -0
  110. package/lib/Typesense/Errors/RequestUnauthorized.js.map +1 -0
  111. package/lib/Typesense/Errors/ServerError.d.ts +3 -0
  112. package/lib/Typesense/Errors/ServerError.js +13 -0
  113. package/lib/Typesense/Errors/ServerError.js.map +1 -0
  114. package/lib/Typesense/Errors/TypesenseError.d.ts +5 -0
  115. package/lib/Typesense/Errors/TypesenseError.js +19 -0
  116. package/lib/Typesense/Errors/TypesenseError.js.map +1 -0
  117. package/lib/Typesense/Errors/index.d.ts +11 -0
  118. package/lib/Typesense/Errors/index.js +25 -0
  119. package/lib/Typesense/Errors/index.js.map +1 -0
  120. package/lib/Typesense/Health.d.ts +9 -0
  121. package/lib/Typesense/Health.js +19 -0
  122. package/lib/Typesense/Health.js.map +1 -0
  123. package/lib/Typesense/Key.d.ts +33 -0
  124. package/lib/Typesense/Key.js +30 -0
  125. package/lib/Typesense/Key.js.map +1 -0
  126. package/lib/Typesense/Keys.d.ts +19 -0
  127. package/lib/Typesense/Keys.js +46 -0
  128. package/lib/Typesense/Keys.js.map +1 -0
  129. package/lib/Typesense/Metrics.d.ts +25 -0
  130. package/lib/Typesense/Metrics.js +19 -0
  131. package/lib/Typesense/Metrics.js.map +1 -0
  132. package/lib/Typesense/MultiSearch.d.ts +25 -0
  133. package/lib/Typesense/MultiSearch.js +63 -0
  134. package/lib/Typesense/MultiSearch.js.map +1 -0
  135. package/lib/Typesense/NLSearchModel.d.ts +16 -0
  136. package/lib/Typesense/NLSearchModel.js +37 -0
  137. package/lib/Typesense/NLSearchModel.js.map +1 -0
  138. package/lib/Typesense/NLSearchModels.d.ts +36 -0
  139. package/lib/Typesense/NLSearchModels.js +36 -0
  140. package/lib/Typesense/NLSearchModels.js.map +1 -0
  141. package/lib/Typesense/Operations.d.ts +12 -0
  142. package/lib/Typesense/Operations.js +27 -0
  143. package/lib/Typesense/Operations.js.map +1 -0
  144. package/lib/Typesense/Override.d.ts +17 -0
  145. package/lib/Typesense/Override.js +32 -0
  146. package/lib/Typesense/Override.js.map +1 -0
  147. package/lib/Typesense/Overrides.d.ts +43 -0
  148. package/lib/Typesense/Overrides.js +38 -0
  149. package/lib/Typesense/Overrides.js.map +1 -0
  150. package/lib/Typesense/Preset.d.ts +17 -0
  151. package/lib/Typesense/Preset.js +30 -0
  152. package/lib/Typesense/Preset.js.map +1 -0
  153. package/lib/Typesense/Presets.d.ts +18 -0
  154. package/lib/Typesense/Presets.js +49 -0
  155. package/lib/Typesense/Presets.js.map +1 -0
  156. package/lib/Typesense/RequestWithCache.d.ts +15 -0
  157. package/lib/Typesense/RequestWithCache.js +117 -0
  158. package/lib/Typesense/RequestWithCache.js.map +1 -0
  159. package/lib/Typesense/SearchClient.d.ts +13 -0
  160. package/lib/Typesense/SearchClient.js +45 -0
  161. package/lib/Typesense/SearchClient.js.map +1 -0
  162. package/lib/Typesense/SearchOnlyCollection.d.ts +11 -0
  163. package/lib/Typesense/SearchOnlyCollection.js +18 -0
  164. package/lib/Typesense/SearchOnlyCollection.js.map +1 -0
  165. package/lib/Typesense/SearchOnlyDocuments.d.ts +16 -0
  166. package/lib/Typesense/SearchOnlyDocuments.js +57 -0
  167. package/lib/Typesense/SearchOnlyDocuments.js.map +1 -0
  168. package/lib/Typesense/Stats.d.ts +24 -0
  169. package/lib/Typesense/Stats.js +19 -0
  170. package/lib/Typesense/Stats.js.map +1 -0
  171. package/lib/Typesense/Stemming.d.ts +12 -0
  172. package/lib/Typesense/Stemming.js +35 -0
  173. package/lib/Typesense/Stemming.js.map +1 -0
  174. package/lib/Typesense/StemmingDictionaries.d.ts +13 -0
  175. package/lib/Typesense/StemmingDictionaries.js +58 -0
  176. package/lib/Typesense/StemmingDictionaries.js.map +1 -0
  177. package/lib/Typesense/StemmingDictionary.d.ts +20 -0
  178. package/lib/Typesense/StemmingDictionary.js +30 -0
  179. package/lib/Typesense/StemmingDictionary.js.map +1 -0
  180. package/lib/Typesense/Stopword.d.ts +20 -0
  181. package/lib/Typesense/Stopword.js +30 -0
  182. package/lib/Typesense/Stopword.js.map +1 -0
  183. package/lib/Typesense/Stopwords.d.ts +17 -0
  184. package/lib/Typesense/Stopwords.js +36 -0
  185. package/lib/Typesense/Stopwords.js.map +1 -0
  186. package/lib/Typesense/Synonym.d.ts +21 -0
  187. package/lib/Typesense/Synonym.js +41 -0
  188. package/lib/Typesense/Synonym.js.map +1 -0
  189. package/lib/Typesense/SynonymSet.d.ts +21 -0
  190. package/lib/Typesense/SynonymSet.js +52 -0
  191. package/lib/Typesense/SynonymSet.js.map +1 -0
  192. package/lib/Typesense/SynonymSetItem.d.ts +14 -0
  193. package/lib/Typesense/SynonymSetItem.js +31 -0
  194. package/lib/Typesense/SynonymSetItem.js.map +1 -0
  195. package/lib/Typesense/SynonymSetItems.d.ts +10 -0
  196. package/lib/Typesense/SynonymSetItems.js +30 -0
  197. package/lib/Typesense/SynonymSetItems.js.map +1 -0
  198. package/lib/Typesense/SynonymSets.d.ts +23 -0
  199. package/lib/Typesense/SynonymSets.js +19 -0
  200. package/lib/Typesense/SynonymSets.js.map +1 -0
  201. package/lib/Typesense/Synonyms.d.ts +24 -0
  202. package/lib/Typesense/Synonyms.js +47 -0
  203. package/lib/Typesense/Synonyms.js.map +1 -0
  204. package/lib/Typesense/Types.d.ts +201 -0
  205. package/lib/Typesense/Types.js +22 -0
  206. package/lib/Typesense/Types.js.map +1 -0
  207. package/lib/Typesense/Utils.d.ts +11 -0
  208. package/lib/Typesense/Utils.js +50 -0
  209. package/lib/Typesense/Utils.js.map +1 -0
  210. package/lib/Typesense.d.ts +46 -0
  211. package/lib/Typesense.js +12 -0
  212. package/lib/Typesense.js.map +1 -0
  213. package/package.json +141 -0
  214. package/src/Typesense/Alias.ts +18 -0
  215. package/src/Typesense/Aliases.ts +41 -0
  216. package/src/Typesense/Analytics.ts +39 -0
  217. package/src/Typesense/AnalyticsEvent.ts +5 -0
  218. package/src/Typesense/AnalyticsEvents.ts +53 -0
  219. package/src/Typesense/AnalyticsRule.ts +63 -0
  220. package/src/Typesense/AnalyticsRuleV1.ts +52 -0
  221. package/src/Typesense/AnalyticsRules.ts +67 -0
  222. package/src/Typesense/AnalyticsRulesV1.ts +43 -0
  223. package/src/Typesense/AnalyticsV1.ts +52 -0
  224. package/src/Typesense/ApiCall.ts +1103 -0
  225. package/src/Typesense/Client.ts +225 -0
  226. package/src/Typesense/Collection.ts +179 -0
  227. package/src/Typesense/Collections.ts +68 -0
  228. package/src/Typesense/Configuration.ts +328 -0
  229. package/src/Typesense/Conversation.ts +45 -0
  230. package/src/Typesense/ConversationModel.ts +55 -0
  231. package/src/Typesense/ConversationModels.ts +38 -0
  232. package/src/Typesense/Conversations.ts +47 -0
  233. package/src/Typesense/CurationSet.ts +56 -0
  234. package/src/Typesense/CurationSetItem.ts +35 -0
  235. package/src/Typesense/CurationSetItems.ts +18 -0
  236. package/src/Typesense/CurationSets.ts +60 -0
  237. package/src/Typesense/Debug.ts +16 -0
  238. package/src/Typesense/Document.ts +34 -0
  239. package/src/Typesense/Documents.ts +445 -0
  240. package/src/Typesense/Errors/HTTPError.ts +3 -0
  241. package/src/Typesense/Errors/ImportError.ts +24 -0
  242. package/src/Typesense/Errors/MissingConfigurationError.ts +3 -0
  243. package/src/Typesense/Errors/ObjectAlreadyExists.ts +3 -0
  244. package/src/Typesense/Errors/ObjectNotFound.ts +3 -0
  245. package/src/Typesense/Errors/ObjectUnprocessable.ts +3 -0
  246. package/src/Typesense/Errors/RequestMalformed.ts +3 -0
  247. package/src/Typesense/Errors/RequestUnauthorized.ts +3 -0
  248. package/src/Typesense/Errors/ServerError.ts +3 -0
  249. package/src/Typesense/Errors/TypesenseError.ts +13 -0
  250. package/src/Typesense/Errors/index.ts +23 -0
  251. package/src/Typesense/Health.ts +15 -0
  252. package/src/Typesense/Key.ts +73 -0
  253. package/src/Typesense/Keys.ts +59 -0
  254. package/src/Typesense/Metrics.ts +31 -0
  255. package/src/Typesense/MultiSearch.ts +144 -0
  256. package/src/Typesense/NLSearchModel.ts +35 -0
  257. package/src/Typesense/NLSearchModels.ts +61 -0
  258. package/src/Typesense/Operations.ts +35 -0
  259. package/src/Typesense/Override.ts +31 -0
  260. package/src/Typesense/Overrides.ts +73 -0
  261. package/src/Typesense/Preset.ts +31 -0
  262. package/src/Typesense/Presets.ts +62 -0
  263. package/src/Typesense/RequestWithCache.ts +209 -0
  264. package/src/Typesense/SearchClient.ts +64 -0
  265. package/src/Typesense/SearchOnlyCollection.ts +24 -0
  266. package/src/Typesense/SearchOnlyDocuments.ts +87 -0
  267. package/src/Typesense/Stats.ts +31 -0
  268. package/src/Typesense/Stemming.ts +38 -0
  269. package/src/Typesense/StemmingDictionaries.ts +59 -0
  270. package/src/Typesense/StemmingDictionary.ts +37 -0
  271. package/src/Typesense/Stopword.ts +31 -0
  272. package/src/Typesense/Stopwords.ts +41 -0
  273. package/src/Typesense/Synonym.ts +42 -0
  274. package/src/Typesense/SynonymSet.ts +57 -0
  275. package/src/Typesense/SynonymSetItem.ts +28 -0
  276. package/src/Typesense/SynonymSetItems.ts +31 -0
  277. package/src/Typesense/SynonymSets.ts +30 -0
  278. package/src/Typesense/Synonyms.ts +55 -0
  279. package/src/Typesense/Types.ts +355 -0
  280. package/src/Typesense/Utils.ts +80 -0
  281. package/src/Typesense.ts +43 -0
@@ -0,0 +1,1103 @@
1
+ import type {
2
+ AxiosAdapter,
3
+ AxiosRequestConfig,
4
+ AxiosResponse,
5
+ Method,
6
+ } from "axios";
7
+ import axios from "axios";
8
+ import { Agent as HTTPAgent } from "http";
9
+ import { Agent as HTTPSAgent } from "https";
10
+ import { Logger } from "loglevel";
11
+ import Configuration from "./Configuration";
12
+ import type { NodeConfiguration, StreamConfig } from "./Configuration";
13
+ import {
14
+ HTTPError,
15
+ ObjectAlreadyExists,
16
+ ObjectNotFound,
17
+ ObjectUnprocessable,
18
+ RequestMalformed,
19
+ RequestUnauthorized,
20
+ ServerError,
21
+ } from "./Errors";
22
+ import TypesenseError from "./Errors/TypesenseError";
23
+ import type { DocumentSchema, SearchResponse } from "./Documents";
24
+ import { toErrorWithMessage } from "./Utils";
25
+ import { MessageChunk } from "./Types";
26
+
27
+ const APIKEYHEADERNAME = "X-TYPESENSE-API-KEY";
28
+ const HEALTHY = true;
29
+ const UNHEALTHY = false;
30
+
31
+ interface Node extends NodeConfiguration {
32
+ isHealthy: boolean;
33
+ index: string | number;
34
+ }
35
+
36
+ const isNodeJSEnvironment =
37
+ typeof process !== "undefined" &&
38
+ process.versions != null &&
39
+ process.versions.node != null &&
40
+ typeof window === "undefined";
41
+
42
+ export interface HttpClient {
43
+ get<T>(
44
+ endpoint: string,
45
+ queryParameters: Record<string, unknown>,
46
+ {
47
+ abortSignal,
48
+ responseType,
49
+ streamConfig,
50
+ isStreamingRequest,
51
+ }: {
52
+ abortSignal?: AbortSignal | null;
53
+ responseType?: AxiosRequestConfig["responseType"] | undefined;
54
+ streamConfig?:
55
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
56
+ | undefined;
57
+ isStreamingRequest: boolean | undefined;
58
+ },
59
+ ): Promise<T>;
60
+ delete<T>(
61
+ endpoint: string,
62
+ queryParameters: Record<string, unknown>,
63
+ ): Promise<T>;
64
+ post<T>(
65
+ endpoint: string,
66
+ bodyParameters: unknown,
67
+ queryParameters: Record<string, unknown>,
68
+ additionalHeaders: Record<string, string>,
69
+ {
70
+ abortSignal,
71
+ responseType,
72
+ streamConfig,
73
+ isStreamingRequest,
74
+ }: {
75
+ abortSignal?: AbortSignal | null;
76
+ responseType?: AxiosRequestConfig["responseType"] | undefined;
77
+ streamConfig?:
78
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
79
+ | undefined;
80
+ isStreamingRequest: boolean | undefined;
81
+ },
82
+ ): Promise<T>;
83
+ put<T>(
84
+ endpoint: string,
85
+ bodyParameters: unknown,
86
+ queryParameters: Record<string, unknown>,
87
+ ): Promise<T>;
88
+ patch<T>(
89
+ endpoint: string,
90
+ bodyParameters: unknown,
91
+ queryParameters: Record<string, unknown>,
92
+ ): Promise<T>;
93
+ }
94
+
95
+ export default class ApiCall implements HttpClient {
96
+ private readonly apiKey: string;
97
+ private readonly nodes: Node[];
98
+ private readonly nearestNode: Node;
99
+ private readonly connectionTimeoutSeconds: number;
100
+ private readonly healthcheckIntervalSeconds: number;
101
+ private readonly retryIntervalSeconds: number;
102
+ private readonly sendApiKeyAsQueryParam?: boolean;
103
+ private readonly numRetriesPerRequest: number;
104
+ private readonly additionalUserHeaders?: Record<string, string>;
105
+
106
+ readonly logger: Logger;
107
+ private currentNodeIndex: number;
108
+
109
+ constructor(private configuration: Configuration) {
110
+ this.apiKey = this.configuration.apiKey;
111
+ this.nodes =
112
+ this.configuration.nodes == null
113
+ ? this.configuration.nodes
114
+ : JSON.parse(JSON.stringify(this.configuration.nodes)); // Make a copy, since we'll be adding additional metadata to the nodes
115
+ this.nearestNode =
116
+ this.configuration.nearestNode == null
117
+ ? this.configuration.nearestNode
118
+ : JSON.parse(JSON.stringify(this.configuration.nearestNode));
119
+ this.connectionTimeoutSeconds = this.configuration.connectionTimeoutSeconds;
120
+ this.healthcheckIntervalSeconds =
121
+ this.configuration.healthcheckIntervalSeconds;
122
+ this.numRetriesPerRequest = this.configuration.numRetries;
123
+ this.retryIntervalSeconds = this.configuration.retryIntervalSeconds;
124
+ this.sendApiKeyAsQueryParam = this.configuration.sendApiKeyAsQueryParam;
125
+ this.additionalUserHeaders = this.configuration.additionalHeaders;
126
+
127
+ this.logger = this.configuration.logger;
128
+
129
+ this.initializeMetadataForNodes();
130
+ this.currentNodeIndex = -1;
131
+ }
132
+
133
+ async get<T>(
134
+ endpoint: string,
135
+ queryParameters: any = {},
136
+ {
137
+ abortSignal = null,
138
+ responseType = undefined,
139
+ streamConfig = undefined,
140
+ isStreamingRequest,
141
+ }: {
142
+ abortSignal?: any;
143
+ responseType?: AxiosRequestConfig["responseType"] | undefined;
144
+ streamConfig?:
145
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
146
+ | undefined;
147
+ isStreamingRequest?: boolean | undefined;
148
+ } = {},
149
+ ): Promise<T> {
150
+ return this.performRequest<T>("get", endpoint, {
151
+ queryParameters,
152
+ abortSignal,
153
+ responseType,
154
+ streamConfig,
155
+ isStreamingRequest,
156
+ });
157
+ }
158
+
159
+ async delete<T>(endpoint: string, queryParameters: any = {}): Promise<T> {
160
+ return this.performRequest<T>("delete", endpoint, {
161
+ queryParameters,
162
+ isStreamingRequest: false,
163
+ });
164
+ }
165
+
166
+ async post<T>(
167
+ endpoint: string,
168
+ bodyParameters: any = {},
169
+ queryParameters: any = {},
170
+ additionalHeaders: any = {},
171
+ {
172
+ abortSignal = null,
173
+ responseType = undefined,
174
+ streamConfig = undefined,
175
+ isStreamingRequest,
176
+ }: {
177
+ abortSignal?: AbortSignal | null;
178
+ responseType?: AxiosRequestConfig["responseType"] | undefined;
179
+ streamConfig?:
180
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
181
+ | undefined;
182
+ isStreamingRequest?: boolean | undefined;
183
+ } = {},
184
+ ): Promise<T> {
185
+ return this.performRequest<T>("post", endpoint, {
186
+ queryParameters,
187
+ bodyParameters,
188
+ additionalHeaders,
189
+ abortSignal,
190
+ responseType,
191
+ streamConfig,
192
+ isStreamingRequest,
193
+ });
194
+ }
195
+
196
+ async put<T>(
197
+ endpoint: string,
198
+ bodyParameters: any = {},
199
+ queryParameters: any = {},
200
+ ): Promise<T> {
201
+ return this.performRequest<T>("put", endpoint, {
202
+ queryParameters,
203
+ bodyParameters,
204
+ isStreamingRequest: false,
205
+ });
206
+ }
207
+
208
+ async patch<T>(
209
+ endpoint: string,
210
+ bodyParameters: any = {},
211
+ queryParameters: any = {},
212
+ ): Promise<T> {
213
+ return this.performRequest<T>("patch", endpoint, {
214
+ queryParameters,
215
+ bodyParameters,
216
+ isStreamingRequest: false,
217
+ });
218
+ }
219
+
220
+ private getAdapter(): AxiosAdapter | undefined {
221
+ if (!this.configuration.axiosAdapter) return undefined;
222
+
223
+ if (typeof this.configuration.axiosAdapter === "function")
224
+ return this.configuration.axiosAdapter;
225
+
226
+ const isCloudflareWorkers =
227
+ typeof navigator !== "undefined" &&
228
+ navigator.userAgent === "Cloudflare-Workers";
229
+
230
+ return isCloudflareWorkers
231
+ ? axios.getAdapter(this.configuration.axiosAdapter).bind(globalThis)
232
+ : axios.getAdapter(this.configuration.axiosAdapter);
233
+ }
234
+
235
+ async performRequest<T>(
236
+ requestType: Method,
237
+ endpoint: string,
238
+ {
239
+ queryParameters = null,
240
+ bodyParameters = null,
241
+ additionalHeaders = {},
242
+ abortSignal = null,
243
+ responseType = undefined,
244
+ skipConnectionTimeout = false,
245
+ enableKeepAlive = undefined,
246
+ streamConfig = undefined,
247
+ isStreamingRequest,
248
+ }: {
249
+ queryParameters?: any;
250
+ bodyParameters?: any;
251
+ additionalHeaders?: any;
252
+ abortSignal?: any;
253
+ responseType?: AxiosRequestConfig["responseType"] | undefined;
254
+ skipConnectionTimeout?: boolean;
255
+ enableKeepAlive?: boolean | undefined;
256
+ streamConfig?:
257
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
258
+ | undefined;
259
+ isStreamingRequest?: boolean | undefined;
260
+ },
261
+ ): Promise<T> {
262
+ this.configuration.validate();
263
+
264
+ if (isStreamingRequest) {
265
+ this.logger.debug(`Request: Performing streaming request to ${endpoint}`);
266
+
267
+ // For browser streaming, always use responseType: "stream" and adapter: "fetch"
268
+ if (!isNodeJSEnvironment && typeof fetch !== "undefined") {
269
+ this.logger.debug("Using fetch adapter for browser streaming");
270
+ responseType = "stream";
271
+ }
272
+ }
273
+
274
+ const requestNumber = Date.now();
275
+ let lastException;
276
+ let wasAborted = false;
277
+ this.logger.debug(
278
+ `Request #${requestNumber}: Performing ${requestType.toUpperCase()} request: ${endpoint}`,
279
+ );
280
+ for (
281
+ let numTries = 1;
282
+ numTries <= this.numRetriesPerRequest + 1;
283
+ numTries++
284
+ ) {
285
+ const node = this.getNextNode(requestNumber);
286
+ this.logger.debug(
287
+ `Request #${requestNumber}: Attempting ${requestType.toUpperCase()} request Try #${numTries} to Node ${
288
+ node.index
289
+ }`,
290
+ );
291
+
292
+ if (abortSignal && abortSignal.aborted) {
293
+ return Promise.reject(new Error("Request aborted by caller."));
294
+ }
295
+
296
+ let abortListener: ((event: Event) => void) | undefined;
297
+
298
+ try {
299
+ const requestOptions: AxiosRequestConfig<string> = {
300
+ method: requestType,
301
+ url: this.uriFor(endpoint, node),
302
+ headers: Object.assign(
303
+ {},
304
+ this.defaultHeaders(),
305
+ additionalHeaders,
306
+ this.additionalUserHeaders,
307
+ ),
308
+ maxContentLength: Infinity,
309
+ maxBodyLength: Infinity,
310
+ validateStatus: (status) => {
311
+ /* Override default validateStatus, which only considers 2xx a success.
312
+ In our case, if the server returns any HTTP code, we will handle it below.
313
+ We do this to be able to raise custom errors based on response code.
314
+ */
315
+ return status > 0;
316
+ },
317
+ transformResponse: [
318
+ (data, headers) => {
319
+ let transformedData = data;
320
+ if (
321
+ headers !== undefined &&
322
+ typeof data === "string" &&
323
+ headers["content-type"] &&
324
+ headers["content-type"].startsWith("application/json")
325
+ ) {
326
+ transformedData = JSON.parse(data);
327
+ }
328
+ return transformedData;
329
+ },
330
+ ],
331
+ };
332
+
333
+ // Use fetch adapter only for streaming requests in browser environments
334
+ requestOptions.adapter =
335
+ isStreamingRequest && !isNodeJSEnvironment
336
+ ? "fetch"
337
+ : this.getAdapter();
338
+
339
+ if (skipConnectionTimeout !== true) {
340
+ requestOptions.timeout = this.connectionTimeoutSeconds * 1000;
341
+ }
342
+
343
+ if (queryParameters && Object.keys(queryParameters).length !== 0) {
344
+ requestOptions.params = queryParameters;
345
+ }
346
+
347
+ if (this.sendApiKeyAsQueryParam) {
348
+ requestOptions.params = requestOptions.params || {};
349
+ requestOptions.params["x-typesense-api-key"] = this.apiKey;
350
+ }
351
+
352
+ if (this.configuration.httpAgent) {
353
+ this.logger.debug(
354
+ `Request #${requestNumber}: Using custom httpAgent`,
355
+ );
356
+ requestOptions.httpAgent = this.configuration.httpAgent;
357
+ } else if (enableKeepAlive === true) {
358
+ if (!isNodeJSEnvironment) {
359
+ this.logger.warn(
360
+ `Request #${requestNumber}: Cannot use custom httpAgent in a browser environment to enable keepAlive`,
361
+ );
362
+ } else {
363
+ this.logger.debug(`Request #${requestNumber}: Enabling KeepAlive`);
364
+ requestOptions.httpAgent = new HTTPAgent({ keepAlive: true });
365
+ }
366
+ }
367
+
368
+ if (this.configuration.httpsAgent) {
369
+ this.logger.debug(
370
+ `Request #${requestNumber}: Using custom httpsAgent`,
371
+ );
372
+ requestOptions.httpsAgent = this.configuration.httpsAgent;
373
+ } else if (enableKeepAlive === true) {
374
+ if (!isNodeJSEnvironment) {
375
+ this.logger.warn(
376
+ `Request #${requestNumber}: Cannot use custom httpAgent in a browser environment to enable keepAlive`,
377
+ );
378
+ } else {
379
+ this.logger.debug(`Request #${requestNumber}: Enabling keepAlive`);
380
+ requestOptions.httpsAgent = new HTTPSAgent({ keepAlive: true });
381
+ }
382
+ }
383
+
384
+ if (this.configuration.paramsSerializer) {
385
+ this.logger.debug(
386
+ `Request #${requestNumber}: Using custom paramsSerializer`,
387
+ );
388
+ requestOptions.paramsSerializer = this.configuration.paramsSerializer;
389
+ }
390
+
391
+ if (
392
+ bodyParameters &&
393
+ ((typeof bodyParameters === "string" &&
394
+ bodyParameters.length !== 0) ||
395
+ (typeof bodyParameters === "object" &&
396
+ Object.keys(bodyParameters).length !== 0))
397
+ ) {
398
+ requestOptions.data = bodyParameters;
399
+ }
400
+
401
+ // Translate from user-provided AbortController to the Axios request cancel mechanism.
402
+ if (abortSignal) {
403
+ const cancelToken = axios.CancelToken;
404
+ const source = cancelToken.source();
405
+ abortListener = () => {
406
+ wasAborted = true;
407
+ source.cancel();
408
+ };
409
+ abortSignal.addEventListener("abort", abortListener);
410
+ requestOptions.cancelToken = source.token;
411
+ }
412
+
413
+ if (isStreamingRequest) {
414
+ requestOptions.responseType = "stream";
415
+ if (!isNodeJSEnvironment) {
416
+ requestOptions.headers = {
417
+ ...requestOptions.headers,
418
+ Accept: "text/event-stream",
419
+ };
420
+ }
421
+ } else if (responseType) {
422
+ requestOptions.responseType = responseType;
423
+ }
424
+
425
+ const response = await axios(requestOptions);
426
+
427
+ if (response.status >= 1 && response.status <= 499) {
428
+ // Treat any status code > 0 and < 500 to be an indication that node is healthy
429
+ // We exclude 0 since some clients return 0 when request fails
430
+ this.setNodeHealthcheck(node, HEALTHY);
431
+ }
432
+
433
+ this.logger.debug(
434
+ `Request #${requestNumber}: Request to Node ${node.index} was made. Response Code was ${response.status}.`,
435
+ );
436
+
437
+ if (response.status >= 200 && response.status < 300) {
438
+ if (isStreamingRequest) {
439
+ return this.handleStreamingResponse<T>(response, streamConfig);
440
+ }
441
+ return Promise.resolve(response.data);
442
+ } else if (response.status < 500) {
443
+ // Next, if response is anything but 5xx, don't retry, return a custom error
444
+ return Promise.reject(
445
+ this.customErrorForResponse(
446
+ response,
447
+ response.data?.message,
448
+ requestOptions.data,
449
+ ),
450
+ );
451
+ } else {
452
+ // Retry all other HTTP errors (HTTPStatus > 500)
453
+ // This will get caught by the catch block below
454
+ throw this.customErrorForResponse(
455
+ response,
456
+ response.data?.message,
457
+ requestOptions.data,
458
+ );
459
+ }
460
+ } catch (error: any) {
461
+ // This block handles retries for HTTPStatus > 500 and network layer issues like connection timeouts
462
+ if (!wasAborted) {
463
+ this.setNodeHealthcheck(node, UNHEALTHY);
464
+ }
465
+ lastException = error;
466
+ this.logger.warn(
467
+ `Request #${requestNumber}: Request to Node ${
468
+ node.index
469
+ } failed due to "${error?.code ?? ""} ${error.message}${
470
+ error.response == null
471
+ ? ""
472
+ : " - " + JSON.stringify(error.response?.data)
473
+ }"`,
474
+ );
475
+
476
+ if (wasAborted) {
477
+ return Promise.reject(new Error("Request aborted by caller."));
478
+ }
479
+
480
+ if (isStreamingRequest) {
481
+ this.invokeOnErrorCallback(error, streamConfig);
482
+ }
483
+
484
+ if (numTries < this.numRetriesPerRequest + 1) {
485
+ this.logger.warn(
486
+ `Request #${requestNumber}: Sleeping for ${this.retryIntervalSeconds}s and then retrying request...`,
487
+ );
488
+ } else {
489
+ this.logger.debug(
490
+ `Request #${requestNumber}: No retries left. Raising last error`,
491
+ );
492
+ return Promise.reject(lastException);
493
+ }
494
+ await this.timer(this.retryIntervalSeconds);
495
+ } finally {
496
+ if (abortSignal && abortListener) {
497
+ abortSignal.removeEventListener("abort", abortListener);
498
+ }
499
+ }
500
+ }
501
+
502
+ this.logger.debug(
503
+ `Request #${requestNumber}: No retries left. Raising last error`,
504
+ );
505
+ return Promise.reject(lastException);
506
+ }
507
+
508
+ private processStreamingLine(line: string): {
509
+ conversation_id: string;
510
+ message: string;
511
+ } | null {
512
+ if (!line.trim() || line === "data: [DONE]") {
513
+ return null;
514
+ }
515
+
516
+ // Handle SSE format (data: {...})
517
+ if (line.startsWith("data: ")) {
518
+ return this.processDataLine(line.slice(6).trim());
519
+ }
520
+
521
+ // Try parsing as JSON if it starts with a brace
522
+ if (line.trim().startsWith("{")) {
523
+ try {
524
+ const jsonData = JSON.parse(line.trim());
525
+ if (jsonData && typeof jsonData === "object") {
526
+ if (!jsonData.conversation_id) {
527
+ jsonData.conversation_id = "unknown";
528
+ }
529
+ if (!jsonData.message && jsonData.message !== "") {
530
+ jsonData.message = "";
531
+ }
532
+ return jsonData;
533
+ }
534
+ return {
535
+ conversation_id: "unknown",
536
+ message: JSON.stringify(jsonData),
537
+ };
538
+ } catch {
539
+ return {
540
+ conversation_id: "unknown",
541
+ message: line.trim(),
542
+ };
543
+ }
544
+ }
545
+
546
+ return {
547
+ conversation_id: "unknown",
548
+ message: line.trim(),
549
+ };
550
+ }
551
+
552
+ private processDataLine(dataContent: string): {
553
+ conversation_id: string;
554
+ message: string;
555
+ } | null {
556
+ if (!dataContent) {
557
+ return null;
558
+ }
559
+
560
+ if (dataContent.startsWith("{")) {
561
+ try {
562
+ const jsonData = JSON.parse(dataContent);
563
+ // Ensure the required fields exist
564
+ if (jsonData && typeof jsonData === "object") {
565
+ if (!jsonData.conversation_id) {
566
+ jsonData.conversation_id = "unknown";
567
+ }
568
+ if (!jsonData.message && jsonData.message !== "") {
569
+ jsonData.message = "";
570
+ }
571
+ return jsonData;
572
+ }
573
+ return {
574
+ conversation_id: "unknown",
575
+ message: JSON.stringify(jsonData),
576
+ };
577
+ } catch {
578
+ // Not valid JSON, use as plain text
579
+ return {
580
+ conversation_id: "unknown",
581
+ message: dataContent,
582
+ };
583
+ }
584
+ }
585
+
586
+ // For plain text
587
+ return {
588
+ conversation_id: "unknown",
589
+ message: dataContent,
590
+ };
591
+ }
592
+
593
+ private async handleStreamingResponse<T>(
594
+ response: AxiosResponse,
595
+ streamConfig:
596
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
597
+ | undefined,
598
+ ): Promise<T> {
599
+ this.logger.debug(
600
+ `Handling streaming response. Environment: ${isNodeJSEnvironment ? "Node.js" : "Browser"}`,
601
+ );
602
+
603
+ if (isNodeJSEnvironment && response.data) {
604
+ return this.handleNodeStreaming<T>(response, streamConfig);
605
+ }
606
+
607
+ if (!isNodeJSEnvironment) {
608
+ return this.handleBrowserStreaming<T>(response, streamConfig);
609
+ }
610
+
611
+ this.logger.debug("Processing non-streaming response");
612
+ this.invokeOnCompleteCallback(response.data, streamConfig);
613
+ return Promise.resolve(response.data as T);
614
+ }
615
+
616
+ private handleNodeStreaming<T>(
617
+ response: AxiosResponse,
618
+ streamConfig?: StreamConfig<T extends DocumentSchema ? T : DocumentSchema>,
619
+ ): Promise<T> {
620
+ this.logger.debug("Processing Node.js stream");
621
+ return new Promise<T>((resolve, reject) => {
622
+ const stream = response.data;
623
+ const allChunks:
624
+ | [
625
+ ...MessageChunk[],
626
+ SearchResponse<T extends DocumentSchema ? T : DocumentSchema>,
627
+ ]
628
+ | [] = [];
629
+ let buffer = "";
630
+
631
+ stream.on("data", (chunk) => {
632
+ try {
633
+ const data = chunk.toString();
634
+ buffer += data;
635
+
636
+ const lines = buffer.split("\n");
637
+ buffer = lines.pop() ?? "";
638
+
639
+ this.processStreamLines(lines, allChunks, streamConfig);
640
+ } catch (error) {
641
+ reject(error);
642
+ }
643
+ });
644
+
645
+ stream.on("end", () => {
646
+ if (buffer.trim().length > 0) {
647
+ const lines = buffer.split("\n");
648
+ this.processStreamLines(lines, allChunks, streamConfig);
649
+ }
650
+
651
+ this.finalizeStreamResult<T>(
652
+ allChunks,
653
+ resolve,
654
+ response,
655
+ streamConfig,
656
+ );
657
+ });
658
+
659
+ stream.on("error", (error: unknown) => {
660
+ this.logger.error(`Stream error: ${error}`);
661
+ this.invokeOnErrorCallback(error, streamConfig);
662
+ reject(error);
663
+ });
664
+ });
665
+ }
666
+
667
+ private handleBrowserStreaming<T>(
668
+ response: AxiosResponse,
669
+ streamConfig?: StreamConfig<T extends DocumentSchema ? T : DocumentSchema>,
670
+ ): Promise<T> {
671
+ this.logger.debug("Processing browser stream");
672
+
673
+ return new Promise<T>(async (resolve, reject) => {
674
+ try {
675
+ if (response.data && typeof response.data.getReader === "function") {
676
+ return this.handleBrowserReadableStream<T>(
677
+ response.data,
678
+ resolve,
679
+ reject,
680
+ response,
681
+ streamConfig,
682
+ );
683
+ }
684
+
685
+ if (typeof response.data === "string") {
686
+ return this.handleBrowserStringResponse<T>(
687
+ response.data,
688
+ resolve,
689
+ response,
690
+ streamConfig,
691
+ );
692
+ }
693
+
694
+ if (typeof response.data === "object" && response.data !== null) {
695
+ this.logger.debug("No stream found, but data object is available");
696
+ this.invokeOnCompleteCallback(response.data, streamConfig);
697
+ return resolve(response.data as T);
698
+ }
699
+
700
+ this.logger.error("No usable data found in response");
701
+ return reject(new Error("No usable data found in response"));
702
+ } catch (error) {
703
+ this.logger.error(`Error processing streaming response: ${error}`);
704
+ this.invokeOnErrorCallback(error, streamConfig);
705
+ reject(error);
706
+ }
707
+ });
708
+ }
709
+
710
+ private async handleBrowserReadableStream<T>(
711
+ stream: any,
712
+ resolve: (value: T) => void,
713
+ reject: (reason?: any) => void,
714
+ response: AxiosResponse,
715
+ streamConfig:
716
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
717
+ | undefined,
718
+ ): Promise<void> {
719
+ this.logger.debug("Found ReadableStream in response.data");
720
+ const reader = stream.getReader();
721
+ const allChunks:
722
+ | [
723
+ ...MessageChunk[],
724
+ SearchResponse<T extends DocumentSchema ? T : DocumentSchema>,
725
+ ]
726
+ | [] = [];
727
+ let buffer = "";
728
+
729
+ try {
730
+ while (true) {
731
+ const { done, value } = await reader.read();
732
+
733
+ if (done) {
734
+ this.logger.debug("Stream reading complete");
735
+ if (buffer.trim()) {
736
+ const lines = buffer.split("\n");
737
+ this.processStreamLines(lines, allChunks, streamConfig);
738
+ }
739
+ break;
740
+ }
741
+
742
+ const chunk = new TextDecoder().decode(value);
743
+ this.logger.debug(`Received chunk: ${chunk.length} bytes`);
744
+
745
+ buffer += chunk;
746
+ const lines = buffer.split("\n");
747
+ buffer = lines.pop() || "";
748
+
749
+ this.processStreamLines(lines, allChunks, streamConfig);
750
+ }
751
+
752
+ this.finalizeStreamResult<T>(allChunks, resolve, response, streamConfig);
753
+ } catch (error) {
754
+ this.logger.error(`Stream error: ${error}`);
755
+ this.invokeOnErrorCallback(error, streamConfig);
756
+ reject(error);
757
+ }
758
+ }
759
+
760
+ private handleBrowserStringResponse<T>(
761
+ data: string,
762
+ resolve: (value: T) => void,
763
+ response: AxiosResponse,
764
+ streamConfig:
765
+ | StreamConfig<T extends DocumentSchema ? T : DocumentSchema>
766
+ | undefined,
767
+ ): void {
768
+ this.logger.debug("Processing text response as stream data");
769
+ const allChunks:
770
+ | [
771
+ ...MessageChunk[],
772
+ SearchResponse<T extends DocumentSchema ? T : DocumentSchema>,
773
+ ]
774
+ | [] = [];
775
+
776
+ const lines = data.split("\n");
777
+ this.processStreamLines(lines, allChunks, streamConfig);
778
+
779
+ if (allChunks.length > 0) {
780
+ const finalResult =
781
+ this.combineStreamingChunks<
782
+ T extends DocumentSchema ? T : DocumentSchema
783
+ >(allChunks);
784
+ this.invokeOnCompleteCallback(finalResult, streamConfig);
785
+ resolve(finalResult as unknown as T);
786
+ } else {
787
+ // If no chunks were processed, use the original response
788
+ this.logger.debug("No chunks processed, returning original API response");
789
+ this.invokeOnCompleteCallback(response.data, streamConfig);
790
+ resolve(response.data as T);
791
+ }
792
+ }
793
+
794
+ private processStreamLines<T extends DocumentSchema>(
795
+ lines: string[],
796
+ allChunks: [...MessageChunk[], SearchResponse<T>] | [],
797
+ streamConfig: StreamConfig<T> | undefined,
798
+ ): void {
799
+ for (const line of lines) {
800
+ if (line.trim() && line !== "data: [DONE]") {
801
+ const processed = this.processStreamingLine(line);
802
+ if (processed !== null) {
803
+ this.invokeOnChunkCallback(processed, streamConfig);
804
+ (allChunks as MessageChunk[]).push(processed);
805
+ }
806
+ }
807
+ }
808
+ }
809
+
810
+ private finalizeStreamResult<T>(
811
+ allChunks:
812
+ | [
813
+ ...MessageChunk[],
814
+ SearchResponse<T extends DocumentSchema ? T : DocumentSchema>,
815
+ ]
816
+ | [],
817
+ resolve: (value: T) => void,
818
+ response: AxiosResponse,
819
+ streamConfig?: StreamConfig<T extends DocumentSchema ? T : DocumentSchema>,
820
+ ): void {
821
+ if (allChunks.length > 0) {
822
+ const finalResult = this.combineStreamingChunks(allChunks);
823
+ this.logger.debug("Stream processing complete");
824
+ this.invokeOnCompleteCallback(finalResult, streamConfig);
825
+ resolve(finalResult as unknown as T);
826
+ } else {
827
+ this.logger.debug("No chunks processed, returning original API response");
828
+ this.invokeOnCompleteCallback(response.data, streamConfig);
829
+ resolve(response.data as T);
830
+ }
831
+ }
832
+
833
+ /**
834
+ * Combines multiple streaming chunks into a single coherent result
835
+ * This is critical for ensuring we return the complete data rather than just the last chunk
836
+ */
837
+ private combineStreamingChunks<T extends DocumentSchema>(
838
+ chunks: [...MessageChunk[], SearchResponse<T>] | [],
839
+ ): SearchResponse<T> {
840
+ if (chunks.length === 0) return {} as SearchResponse<T>;
841
+ if (chunks.length === 1) return chunks[0] as unknown as SearchResponse<T>;
842
+
843
+ // For conversation streams with message chunks
844
+ const messagesChunks = this.getMessageChunks(
845
+ chunks as [...MessageChunk[], SearchResponse<T>],
846
+ );
847
+ if (messagesChunks.length > 0) {
848
+ return this.combineMessageChunks(
849
+ chunks as [...MessageChunk[], SearchResponse<T>],
850
+ messagesChunks,
851
+ );
852
+ }
853
+
854
+ // For regular search responses
855
+ const lastChunk = chunks[chunks.length - 1];
856
+ if (!this.isCompleteSearchResponse(lastChunk)) {
857
+ throw new Error("Last chunk is not a complete search response");
858
+ }
859
+
860
+ return lastChunk;
861
+ }
862
+
863
+ private getMessageChunks<T extends DocumentSchema>(
864
+ chunks: [...MessageChunk[], SearchResponse<T>],
865
+ ): MessageChunk[] {
866
+ return chunks.filter(this.isChunkMessage);
867
+ }
868
+
869
+ private isChunkMessage(chunk: unknown): chunk is MessageChunk {
870
+ return (
871
+ typeof chunk === "object" &&
872
+ chunk !== null &&
873
+ "message" in chunk &&
874
+ "conversation_id" in chunk
875
+ );
876
+ }
877
+
878
+ private combineMessageChunks<T extends DocumentSchema>(
879
+ chunks: [...MessageChunk[], SearchResponse<T>],
880
+ messagesChunks: MessageChunk[],
881
+ ): SearchResponse<T> {
882
+ this.logger.debug(
883
+ `Found ${messagesChunks.length} message chunks to combine`,
884
+ );
885
+
886
+ const lastChunk = chunks[chunks.length - 1];
887
+ if (this.isCompleteSearchResponse(lastChunk)) {
888
+ return lastChunk;
889
+ }
890
+
891
+ const metadataChunk = chunks.find(this.isCompleteSearchResponse);
892
+
893
+ if (!metadataChunk) {
894
+ throw new Error("No metadata chunk found");
895
+ }
896
+
897
+ return metadataChunk;
898
+ }
899
+
900
+ private isCompleteSearchResponse<T extends DocumentSchema>(
901
+ chunk: MessageChunk | SearchResponse<T>,
902
+ ): chunk is SearchResponse<T> {
903
+ if (
904
+ typeof chunk === "object" &&
905
+ chunk !== null &&
906
+ Object.keys(chunk as object).length > 0
907
+ ) {
908
+ return (
909
+ "results" in (chunk as object) ||
910
+ "found" in (chunk as object) ||
911
+ "hits" in (chunk as object) ||
912
+ "page" in (chunk as object) ||
913
+ "search_time_ms" in (chunk as object)
914
+ );
915
+ }
916
+ return false;
917
+ }
918
+
919
+ // Attempts to find the next healthy node, looping through the list of nodes once.
920
+ // But if no healthy nodes are found, it will just return the next node, even if it's unhealthy
921
+ // so we can try the request for good measure, in case that node has become healthy since
922
+ getNextNode(requestNumber = 0): Node {
923
+ // Check if nearestNode is set and is healthy, if so return it
924
+ if (this.nearestNode != null) {
925
+ this.logger.debug(
926
+ `Request #${requestNumber}: Nodes Health: Node ${
927
+ this.nearestNode.index
928
+ } is ${this.nearestNode.isHealthy === true ? "Healthy" : "Unhealthy"}`,
929
+ );
930
+ if (
931
+ this.nearestNode.isHealthy === true ||
932
+ this.nodeDueForHealthcheck(this.nearestNode, requestNumber)
933
+ ) {
934
+ this.logger.debug(
935
+ `Request #${requestNumber}: Updated current node to Node ${this.nearestNode.index}`,
936
+ );
937
+ return this.nearestNode;
938
+ }
939
+ this.logger.debug(
940
+ `Request #${requestNumber}: Falling back to individual nodes`,
941
+ );
942
+ }
943
+
944
+ // Fallback to nodes as usual
945
+ this.logger.debug(
946
+ `Request #${requestNumber}: Nodes Health: ${this.nodes
947
+ .map(
948
+ (node) =>
949
+ `Node ${node.index} is ${
950
+ node.isHealthy === true ? "Healthy" : "Unhealthy"
951
+ }`,
952
+ )
953
+ .join(" || ")}`,
954
+ );
955
+ let candidateNode: Node = this.nodes[0];
956
+ for (let i = 0; i <= this.nodes.length; i++) {
957
+ this.currentNodeIndex = (this.currentNodeIndex + 1) % this.nodes.length;
958
+ candidateNode = this.nodes[this.currentNodeIndex];
959
+ if (
960
+ candidateNode.isHealthy === true ||
961
+ this.nodeDueForHealthcheck(candidateNode, requestNumber)
962
+ ) {
963
+ this.logger.debug(
964
+ `Request #${requestNumber}: Updated current node to Node ${candidateNode.index}`,
965
+ );
966
+ return candidateNode;
967
+ }
968
+ }
969
+
970
+ // None of the nodes are marked healthy, but some of them could have become healthy since last health check.
971
+ // So we will just return the next node.
972
+ this.logger.debug(
973
+ `Request #${requestNumber}: No healthy nodes were found. Returning the next node, Node ${candidateNode.index}`,
974
+ );
975
+ return candidateNode;
976
+ }
977
+
978
+ nodeDueForHealthcheck(node, requestNumber = 0): boolean {
979
+ const isDueForHealthcheck =
980
+ Date.now() - node.lastAccessTimestamp >
981
+ this.healthcheckIntervalSeconds * 1000;
982
+ if (isDueForHealthcheck) {
983
+ this.logger.debug(
984
+ `Request #${requestNumber}: Node ${node.index} has exceeded healtcheckIntervalSeconds of ${this.healthcheckIntervalSeconds}. Adding it back into rotation.`,
985
+ );
986
+ }
987
+ return isDueForHealthcheck;
988
+ }
989
+
990
+ initializeMetadataForNodes(): void {
991
+ if (this.nearestNode != null) {
992
+ this.nearestNode.index = "nearestNode";
993
+ this.setNodeHealthcheck(this.nearestNode, HEALTHY);
994
+ }
995
+
996
+ this.nodes.forEach((node, i) => {
997
+ node.index = i;
998
+ this.setNodeHealthcheck(node, HEALTHY);
999
+ });
1000
+ }
1001
+
1002
+ setNodeHealthcheck(node, isHealthy): void {
1003
+ node.isHealthy = isHealthy;
1004
+ node.lastAccessTimestamp = Date.now();
1005
+ }
1006
+
1007
+ uriFor(endpoint: string, node): string {
1008
+ if (node.url != null) {
1009
+ return `${node.url}${endpoint}`;
1010
+ }
1011
+ return `${node.protocol}://${node.host}:${node.port}${node.path}${endpoint}`;
1012
+ }
1013
+
1014
+ defaultHeaders(): any {
1015
+ const defaultHeaders = {};
1016
+ if (!this.sendApiKeyAsQueryParam) {
1017
+ defaultHeaders[APIKEYHEADERNAME] = this.apiKey;
1018
+ }
1019
+ defaultHeaders["Content-Type"] = "application/json";
1020
+ return defaultHeaders;
1021
+ }
1022
+
1023
+ async timer(seconds): Promise<void> {
1024
+ return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
1025
+ }
1026
+
1027
+ customErrorForResponse(
1028
+ response: AxiosResponse,
1029
+ messageFromServer: string,
1030
+ httpBody?: string,
1031
+ ): TypesenseError {
1032
+ let errorMessage = `Request failed with HTTP code ${response.status}`;
1033
+ if (
1034
+ typeof messageFromServer === "string" &&
1035
+ messageFromServer.trim() !== ""
1036
+ ) {
1037
+ errorMessage += ` | Server said: ${messageFromServer}`;
1038
+ }
1039
+
1040
+ let error = new TypesenseError(errorMessage, httpBody, response.status);
1041
+
1042
+ if (response.status === 400) {
1043
+ error = new RequestMalformed(errorMessage, httpBody, response.status);
1044
+ } else if (response.status === 401) {
1045
+ error = new RequestUnauthorized(errorMessage, httpBody, response.status);
1046
+ } else if (response.status === 404) {
1047
+ error = new ObjectNotFound(errorMessage, httpBody, response.status);
1048
+ } else if (response.status === 409) {
1049
+ error = new ObjectAlreadyExists(errorMessage, httpBody, response.status);
1050
+ } else if (response.status === 422) {
1051
+ error = new ObjectUnprocessable(errorMessage, httpBody, response.status);
1052
+ } else if (response.status >= 500 && response.status <= 599) {
1053
+ error = new ServerError(errorMessage, httpBody, response.status);
1054
+ } else {
1055
+ error = new HTTPError(errorMessage, httpBody, response.status);
1056
+ }
1057
+
1058
+ return error;
1059
+ }
1060
+
1061
+ private invokeOnChunkCallback<T extends DocumentSchema>(
1062
+ data: {
1063
+ conversation_id: string;
1064
+ message: string;
1065
+ },
1066
+ streamConfig: StreamConfig<T> | undefined,
1067
+ ): void {
1068
+ if (streamConfig?.onChunk) {
1069
+ try {
1070
+ streamConfig.onChunk(data);
1071
+ } catch (error) {
1072
+ this.logger.warn(`Error in onChunk callback: ${error}`);
1073
+ }
1074
+ }
1075
+ }
1076
+
1077
+ private invokeOnCompleteCallback<T extends DocumentSchema>(
1078
+ data: SearchResponse<T>,
1079
+ streamConfig: StreamConfig<T> | undefined,
1080
+ ): void {
1081
+ if (streamConfig?.onComplete) {
1082
+ try {
1083
+ streamConfig.onComplete(data);
1084
+ } catch (error) {
1085
+ this.logger.warn(`Error in onComplete callback: ${error}`);
1086
+ }
1087
+ }
1088
+ }
1089
+
1090
+ private invokeOnErrorCallback<T extends DocumentSchema>(
1091
+ error: unknown,
1092
+ streamConfig: StreamConfig<T> | undefined,
1093
+ ): void {
1094
+ if (streamConfig?.onError) {
1095
+ const errorObj = toErrorWithMessage(error);
1096
+ try {
1097
+ streamConfig.onError(errorObj);
1098
+ } catch (callbackError) {
1099
+ this.logger.warn(`Error in onError callback: ${callbackError}`);
1100
+ }
1101
+ }
1102
+ }
1103
+ }