@roarkanalytics/sdk 0.1.0-alpha.1 → 0.1.0-alpha.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (347) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE +1 -1
  3. package/README.md +48 -85
  4. package/_shims/MultipartBody.d.ts +9 -0
  5. package/_shims/MultipartBody.d.ts.map +1 -0
  6. package/_shims/MultipartBody.js +16 -0
  7. package/_shims/MultipartBody.js.map +1 -0
  8. package/_shims/MultipartBody.mjs +12 -0
  9. package/_shims/MultipartBody.mjs.map +1 -0
  10. package/_shims/README.md +46 -0
  11. package/_shims/auto/runtime-bun.d.ts +5 -0
  12. package/_shims/auto/runtime-bun.d.ts.map +1 -0
  13. package/{internal/utils.js → _shims/auto/runtime-bun.js} +5 -8
  14. package/_shims/auto/runtime-bun.js.map +1 -0
  15. package/_shims/auto/runtime-bun.mjs +2 -0
  16. package/_shims/auto/runtime-bun.mjs.map +1 -0
  17. package/_shims/auto/runtime-node.d.ts +5 -0
  18. package/_shims/auto/runtime-node.d.ts.map +1 -0
  19. package/_shims/auto/runtime-node.js +21 -0
  20. package/_shims/auto/runtime-node.js.map +1 -0
  21. package/_shims/auto/runtime-node.mjs +2 -0
  22. package/_shims/auto/runtime-node.mjs.map +1 -0
  23. package/_shims/auto/runtime.d.ts +5 -0
  24. package/_shims/auto/runtime.d.ts.map +1 -0
  25. package/_shims/auto/runtime.js +21 -0
  26. package/_shims/auto/runtime.js.map +1 -0
  27. package/_shims/auto/runtime.mjs +2 -0
  28. package/_shims/auto/runtime.mjs.map +1 -0
  29. package/_shims/auto/types-node.d.ts +5 -0
  30. package/_shims/auto/types-node.d.ts.map +1 -0
  31. package/_shims/auto/types-node.js +21 -0
  32. package/_shims/auto/types-node.js.map +1 -0
  33. package/_shims/auto/types-node.mjs +2 -0
  34. package/_shims/auto/types-node.mjs.map +1 -0
  35. package/_shims/auto/types.d.ts +101 -0
  36. package/_shims/auto/types.js +3 -0
  37. package/_shims/auto/types.mjs +3 -0
  38. package/_shims/bun-runtime.d.ts +6 -0
  39. package/_shims/bun-runtime.d.ts.map +1 -0
  40. package/_shims/bun-runtime.js +14 -0
  41. package/_shims/bun-runtime.js.map +1 -0
  42. package/_shims/bun-runtime.mjs +10 -0
  43. package/_shims/bun-runtime.mjs.map +1 -0
  44. package/_shims/index.d.ts +81 -0
  45. package/_shims/index.js +13 -0
  46. package/_shims/index.mjs +7 -0
  47. package/_shims/manual-types.d.ts +12 -0
  48. package/_shims/manual-types.js +3 -0
  49. package/_shims/manual-types.mjs +3 -0
  50. package/_shims/node-runtime.d.ts +3 -0
  51. package/_shims/node-runtime.d.ts.map +1 -0
  52. package/_shims/node-runtime.js +89 -0
  53. package/_shims/node-runtime.js.map +1 -0
  54. package/_shims/node-runtime.mjs +56 -0
  55. package/_shims/node-runtime.mjs.map +1 -0
  56. package/_shims/node-types.d.ts +42 -0
  57. package/_shims/node-types.js +3 -0
  58. package/_shims/node-types.mjs +3 -0
  59. package/_shims/registry.d.ts +37 -0
  60. package/_shims/registry.d.ts.map +1 -0
  61. package/_shims/registry.js +41 -0
  62. package/_shims/registry.js.map +1 -0
  63. package/_shims/registry.mjs +37 -0
  64. package/_shims/registry.mjs.map +1 -0
  65. package/_shims/web-runtime.d.ts +5 -0
  66. package/_shims/web-runtime.d.ts.map +1 -0
  67. package/_shims/web-runtime.js +78 -0
  68. package/_shims/web-runtime.js.map +1 -0
  69. package/_shims/web-runtime.mjs +71 -0
  70. package/_shims/web-runtime.mjs.map +1 -0
  71. package/_shims/web-types.d.ts +83 -0
  72. package/_shims/web-types.js +3 -0
  73. package/_shims/web-types.mjs +3 -0
  74. package/core.d.ts +241 -0
  75. package/core.d.ts.map +1 -0
  76. package/core.js +908 -0
  77. package/core.js.map +1 -0
  78. package/core.mjs +876 -0
  79. package/core.mjs.map +1 -0
  80. package/error.d.ts +3 -3
  81. package/error.d.ts.map +1 -1
  82. package/error.js +6 -6
  83. package/error.js.map +1 -1
  84. package/error.mjs +3 -3
  85. package/error.mjs.map +1 -1
  86. package/index.d.mts +109 -5
  87. package/index.d.ts +109 -5
  88. package/index.d.ts.map +1 -1
  89. package/index.js +103 -11
  90. package/index.js.map +1 -1
  91. package/index.mjs +77 -5
  92. package/index.mjs.map +1 -1
  93. package/package.json +66 -82
  94. package/resource.d.ts +3 -3
  95. package/resource.d.ts.map +1 -1
  96. package/resource.js.map +1 -1
  97. package/resource.mjs.map +1 -1
  98. package/resources/calls.d.ts +2 -3
  99. package/resources/calls.d.ts.map +1 -1
  100. package/resources/calls.js.map +1 -1
  101. package/resources/calls.mjs.map +1 -1
  102. package/resources/index.d.ts.map +1 -1
  103. package/shims/node.d.ts +30 -0
  104. package/shims/node.d.ts.map +1 -0
  105. package/shims/node.js +31 -0
  106. package/shims/node.js.map +1 -0
  107. package/shims/node.mjs +5 -0
  108. package/shims/node.mjs.map +1 -0
  109. package/shims/web.d.ts +26 -0
  110. package/shims/web.d.ts.map +1 -0
  111. package/shims/web.js +31 -0
  112. package/shims/web.js.map +1 -0
  113. package/shims/web.mjs +5 -0
  114. package/shims/web.mjs.map +1 -0
  115. package/src/_shims/MultipartBody.ts +9 -0
  116. package/src/_shims/README.md +46 -0
  117. package/src/_shims/auto/runtime-bun.ts +4 -0
  118. package/src/_shims/auto/runtime-node.ts +4 -0
  119. package/src/_shims/auto/runtime.ts +4 -0
  120. package/src/_shims/auto/types-node.ts +4 -0
  121. package/src/_shims/auto/types.d.ts +101 -0
  122. package/src/_shims/auto/types.js +3 -0
  123. package/src/_shims/auto/types.mjs +3 -0
  124. package/src/_shims/bun-runtime.ts +14 -0
  125. package/src/_shims/index.d.ts +81 -0
  126. package/src/_shims/index.js +13 -0
  127. package/src/_shims/index.mjs +7 -0
  128. package/src/_shims/manual-types.d.ts +12 -0
  129. package/src/_shims/manual-types.js +3 -0
  130. package/src/_shims/manual-types.mjs +3 -0
  131. package/src/_shims/node-runtime.ts +81 -0
  132. package/src/_shims/node-types.d.ts +42 -0
  133. package/src/_shims/node-types.js +3 -0
  134. package/src/_shims/node-types.mjs +3 -0
  135. package/src/_shims/registry.ts +67 -0
  136. package/src/_shims/web-runtime.ts +103 -0
  137. package/src/_shims/web-types.d.ts +83 -0
  138. package/src/_shims/web-types.js +3 -0
  139. package/src/_shims/web-types.mjs +3 -0
  140. package/src/core.ts +1200 -0
  141. package/src/error.ts +3 -3
  142. package/src/index.ts +172 -5
  143. package/src/resource.ts +3 -3
  144. package/src/resources/calls.ts +2 -3
  145. package/src/shims/node.ts +50 -0
  146. package/src/shims/web.ts +50 -0
  147. package/src/tsconfig.json +2 -2
  148. package/src/uploads.ts +255 -1
  149. package/src/version.ts +1 -1
  150. package/uploads.d.ts +74 -1
  151. package/uploads.d.ts.map +1 -1
  152. package/uploads.js +168 -3
  153. package/uploads.js.map +1 -1
  154. package/uploads.mjs +157 -1
  155. package/uploads.mjs.map +1 -1
  156. package/version.d.ts +1 -1
  157. package/version.js +1 -1
  158. package/version.mjs +1 -1
  159. package/api-promise.d.mts +0 -47
  160. package/api-promise.d.mts.map +0 -1
  161. package/api-promise.d.ts +0 -47
  162. package/api-promise.d.ts.map +0 -1
  163. package/api-promise.js +0 -84
  164. package/api-promise.js.map +0 -1
  165. package/api-promise.mjs +0 -80
  166. package/api-promise.mjs.map +0 -1
  167. package/client.d.mts +0 -186
  168. package/client.d.mts.map +0 -1
  169. package/client.d.ts +0 -186
  170. package/client.d.ts.map +0 -1
  171. package/client.js +0 -449
  172. package/client.js.map +0 -1
  173. package/client.mjs +0 -422
  174. package/client.mjs.map +0 -1
  175. package/error.d.mts +0 -47
  176. package/error.d.mts.map +0 -1
  177. package/index.d.mts.map +0 -1
  178. package/internal/builtin-types.d.mts +0 -65
  179. package/internal/builtin-types.d.mts.map +0 -1
  180. package/internal/builtin-types.d.ts +0 -65
  181. package/internal/builtin-types.d.ts.map +0 -1
  182. package/internal/builtin-types.js +0 -4
  183. package/internal/builtin-types.js.map +0 -1
  184. package/internal/builtin-types.mjs +0 -3
  185. package/internal/builtin-types.mjs.map +0 -1
  186. package/internal/detect-platform.d.mts +0 -15
  187. package/internal/detect-platform.d.mts.map +0 -1
  188. package/internal/detect-platform.d.ts +0 -15
  189. package/internal/detect-platform.d.ts.map +0 -1
  190. package/internal/detect-platform.js +0 -162
  191. package/internal/detect-platform.js.map +0 -1
  192. package/internal/detect-platform.mjs +0 -157
  193. package/internal/detect-platform.mjs.map +0 -1
  194. package/internal/errors.d.mts +0 -3
  195. package/internal/errors.d.mts.map +0 -1
  196. package/internal/errors.d.ts +0 -3
  197. package/internal/errors.d.ts.map +0 -1
  198. package/internal/errors.js +0 -26
  199. package/internal/errors.js.map +0 -1
  200. package/internal/errors.mjs +0 -21
  201. package/internal/errors.mjs.map +0 -1
  202. package/internal/headers.d.mts +0 -21
  203. package/internal/headers.d.mts.map +0 -1
  204. package/internal/headers.d.ts +0 -21
  205. package/internal/headers.d.ts.map +0 -1
  206. package/internal/headers.js +0 -77
  207. package/internal/headers.js.map +0 -1
  208. package/internal/headers.mjs +0 -72
  209. package/internal/headers.mjs.map +0 -1
  210. package/internal/parse.d.mts +0 -10
  211. package/internal/parse.d.mts.map +0 -1
  212. package/internal/parse.d.ts +0 -10
  213. package/internal/parse.d.ts.map +0 -1
  214. package/internal/parse.js +0 -28
  215. package/internal/parse.js.map +0 -1
  216. package/internal/parse.mjs +0 -24
  217. package/internal/parse.mjs.map +0 -1
  218. package/internal/polyfill/crypto.node.d.ts +0 -10
  219. package/internal/polyfill/crypto.node.js +0 -11
  220. package/internal/polyfill/crypto.node.mjs +0 -2
  221. package/internal/polyfill/file.node.d.ts +0 -9
  222. package/internal/polyfill/file.node.js +0 -17
  223. package/internal/polyfill/file.node.mjs +0 -9
  224. package/internal/request-options.d.mts +0 -34
  225. package/internal/request-options.d.mts.map +0 -1
  226. package/internal/request-options.d.ts +0 -34
  227. package/internal/request-options.d.ts.map +0 -1
  228. package/internal/request-options.js +0 -39
  229. package/internal/request-options.js.map +0 -1
  230. package/internal/request-options.mjs +0 -34
  231. package/internal/request-options.mjs.map +0 -1
  232. package/internal/shim-types.d.mts +0 -28
  233. package/internal/shim-types.d.ts +0 -28
  234. package/internal/shims.d.mts +0 -61
  235. package/internal/shims.d.mts.map +0 -1
  236. package/internal/shims.d.ts +0 -61
  237. package/internal/shims.d.ts.map +0 -1
  238. package/internal/shims.js +0 -101
  239. package/internal/shims.js.map +0 -1
  240. package/internal/shims.mjs +0 -92
  241. package/internal/shims.mjs.map +0 -1
  242. package/internal/types.d.mts +0 -68
  243. package/internal/types.d.mts.map +0 -1
  244. package/internal/types.d.ts +0 -68
  245. package/internal/types.d.ts.map +0 -1
  246. package/internal/types.js +0 -4
  247. package/internal/types.js.map +0 -1
  248. package/internal/types.mjs +0 -3
  249. package/internal/types.mjs.map +0 -1
  250. package/internal/uploads.d.mts +0 -73
  251. package/internal/uploads.d.mts.map +0 -1
  252. package/internal/uploads.d.ts +0 -73
  253. package/internal/uploads.d.ts.map +0 -1
  254. package/internal/uploads.js +0 -208
  255. package/internal/uploads.js.map +0 -1
  256. package/internal/uploads.mjs +0 -200
  257. package/internal/uploads.mjs.map +0 -1
  258. package/internal/utils/base64.d.mts +0 -3
  259. package/internal/utils/base64.d.mts.map +0 -1
  260. package/internal/utils/base64.d.ts +0 -3
  261. package/internal/utils/base64.d.ts.map +0 -1
  262. package/internal/utils/base64.js +0 -33
  263. package/internal/utils/base64.js.map +0 -1
  264. package/internal/utils/base64.mjs +0 -28
  265. package/internal/utils/base64.mjs.map +0 -1
  266. package/internal/utils/env.d.mts +0 -9
  267. package/internal/utils/env.d.mts.map +0 -1
  268. package/internal/utils/env.d.ts +0 -9
  269. package/internal/utils/env.d.ts.map +0 -1
  270. package/internal/utils/env.js +0 -22
  271. package/internal/utils/env.js.map +0 -1
  272. package/internal/utils/env.mjs +0 -18
  273. package/internal/utils/env.mjs.map +0 -1
  274. package/internal/utils/log.d.mts +0 -4
  275. package/internal/utils/log.d.mts.map +0 -1
  276. package/internal/utils/log.d.ts +0 -4
  277. package/internal/utils/log.d.ts.map +0 -1
  278. package/internal/utils/log.js +0 -47
  279. package/internal/utils/log.js.map +0 -1
  280. package/internal/utils/log.mjs +0 -43
  281. package/internal/utils/log.mjs.map +0 -1
  282. package/internal/utils/sleep.d.mts +0 -2
  283. package/internal/utils/sleep.d.mts.map +0 -1
  284. package/internal/utils/sleep.d.ts +0 -2
  285. package/internal/utils/sleep.d.ts.map +0 -1
  286. package/internal/utils/sleep.js +0 -7
  287. package/internal/utils/sleep.js.map +0 -1
  288. package/internal/utils/sleep.mjs +0 -3
  289. package/internal/utils/sleep.mjs.map +0 -1
  290. package/internal/utils/uuid.d.mts +0 -5
  291. package/internal/utils/uuid.d.mts.map +0 -1
  292. package/internal/utils/uuid.d.ts +0 -5
  293. package/internal/utils/uuid.d.ts.map +0 -1
  294. package/internal/utils/uuid.js +0 -15
  295. package/internal/utils/uuid.js.map +0 -1
  296. package/internal/utils/uuid.mjs +0 -11
  297. package/internal/utils/uuid.mjs.map +0 -1
  298. package/internal/utils/values.d.mts +0 -15
  299. package/internal/utils/values.d.mts.map +0 -1
  300. package/internal/utils/values.d.ts +0 -15
  301. package/internal/utils/values.d.ts.map +0 -1
  302. package/internal/utils/values.js +0 -100
  303. package/internal/utils/values.js.map +0 -1
  304. package/internal/utils/values.mjs +0 -84
  305. package/internal/utils/values.mjs.map +0 -1
  306. package/internal/utils.d.mts +0 -7
  307. package/internal/utils.d.mts.map +0 -1
  308. package/internal/utils.d.ts +0 -7
  309. package/internal/utils.d.ts.map +0 -1
  310. package/internal/utils.js.map +0 -1
  311. package/internal/utils.mjs +0 -8
  312. package/internal/utils.mjs.map +0 -1
  313. package/resource.d.mts +0 -6
  314. package/resource.d.mts.map +0 -1
  315. package/resources/calls.d.mts +0 -85
  316. package/resources/calls.d.mts.map +0 -1
  317. package/resources/index.d.mts +0 -2
  318. package/resources/index.d.mts.map +0 -1
  319. package/src/api-promise.ts +0 -92
  320. package/src/client.ts +0 -646
  321. package/src/internal/builtin-types.ts +0 -79
  322. package/src/internal/detect-platform.ts +0 -196
  323. package/src/internal/errors.ts +0 -22
  324. package/src/internal/headers.ts +0 -96
  325. package/src/internal/parse.ts +0 -41
  326. package/src/internal/polyfill/crypto.node.d.ts +0 -10
  327. package/src/internal/polyfill/crypto.node.js +0 -11
  328. package/src/internal/polyfill/crypto.node.mjs +0 -2
  329. package/src/internal/polyfill/file.node.d.ts +0 -9
  330. package/src/internal/polyfill/file.node.js +0 -17
  331. package/src/internal/polyfill/file.node.mjs +0 -9
  332. package/src/internal/request-options.ts +0 -67
  333. package/src/internal/shim-types.d.ts +0 -28
  334. package/src/internal/shims.ts +0 -145
  335. package/src/internal/types.ts +0 -98
  336. package/src/internal/uploads.ts +0 -307
  337. package/src/internal/utils/base64.ts +0 -37
  338. package/src/internal/utils/env.ts +0 -18
  339. package/src/internal/utils/log.ts +0 -49
  340. package/src/internal/utils/sleep.ts +0 -3
  341. package/src/internal/utils/uuid.ts +0 -13
  342. package/src/internal/utils/values.ts +0 -94
  343. package/src/internal/utils.ts +0 -8
  344. package/uploads.d.mts +0 -2
  345. package/uploads.d.mts.map +0 -1
  346. package/version.d.mts +0 -2
  347. package/version.d.mts.map +0 -1
package/src/core.ts ADDED
@@ -0,0 +1,1200 @@
1
+ import { VERSION } from './version';
2
+ import {
3
+ RoarkError,
4
+ APIError,
5
+ APIConnectionError,
6
+ APIConnectionTimeoutError,
7
+ APIUserAbortError,
8
+ } from './error';
9
+ import {
10
+ kind as shimsKind,
11
+ type Readable,
12
+ getDefaultAgent,
13
+ type Agent,
14
+ fetch,
15
+ type RequestInfo,
16
+ type RequestInit,
17
+ type Response,
18
+ type HeadersInit,
19
+ } from './_shims/index';
20
+ export { type Response };
21
+ import { BlobLike, isBlobLike, isMultipartBody } from './uploads';
22
+ export {
23
+ maybeMultipartFormRequestOptions,
24
+ multipartFormRequestOptions,
25
+ createForm,
26
+ type Uploadable,
27
+ } from './uploads';
28
+
29
+ export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
30
+
31
+ type PromiseOrValue<T> = T | Promise<T>;
32
+
33
+ type APIResponseProps = {
34
+ response: Response;
35
+ options: FinalRequestOptions;
36
+ controller: AbortController;
37
+ };
38
+
39
+ async function defaultParseResponse<T>(props: APIResponseProps): Promise<T> {
40
+ const { response } = props;
41
+ // fetch refuses to read the body when the status code is 204.
42
+ if (response.status === 204) {
43
+ return null as T;
44
+ }
45
+
46
+ if (props.options.__binaryResponse) {
47
+ return response as unknown as T;
48
+ }
49
+
50
+ const contentType = response.headers.get('content-type');
51
+ const isJSON =
52
+ contentType?.includes('application/json') || contentType?.includes('application/vnd.api+json');
53
+ if (isJSON) {
54
+ const json = await response.json();
55
+
56
+ debug('response', response.status, response.url, response.headers, json);
57
+
58
+ return json as T;
59
+ }
60
+
61
+ const text = await response.text();
62
+ debug('response', response.status, response.url, response.headers, text);
63
+
64
+ // TODO handle blob, arraybuffer, other content types, etc.
65
+ return text as unknown as T;
66
+ }
67
+
68
+ /**
69
+ * A subclass of `Promise` providing additional helper methods
70
+ * for interacting with the SDK.
71
+ */
72
+ export class APIPromise<T> extends Promise<T> {
73
+ private parsedPromise: Promise<T> | undefined;
74
+
75
+ constructor(
76
+ private responsePromise: Promise<APIResponseProps>,
77
+ private parseResponse: (props: APIResponseProps) => PromiseOrValue<T> = defaultParseResponse,
78
+ ) {
79
+ super((resolve) => {
80
+ // this is maybe a bit weird but this has to be a no-op to not implicitly
81
+ // parse the response body; instead .then, .catch, .finally are overridden
82
+ // to parse the response
83
+ resolve(null as any);
84
+ });
85
+ }
86
+
87
+ _thenUnwrap<U>(transform: (data: T, props: APIResponseProps) => U): APIPromise<U> {
88
+ return new APIPromise(this.responsePromise, async (props) =>
89
+ transform(await this.parseResponse(props), props),
90
+ );
91
+ }
92
+
93
+ /**
94
+ * Gets the raw `Response` instance instead of parsing the response
95
+ * data.
96
+ *
97
+ * If you want to parse the response body but still get the `Response`
98
+ * instance, you can use {@link withResponse()}.
99
+ *
100
+ * 👋 Getting the wrong TypeScript type for `Response`?
101
+ * Try setting `"moduleResolution": "NodeNext"` if you can,
102
+ * or add one of these imports before your first `import … from '@roarkanalytics/sdk'`:
103
+ * - `import '@roarkanalytics/sdk/shims/node'` (if you're running on Node)
104
+ * - `import '@roarkanalytics/sdk/shims/web'` (otherwise)
105
+ */
106
+ asResponse(): Promise<Response> {
107
+ return this.responsePromise.then((p) => p.response);
108
+ }
109
+ /**
110
+ * Gets the parsed response data and the raw `Response` instance.
111
+ *
112
+ * If you just want to get the raw `Response` instance without parsing it,
113
+ * you can use {@link asResponse()}.
114
+ *
115
+ *
116
+ * 👋 Getting the wrong TypeScript type for `Response`?
117
+ * Try setting `"moduleResolution": "NodeNext"` if you can,
118
+ * or add one of these imports before your first `import … from '@roarkanalytics/sdk'`:
119
+ * - `import '@roarkanalytics/sdk/shims/node'` (if you're running on Node)
120
+ * - `import '@roarkanalytics/sdk/shims/web'` (otherwise)
121
+ */
122
+ async withResponse(): Promise<{ data: T; response: Response }> {
123
+ const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
124
+ return { data, response };
125
+ }
126
+
127
+ private parse(): Promise<T> {
128
+ if (!this.parsedPromise) {
129
+ this.parsedPromise = this.responsePromise.then(this.parseResponse);
130
+ }
131
+ return this.parsedPromise;
132
+ }
133
+
134
+ override then<TResult1 = T, TResult2 = never>(
135
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
136
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
137
+ ): Promise<TResult1 | TResult2> {
138
+ return this.parse().then(onfulfilled, onrejected);
139
+ }
140
+
141
+ override catch<TResult = never>(
142
+ onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
143
+ ): Promise<T | TResult> {
144
+ return this.parse().catch(onrejected);
145
+ }
146
+
147
+ override finally(onfinally?: (() => void) | undefined | null): Promise<T> {
148
+ return this.parse().finally(onfinally);
149
+ }
150
+ }
151
+
152
+ export abstract class APIClient {
153
+ baseURL: string;
154
+ maxRetries: number;
155
+ timeout: number;
156
+ httpAgent: Agent | undefined;
157
+
158
+ private fetch: Fetch;
159
+ protected idempotencyHeader?: string;
160
+
161
+ constructor({
162
+ baseURL,
163
+ maxRetries = 2,
164
+ timeout = 60000, // 1 minute
165
+ httpAgent,
166
+ fetch: overriddenFetch,
167
+ }: {
168
+ baseURL: string;
169
+ maxRetries?: number | undefined;
170
+ timeout: number | undefined;
171
+ httpAgent: Agent | undefined;
172
+ fetch: Fetch | undefined;
173
+ }) {
174
+ this.baseURL = baseURL;
175
+ this.maxRetries = validatePositiveInteger('maxRetries', maxRetries);
176
+ this.timeout = validatePositiveInteger('timeout', timeout);
177
+ this.httpAgent = httpAgent;
178
+
179
+ this.fetch = overriddenFetch ?? fetch;
180
+ }
181
+
182
+ protected authHeaders(opts: FinalRequestOptions): Headers {
183
+ return {};
184
+ }
185
+
186
+ /**
187
+ * Override this to add your own default headers, for example:
188
+ *
189
+ * {
190
+ * ...super.defaultHeaders(),
191
+ * Authorization: 'Bearer 123',
192
+ * }
193
+ */
194
+ protected defaultHeaders(opts: FinalRequestOptions): Headers {
195
+ return {
196
+ Accept: 'application/json',
197
+ 'Content-Type': 'application/json',
198
+ 'User-Agent': this.getUserAgent(),
199
+ ...getPlatformHeaders(),
200
+ ...this.authHeaders(opts),
201
+ };
202
+ }
203
+
204
+ protected abstract defaultQuery(): DefaultQuery | undefined;
205
+
206
+ /**
207
+ * Override this to add your own headers validation:
208
+ */
209
+ protected validateHeaders(headers: Headers, customHeaders: Headers) {}
210
+
211
+ protected defaultIdempotencyKey(): string {
212
+ return `stainless-node-retry-${uuid4()}`;
213
+ }
214
+
215
+ get<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
216
+ return this.methodRequest('get', path, opts);
217
+ }
218
+
219
+ post<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
220
+ return this.methodRequest('post', path, opts);
221
+ }
222
+
223
+ patch<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
224
+ return this.methodRequest('patch', path, opts);
225
+ }
226
+
227
+ put<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
228
+ return this.methodRequest('put', path, opts);
229
+ }
230
+
231
+ delete<Req, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
232
+ return this.methodRequest('delete', path, opts);
233
+ }
234
+
235
+ private methodRequest<Req, Rsp>(
236
+ method: HTTPMethod,
237
+ path: string,
238
+ opts?: PromiseOrValue<RequestOptions<Req>>,
239
+ ): APIPromise<Rsp> {
240
+ return this.request(
241
+ Promise.resolve(opts).then(async (opts) => {
242
+ const body =
243
+ opts && isBlobLike(opts?.body) ? new DataView(await opts.body.arrayBuffer())
244
+ : opts?.body instanceof DataView ? opts.body
245
+ : opts?.body instanceof ArrayBuffer ? new DataView(opts.body)
246
+ : opts && ArrayBuffer.isView(opts?.body) ? new DataView(opts.body.buffer)
247
+ : opts?.body;
248
+ return { method, path, ...opts, body };
249
+ }),
250
+ );
251
+ }
252
+
253
+ getAPIList<Item, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(
254
+ path: string,
255
+ Page: new (...args: any[]) => PageClass,
256
+ opts?: RequestOptions<any>,
257
+ ): PagePromise<PageClass, Item> {
258
+ return this.requestAPIList(Page, { method: 'get', path, ...opts });
259
+ }
260
+
261
+ private calculateContentLength(body: unknown): string | null {
262
+ if (typeof body === 'string') {
263
+ if (typeof Buffer !== 'undefined') {
264
+ return Buffer.byteLength(body, 'utf8').toString();
265
+ }
266
+
267
+ if (typeof TextEncoder !== 'undefined') {
268
+ const encoder = new TextEncoder();
269
+ const encoded = encoder.encode(body);
270
+ return encoded.length.toString();
271
+ }
272
+ } else if (ArrayBuffer.isView(body)) {
273
+ return body.byteLength.toString();
274
+ }
275
+
276
+ return null;
277
+ }
278
+
279
+ buildRequest<Req>(
280
+ options: FinalRequestOptions<Req>,
281
+ { retryCount = 0 }: { retryCount?: number } = {},
282
+ ): { req: RequestInit; url: string; timeout: number } {
283
+ const { method, path, query, headers: headers = {} } = options;
284
+
285
+ const body =
286
+ ArrayBuffer.isView(options.body) || (options.__binaryRequest && typeof options.body === 'string') ?
287
+ options.body
288
+ : isMultipartBody(options.body) ? options.body.body
289
+ : options.body ? JSON.stringify(options.body, null, 2)
290
+ : null;
291
+ const contentLength = this.calculateContentLength(body);
292
+
293
+ const url = this.buildURL(path!, query);
294
+ if ('timeout' in options) validatePositiveInteger('timeout', options.timeout);
295
+ const timeout = options.timeout ?? this.timeout;
296
+ const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
297
+ const minAgentTimeout = timeout + 1000;
298
+ if (
299
+ typeof (httpAgent as any)?.options?.timeout === 'number' &&
300
+ minAgentTimeout > ((httpAgent as any).options.timeout ?? 0)
301
+ ) {
302
+ // Allow any given request to bump our agent active socket timeout.
303
+ // This may seem strange, but leaking active sockets should be rare and not particularly problematic,
304
+ // and without mutating agent we would need to create more of them.
305
+ // This tradeoff optimizes for performance.
306
+ (httpAgent as any).options.timeout = minAgentTimeout;
307
+ }
308
+
309
+ if (this.idempotencyHeader && method !== 'get') {
310
+ if (!options.idempotencyKey) options.idempotencyKey = this.defaultIdempotencyKey();
311
+ headers[this.idempotencyHeader] = options.idempotencyKey;
312
+ }
313
+
314
+ const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount });
315
+
316
+ const req: RequestInit = {
317
+ method,
318
+ ...(body && { body: body as any }),
319
+ headers: reqHeaders,
320
+ ...(httpAgent && { agent: httpAgent }),
321
+ // @ts-ignore node-fetch uses a custom AbortSignal type that is
322
+ // not compatible with standard web types
323
+ signal: options.signal ?? null,
324
+ };
325
+
326
+ return { req, url, timeout };
327
+ }
328
+
329
+ private buildHeaders({
330
+ options,
331
+ headers,
332
+ contentLength,
333
+ retryCount,
334
+ }: {
335
+ options: FinalRequestOptions;
336
+ headers: Record<string, string | null | undefined>;
337
+ contentLength: string | null | undefined;
338
+ retryCount: number;
339
+ }): Record<string, string> {
340
+ const reqHeaders: Record<string, string> = {};
341
+ if (contentLength) {
342
+ reqHeaders['content-length'] = contentLength;
343
+ }
344
+
345
+ const defaultHeaders = this.defaultHeaders(options);
346
+ applyHeadersMut(reqHeaders, defaultHeaders);
347
+ applyHeadersMut(reqHeaders, headers);
348
+
349
+ // let builtin fetch set the Content-Type for multipart bodies
350
+ if (isMultipartBody(options.body) && shimsKind !== 'node') {
351
+ delete reqHeaders['content-type'];
352
+ }
353
+
354
+ // Don't set the retry count header if it was already set or removed through default headers or by the
355
+ // caller. We check `defaultHeaders` and `headers`, which can contain nulls, instead of `reqHeaders` to
356
+ // account for the removal case.
357
+ if (
358
+ getHeader(defaultHeaders, 'x-stainless-retry-count') === undefined &&
359
+ getHeader(headers, 'x-stainless-retry-count') === undefined
360
+ ) {
361
+ reqHeaders['x-stainless-retry-count'] = String(retryCount);
362
+ }
363
+
364
+ this.validateHeaders(reqHeaders, headers);
365
+
366
+ return reqHeaders;
367
+ }
368
+
369
+ /**
370
+ * Used as a callback for mutating the given `FinalRequestOptions` object.
371
+ */
372
+ protected async prepareOptions(options: FinalRequestOptions): Promise<void> {}
373
+
374
+ /**
375
+ * Used as a callback for mutating the given `RequestInit` object.
376
+ *
377
+ * This is useful for cases where you want to add certain headers based off of
378
+ * the request properties, e.g. `method` or `url`.
379
+ */
380
+ protected async prepareRequest(
381
+ request: RequestInit,
382
+ { url, options }: { url: string; options: FinalRequestOptions },
383
+ ): Promise<void> {}
384
+
385
+ protected parseHeaders(headers: HeadersInit | null | undefined): Record<string, string> {
386
+ return (
387
+ !headers ? {}
388
+ : Symbol.iterator in headers ?
389
+ Object.fromEntries(Array.from(headers as Iterable<string[]>).map((header) => [...header]))
390
+ : { ...headers }
391
+ );
392
+ }
393
+
394
+ protected makeStatusError(
395
+ status: number | undefined,
396
+ error: Object | undefined,
397
+ message: string | undefined,
398
+ headers: Headers | undefined,
399
+ ): APIError {
400
+ return APIError.generate(status, error, message, headers);
401
+ }
402
+
403
+ request<Req, Rsp>(
404
+ options: PromiseOrValue<FinalRequestOptions<Req>>,
405
+ remainingRetries: number | null = null,
406
+ ): APIPromise<Rsp> {
407
+ return new APIPromise(this.makeRequest(options, remainingRetries));
408
+ }
409
+
410
+ private async makeRequest<Req>(
411
+ optionsInput: PromiseOrValue<FinalRequestOptions<Req>>,
412
+ retriesRemaining: number | null,
413
+ ): Promise<APIResponseProps> {
414
+ const options = await optionsInput;
415
+ const maxRetries = options.maxRetries ?? this.maxRetries;
416
+ if (retriesRemaining == null) {
417
+ retriesRemaining = maxRetries;
418
+ }
419
+
420
+ await this.prepareOptions(options);
421
+
422
+ const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining });
423
+
424
+ await this.prepareRequest(req, { url, options });
425
+
426
+ debug('request', url, options, req.headers);
427
+
428
+ if (options.signal?.aborted) {
429
+ throw new APIUserAbortError();
430
+ }
431
+
432
+ const controller = new AbortController();
433
+ const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
434
+
435
+ if (response instanceof Error) {
436
+ if (options.signal?.aborted) {
437
+ throw new APIUserAbortError();
438
+ }
439
+ if (retriesRemaining) {
440
+ return this.retryRequest(options, retriesRemaining);
441
+ }
442
+ if (response.name === 'AbortError') {
443
+ throw new APIConnectionTimeoutError();
444
+ }
445
+ throw new APIConnectionError({ cause: response });
446
+ }
447
+
448
+ const responseHeaders = createResponseHeaders(response.headers);
449
+
450
+ if (!response.ok) {
451
+ if (retriesRemaining && this.shouldRetry(response)) {
452
+ const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
453
+ debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders);
454
+ return this.retryRequest(options, retriesRemaining, responseHeaders);
455
+ }
456
+
457
+ const errText = await response.text().catch((e) => castToError(e).message);
458
+ const errJSON = safeJSON(errText);
459
+ const errMessage = errJSON ? undefined : errText;
460
+ const retryMessage = retriesRemaining ? `(error; no more retries left)` : `(error; not retryable)`;
461
+
462
+ debug(`response (error; ${retryMessage})`, response.status, url, responseHeaders, errMessage);
463
+
464
+ const err = this.makeStatusError(response.status, errJSON, errMessage, responseHeaders);
465
+ throw err;
466
+ }
467
+
468
+ return { response, options, controller };
469
+ }
470
+
471
+ requestAPIList<Item = unknown, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(
472
+ Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
473
+ options: FinalRequestOptions,
474
+ ): PagePromise<PageClass, Item> {
475
+ const request = this.makeRequest(options, null);
476
+ return new PagePromise<PageClass, Item>(this, request, Page);
477
+ }
478
+
479
+ buildURL<Req>(path: string, query: Req | null | undefined): string {
480
+ const url =
481
+ isAbsoluteURL(path) ?
482
+ new URL(path)
483
+ : new URL(this.baseURL + (this.baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
484
+
485
+ const defaultQuery = this.defaultQuery();
486
+ if (!isEmptyObj(defaultQuery)) {
487
+ query = { ...defaultQuery, ...query } as Req;
488
+ }
489
+
490
+ if (typeof query === 'object' && query && !Array.isArray(query)) {
491
+ url.search = this.stringifyQuery(query as Record<string, unknown>);
492
+ }
493
+
494
+ return url.toString();
495
+ }
496
+
497
+ protected stringifyQuery(query: Record<string, unknown>): string {
498
+ return Object.entries(query)
499
+ .filter(([_, value]) => typeof value !== 'undefined')
500
+ .map(([key, value]) => {
501
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
502
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
503
+ }
504
+ if (value === null) {
505
+ return `${encodeURIComponent(key)}=`;
506
+ }
507
+ throw new RoarkError(
508
+ `Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`,
509
+ );
510
+ })
511
+ .join('&');
512
+ }
513
+
514
+ async fetchWithTimeout(
515
+ url: RequestInfo,
516
+ init: RequestInit | undefined,
517
+ ms: number,
518
+ controller: AbortController,
519
+ ): Promise<Response> {
520
+ const { signal, ...options } = init || {};
521
+ if (signal) signal.addEventListener('abort', () => controller.abort());
522
+
523
+ const timeout = setTimeout(() => controller.abort(), ms);
524
+
525
+ const fetchOptions = {
526
+ signal: controller.signal as any,
527
+ ...options,
528
+ };
529
+ if (fetchOptions.method) {
530
+ // Custom methods like 'patch' need to be uppercased
531
+ // See https://github.com/nodejs/undici/issues/2294
532
+ fetchOptions.method = fetchOptions.method.toUpperCase();
533
+ }
534
+
535
+ return (
536
+ // use undefined this binding; fetch errors if bound to something else in browser/cloudflare
537
+ this.fetch.call(undefined, url, fetchOptions).finally(() => {
538
+ clearTimeout(timeout);
539
+ })
540
+ );
541
+ }
542
+
543
+ private shouldRetry(response: Response): boolean {
544
+ // Note this is not a standard header.
545
+ const shouldRetryHeader = response.headers.get('x-should-retry');
546
+
547
+ // If the server explicitly says whether or not to retry, obey.
548
+ if (shouldRetryHeader === 'true') return true;
549
+ if (shouldRetryHeader === 'false') return false;
550
+
551
+ // Retry on request timeouts.
552
+ if (response.status === 408) return true;
553
+
554
+ // Retry on lock timeouts.
555
+ if (response.status === 409) return true;
556
+
557
+ // Retry on rate limits.
558
+ if (response.status === 429) return true;
559
+
560
+ // Retry internal errors.
561
+ if (response.status >= 500) return true;
562
+
563
+ return false;
564
+ }
565
+
566
+ private async retryRequest(
567
+ options: FinalRequestOptions,
568
+ retriesRemaining: number,
569
+ responseHeaders?: Headers | undefined,
570
+ ): Promise<APIResponseProps> {
571
+ let timeoutMillis: number | undefined;
572
+
573
+ // Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it.
574
+ const retryAfterMillisHeader = responseHeaders?.['retry-after-ms'];
575
+ if (retryAfterMillisHeader) {
576
+ const timeoutMs = parseFloat(retryAfterMillisHeader);
577
+ if (!Number.isNaN(timeoutMs)) {
578
+ timeoutMillis = timeoutMs;
579
+ }
580
+ }
581
+
582
+ // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
583
+ const retryAfterHeader = responseHeaders?.['retry-after'];
584
+ if (retryAfterHeader && !timeoutMillis) {
585
+ const timeoutSeconds = parseFloat(retryAfterHeader);
586
+ if (!Number.isNaN(timeoutSeconds)) {
587
+ timeoutMillis = timeoutSeconds * 1000;
588
+ } else {
589
+ timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
590
+ }
591
+ }
592
+
593
+ // If the API asks us to wait a certain amount of time (and it's a reasonable amount),
594
+ // just do what it says, but otherwise calculate a default
595
+ if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) {
596
+ const maxRetries = options.maxRetries ?? this.maxRetries;
597
+ timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
598
+ }
599
+ await sleep(timeoutMillis);
600
+
601
+ return this.makeRequest(options, retriesRemaining - 1);
602
+ }
603
+
604
+ private calculateDefaultRetryTimeoutMillis(retriesRemaining: number, maxRetries: number): number {
605
+ const initialRetryDelay = 0.5;
606
+ const maxRetryDelay = 8.0;
607
+
608
+ const numRetries = maxRetries - retriesRemaining;
609
+
610
+ // Apply exponential backoff, but not more than the max.
611
+ const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);
612
+
613
+ // Apply some jitter, take up to at most 25 percent of the retry time.
614
+ const jitter = 1 - Math.random() * 0.25;
615
+
616
+ return sleepSeconds * jitter * 1000;
617
+ }
618
+
619
+ private getUserAgent(): string {
620
+ return `${this.constructor.name}/JS ${VERSION}`;
621
+ }
622
+ }
623
+
624
+ export type PageInfo = { url: URL } | { params: Record<string, unknown> | null };
625
+
626
+ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
627
+ #client: APIClient;
628
+ protected options: FinalRequestOptions;
629
+
630
+ protected response: Response;
631
+ protected body: unknown;
632
+
633
+ constructor(client: APIClient, response: Response, body: unknown, options: FinalRequestOptions) {
634
+ this.#client = client;
635
+ this.options = options;
636
+ this.response = response;
637
+ this.body = body;
638
+ }
639
+
640
+ /**
641
+ * @deprecated Use nextPageInfo instead
642
+ */
643
+ abstract nextPageParams(): Partial<Record<string, unknown>> | null;
644
+ abstract nextPageInfo(): PageInfo | null;
645
+
646
+ abstract getPaginatedItems(): Item[];
647
+
648
+ hasNextPage(): boolean {
649
+ const items = this.getPaginatedItems();
650
+ if (!items.length) return false;
651
+ return this.nextPageInfo() != null;
652
+ }
653
+
654
+ async getNextPage(): Promise<this> {
655
+ const nextInfo = this.nextPageInfo();
656
+ if (!nextInfo) {
657
+ throw new RoarkError(
658
+ 'No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.',
659
+ );
660
+ }
661
+ const nextOptions = { ...this.options };
662
+ if ('params' in nextInfo && typeof nextOptions.query === 'object') {
663
+ nextOptions.query = { ...nextOptions.query, ...nextInfo.params };
664
+ } else if ('url' in nextInfo) {
665
+ const params = [...Object.entries(nextOptions.query || {}), ...nextInfo.url.searchParams.entries()];
666
+ for (const [key, value] of params) {
667
+ nextInfo.url.searchParams.set(key, value as any);
668
+ }
669
+ nextOptions.query = undefined;
670
+ nextOptions.path = nextInfo.url.toString();
671
+ }
672
+ return await this.#client.requestAPIList(this.constructor as any, nextOptions);
673
+ }
674
+
675
+ async *iterPages(): AsyncGenerator<this> {
676
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
677
+ let page: this = this;
678
+ yield page;
679
+ while (page.hasNextPage()) {
680
+ page = await page.getNextPage();
681
+ yield page;
682
+ }
683
+ }
684
+
685
+ async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
686
+ for await (const page of this.iterPages()) {
687
+ for (const item of page.getPaginatedItems()) {
688
+ yield item;
689
+ }
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * This subclass of Promise will resolve to an instantiated Page once the request completes.
696
+ *
697
+ * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
698
+ *
699
+ * for await (const item of client.items.list()) {
700
+ * console.log(item)
701
+ * }
702
+ */
703
+ export class PagePromise<
704
+ PageClass extends AbstractPage<Item>,
705
+ Item = ReturnType<PageClass['getPaginatedItems']>[number],
706
+ >
707
+ extends APIPromise<PageClass>
708
+ implements AsyncIterable<Item>
709
+ {
710
+ constructor(
711
+ client: APIClient,
712
+ request: Promise<APIResponseProps>,
713
+ Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
714
+ ) {
715
+ super(
716
+ request,
717
+ async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options),
718
+ );
719
+ }
720
+
721
+ /**
722
+ * Allow auto-paginating iteration on an unawaited list call, eg:
723
+ *
724
+ * for await (const item of client.items.list()) {
725
+ * console.log(item)
726
+ * }
727
+ */
728
+ async *[Symbol.asyncIterator](): AsyncGenerator<Item> {
729
+ const page = await this;
730
+ for await (const item of page) {
731
+ yield item;
732
+ }
733
+ }
734
+ }
735
+
736
+ export const createResponseHeaders = (
737
+ headers: Awaited<ReturnType<Fetch>>['headers'],
738
+ ): Record<string, string> => {
739
+ return new Proxy(
740
+ Object.fromEntries(
741
+ // @ts-ignore
742
+ headers.entries(),
743
+ ),
744
+ {
745
+ get(target, name) {
746
+ const key = name.toString();
747
+ return target[key.toLowerCase()] || target[key];
748
+ },
749
+ },
750
+ );
751
+ };
752
+
753
+ type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
754
+
755
+ export type RequestClient = { fetch: Fetch };
756
+ export type Headers = Record<string, string | null | undefined>;
757
+ export type DefaultQuery = Record<string, string | undefined>;
758
+ export type KeysEnum<T> = { [P in keyof Required<T>]: true };
759
+
760
+ export type RequestOptions<
761
+ Req = unknown | Record<string, unknown> | Readable | BlobLike | ArrayBufferView | ArrayBuffer,
762
+ > = {
763
+ method?: HTTPMethod;
764
+ path?: string;
765
+ query?: Req | undefined;
766
+ body?: Req | null | undefined;
767
+ headers?: Headers | undefined;
768
+
769
+ maxRetries?: number;
770
+ stream?: boolean | undefined;
771
+ timeout?: number;
772
+ httpAgent?: Agent;
773
+ signal?: AbortSignal | undefined | null;
774
+ idempotencyKey?: string;
775
+
776
+ __binaryRequest?: boolean | undefined;
777
+ __binaryResponse?: boolean | undefined;
778
+ };
779
+
780
+ // This is required so that we can determine if a given object matches the RequestOptions
781
+ // type at runtime. While this requires duplication, it is enforced by the TypeScript
782
+ // compiler such that any missing / extraneous keys will cause an error.
783
+ const requestOptionsKeys: KeysEnum<RequestOptions> = {
784
+ method: true,
785
+ path: true,
786
+ query: true,
787
+ body: true,
788
+ headers: true,
789
+
790
+ maxRetries: true,
791
+ stream: true,
792
+ timeout: true,
793
+ httpAgent: true,
794
+ signal: true,
795
+ idempotencyKey: true,
796
+
797
+ __binaryRequest: true,
798
+ __binaryResponse: true,
799
+ };
800
+
801
+ export const isRequestOptions = (obj: unknown): obj is RequestOptions => {
802
+ return (
803
+ typeof obj === 'object' &&
804
+ obj !== null &&
805
+ !isEmptyObj(obj) &&
806
+ Object.keys(obj).every((k) => hasOwn(requestOptionsKeys, k))
807
+ );
808
+ };
809
+
810
+ export type FinalRequestOptions<Req = unknown | Record<string, unknown> | Readable | DataView> =
811
+ RequestOptions<Req> & {
812
+ method: HTTPMethod;
813
+ path: string;
814
+ };
815
+
816
+ declare const Deno: any;
817
+ declare const EdgeRuntime: any;
818
+ type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown';
819
+ type PlatformName =
820
+ | 'MacOS'
821
+ | 'Linux'
822
+ | 'Windows'
823
+ | 'FreeBSD'
824
+ | 'OpenBSD'
825
+ | 'iOS'
826
+ | 'Android'
827
+ | `Other:${string}`
828
+ | 'Unknown';
829
+ type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari';
830
+ type PlatformProperties = {
831
+ 'X-Stainless-Lang': 'js';
832
+ 'X-Stainless-Package-Version': string;
833
+ 'X-Stainless-OS': PlatformName;
834
+ 'X-Stainless-Arch': Arch;
835
+ 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown';
836
+ 'X-Stainless-Runtime-Version': string;
837
+ };
838
+ const getPlatformProperties = (): PlatformProperties => {
839
+ if (typeof Deno !== 'undefined' && Deno.build != null) {
840
+ return {
841
+ 'X-Stainless-Lang': 'js',
842
+ 'X-Stainless-Package-Version': VERSION,
843
+ 'X-Stainless-OS': normalizePlatform(Deno.build.os),
844
+ 'X-Stainless-Arch': normalizeArch(Deno.build.arch),
845
+ 'X-Stainless-Runtime': 'deno',
846
+ 'X-Stainless-Runtime-Version':
847
+ typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
848
+ };
849
+ }
850
+ if (typeof EdgeRuntime !== 'undefined') {
851
+ return {
852
+ 'X-Stainless-Lang': 'js',
853
+ 'X-Stainless-Package-Version': VERSION,
854
+ 'X-Stainless-OS': 'Unknown',
855
+ 'X-Stainless-Arch': `other:${EdgeRuntime}`,
856
+ 'X-Stainless-Runtime': 'edge',
857
+ 'X-Stainless-Runtime-Version': process.version,
858
+ };
859
+ }
860
+ // Check if Node.js
861
+ if (Object.prototype.toString.call(typeof process !== 'undefined' ? process : 0) === '[object process]') {
862
+ return {
863
+ 'X-Stainless-Lang': 'js',
864
+ 'X-Stainless-Package-Version': VERSION,
865
+ 'X-Stainless-OS': normalizePlatform(process.platform),
866
+ 'X-Stainless-Arch': normalizeArch(process.arch),
867
+ 'X-Stainless-Runtime': 'node',
868
+ 'X-Stainless-Runtime-Version': process.version,
869
+ };
870
+ }
871
+
872
+ const browserInfo = getBrowserInfo();
873
+ if (browserInfo) {
874
+ return {
875
+ 'X-Stainless-Lang': 'js',
876
+ 'X-Stainless-Package-Version': VERSION,
877
+ 'X-Stainless-OS': 'Unknown',
878
+ 'X-Stainless-Arch': 'unknown',
879
+ 'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
880
+ 'X-Stainless-Runtime-Version': browserInfo.version,
881
+ };
882
+ }
883
+
884
+ // TODO add support for Cloudflare workers, etc.
885
+ return {
886
+ 'X-Stainless-Lang': 'js',
887
+ 'X-Stainless-Package-Version': VERSION,
888
+ 'X-Stainless-OS': 'Unknown',
889
+ 'X-Stainless-Arch': 'unknown',
890
+ 'X-Stainless-Runtime': 'unknown',
891
+ 'X-Stainless-Runtime-Version': 'unknown',
892
+ };
893
+ };
894
+
895
+ type BrowserInfo = {
896
+ browser: Browser;
897
+ version: string;
898
+ };
899
+
900
+ declare const navigator: { userAgent: string } | undefined;
901
+
902
+ // Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
903
+ function getBrowserInfo(): BrowserInfo | null {
904
+ if (typeof navigator === 'undefined' || !navigator) {
905
+ return null;
906
+ }
907
+
908
+ // NOTE: The order matters here!
909
+ const browserPatterns = [
910
+ { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
911
+ { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
912
+ { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
913
+ { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
914
+ { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
915
+ { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
916
+ ];
917
+
918
+ // Find the FIRST matching browser
919
+ for (const { key, pattern } of browserPatterns) {
920
+ const match = pattern.exec(navigator.userAgent);
921
+ if (match) {
922
+ const major = match[1] || 0;
923
+ const minor = match[2] || 0;
924
+ const patch = match[3] || 0;
925
+
926
+ return { browser: key, version: `${major}.${minor}.${patch}` };
927
+ }
928
+ }
929
+
930
+ return null;
931
+ }
932
+
933
+ const normalizeArch = (arch: string): Arch => {
934
+ // Node docs:
935
+ // - https://nodejs.org/api/process.html#processarch
936
+ // Deno docs:
937
+ // - https://doc.deno.land/deno/stable/~/Deno.build
938
+ if (arch === 'x32') return 'x32';
939
+ if (arch === 'x86_64' || arch === 'x64') return 'x64';
940
+ if (arch === 'arm') return 'arm';
941
+ if (arch === 'aarch64' || arch === 'arm64') return 'arm64';
942
+ if (arch) return `other:${arch}`;
943
+ return 'unknown';
944
+ };
945
+
946
+ const normalizePlatform = (platform: string): PlatformName => {
947
+ // Node platforms:
948
+ // - https://nodejs.org/api/process.html#processplatform
949
+ // Deno platforms:
950
+ // - https://doc.deno.land/deno/stable/~/Deno.build
951
+ // - https://github.com/denoland/deno/issues/14799
952
+
953
+ platform = platform.toLowerCase();
954
+
955
+ // NOTE: this iOS check is untested and may not work
956
+ // Node does not work natively on IOS, there is a fork at
957
+ // https://github.com/nodejs-mobile/nodejs-mobile
958
+ // however it is unknown at the time of writing how to detect if it is running
959
+ if (platform.includes('ios')) return 'iOS';
960
+ if (platform === 'android') return 'Android';
961
+ if (platform === 'darwin') return 'MacOS';
962
+ if (platform === 'win32') return 'Windows';
963
+ if (platform === 'freebsd') return 'FreeBSD';
964
+ if (platform === 'openbsd') return 'OpenBSD';
965
+ if (platform === 'linux') return 'Linux';
966
+ if (platform) return `Other:${platform}`;
967
+ return 'Unknown';
968
+ };
969
+
970
+ let _platformHeaders: PlatformProperties;
971
+ const getPlatformHeaders = () => {
972
+ return (_platformHeaders ??= getPlatformProperties());
973
+ };
974
+
975
+ export const safeJSON = (text: string) => {
976
+ try {
977
+ return JSON.parse(text);
978
+ } catch (err) {
979
+ return undefined;
980
+ }
981
+ };
982
+
983
+ // https://url.spec.whatwg.org/#url-scheme-string
984
+ const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
985
+ const isAbsoluteURL = (url: string): boolean => {
986
+ return startsWithSchemeRegexp.test(url);
987
+ };
988
+
989
+ export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
990
+
991
+ const validatePositiveInteger = (name: string, n: unknown): number => {
992
+ if (typeof n !== 'number' || !Number.isInteger(n)) {
993
+ throw new RoarkError(`${name} must be an integer`);
994
+ }
995
+ if (n < 0) {
996
+ throw new RoarkError(`${name} must be a positive integer`);
997
+ }
998
+ return n;
999
+ };
1000
+
1001
+ export const castToError = (err: any): Error => {
1002
+ if (err instanceof Error) return err;
1003
+ if (typeof err === 'object' && err !== null) {
1004
+ try {
1005
+ return new Error(JSON.stringify(err));
1006
+ } catch {}
1007
+ }
1008
+ return new Error(err);
1009
+ };
1010
+
1011
+ export const ensurePresent = <T>(value: T | null | undefined): T => {
1012
+ if (value == null) throw new RoarkError(`Expected a value to be given but received ${value} instead.`);
1013
+ return value;
1014
+ };
1015
+
1016
+ /**
1017
+ * Read an environment variable.
1018
+ *
1019
+ * Trims beginning and trailing whitespace.
1020
+ *
1021
+ * Will return undefined if the environment variable doesn't exist or cannot be accessed.
1022
+ */
1023
+ export const readEnv = (env: string): string | undefined => {
1024
+ if (typeof process !== 'undefined') {
1025
+ return process.env?.[env]?.trim() ?? undefined;
1026
+ }
1027
+ if (typeof Deno !== 'undefined') {
1028
+ return Deno.env?.get?.(env)?.trim();
1029
+ }
1030
+ return undefined;
1031
+ };
1032
+
1033
+ export const coerceInteger = (value: unknown): number => {
1034
+ if (typeof value === 'number') return Math.round(value);
1035
+ if (typeof value === 'string') return parseInt(value, 10);
1036
+
1037
+ throw new RoarkError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
1038
+ };
1039
+
1040
+ export const coerceFloat = (value: unknown): number => {
1041
+ if (typeof value === 'number') return value;
1042
+ if (typeof value === 'string') return parseFloat(value);
1043
+
1044
+ throw new RoarkError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
1045
+ };
1046
+
1047
+ export const coerceBoolean = (value: unknown): boolean => {
1048
+ if (typeof value === 'boolean') return value;
1049
+ if (typeof value === 'string') return value === 'true';
1050
+ return Boolean(value);
1051
+ };
1052
+
1053
+ export const maybeCoerceInteger = (value: unknown): number | undefined => {
1054
+ if (value === undefined) {
1055
+ return undefined;
1056
+ }
1057
+ return coerceInteger(value);
1058
+ };
1059
+
1060
+ export const maybeCoerceFloat = (value: unknown): number | undefined => {
1061
+ if (value === undefined) {
1062
+ return undefined;
1063
+ }
1064
+ return coerceFloat(value);
1065
+ };
1066
+
1067
+ export const maybeCoerceBoolean = (value: unknown): boolean | undefined => {
1068
+ if (value === undefined) {
1069
+ return undefined;
1070
+ }
1071
+ return coerceBoolean(value);
1072
+ };
1073
+
1074
+ // https://stackoverflow.com/a/34491287
1075
+ export function isEmptyObj(obj: Object | null | undefined): boolean {
1076
+ if (!obj) return true;
1077
+ for (const _k in obj) return false;
1078
+ return true;
1079
+ }
1080
+
1081
+ // https://eslint.org/docs/latest/rules/no-prototype-builtins
1082
+ export function hasOwn(obj: Object, key: string): boolean {
1083
+ return Object.prototype.hasOwnProperty.call(obj, key);
1084
+ }
1085
+
1086
+ /**
1087
+ * Copies headers from "newHeaders" onto "targetHeaders",
1088
+ * using lower-case for all properties,
1089
+ * ignoring any keys with undefined values,
1090
+ * and deleting any keys with null values.
1091
+ */
1092
+ function applyHeadersMut(targetHeaders: Headers, newHeaders: Headers): void {
1093
+ for (const k in newHeaders) {
1094
+ if (!hasOwn(newHeaders, k)) continue;
1095
+ const lowerKey = k.toLowerCase();
1096
+ if (!lowerKey) continue;
1097
+
1098
+ const val = newHeaders[k];
1099
+
1100
+ if (val === null) {
1101
+ delete targetHeaders[lowerKey];
1102
+ } else if (val !== undefined) {
1103
+ targetHeaders[lowerKey] = val;
1104
+ }
1105
+ }
1106
+ }
1107
+
1108
+ export function debug(action: string, ...args: any[]) {
1109
+ if (typeof process !== 'undefined' && process?.env?.['DEBUG'] === 'true') {
1110
+ console.log(`Roark:DEBUG:${action}`, ...args);
1111
+ }
1112
+ }
1113
+
1114
+ /**
1115
+ * https://stackoverflow.com/a/2117523
1116
+ */
1117
+ const uuid4 = () => {
1118
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1119
+ const r = (Math.random() * 16) | 0;
1120
+ const v = c === 'x' ? r : (r & 0x3) | 0x8;
1121
+ return v.toString(16);
1122
+ });
1123
+ };
1124
+
1125
+ export const isRunningInBrowser = () => {
1126
+ return (
1127
+ // @ts-ignore
1128
+ typeof window !== 'undefined' &&
1129
+ // @ts-ignore
1130
+ typeof window.document !== 'undefined' &&
1131
+ // @ts-ignore
1132
+ typeof navigator !== 'undefined'
1133
+ );
1134
+ };
1135
+
1136
+ export interface HeadersProtocol {
1137
+ get: (header: string) => string | null | undefined;
1138
+ }
1139
+ export type HeadersLike = Record<string, string | string[] | undefined> | HeadersProtocol;
1140
+
1141
+ export const isHeadersProtocol = (headers: any): headers is HeadersProtocol => {
1142
+ return typeof headers?.get === 'function';
1143
+ };
1144
+
1145
+ export const getRequiredHeader = (headers: HeadersLike | Headers, header: string): string => {
1146
+ const foundHeader = getHeader(headers, header);
1147
+ if (foundHeader === undefined) {
1148
+ throw new Error(`Could not find ${header} header`);
1149
+ }
1150
+ return foundHeader;
1151
+ };
1152
+
1153
+ export const getHeader = (headers: HeadersLike | Headers, header: string): string | undefined => {
1154
+ const lowerCasedHeader = header.toLowerCase();
1155
+ if (isHeadersProtocol(headers)) {
1156
+ // to deal with the case where the header looks like Stainless-Event-Id
1157
+ const intercapsHeader =
1158
+ header[0]?.toUpperCase() +
1159
+ header.substring(1).replace(/([^\w])(\w)/g, (_m, g1, g2) => g1 + g2.toUpperCase());
1160
+ for (const key of [header, lowerCasedHeader, header.toUpperCase(), intercapsHeader]) {
1161
+ const value = headers.get(key);
1162
+ if (value) {
1163
+ return value;
1164
+ }
1165
+ }
1166
+ }
1167
+
1168
+ for (const [key, value] of Object.entries(headers)) {
1169
+ if (key.toLowerCase() === lowerCasedHeader) {
1170
+ if (Array.isArray(value)) {
1171
+ if (value.length <= 1) return value[0];
1172
+ console.warn(`Received ${value.length} entries for the ${header} header, using the first entry.`);
1173
+ return value[0];
1174
+ }
1175
+ return value;
1176
+ }
1177
+ }
1178
+
1179
+ return undefined;
1180
+ };
1181
+
1182
+ /**
1183
+ * Encodes a string to Base64 format.
1184
+ */
1185
+ export const toBase64 = (str: string | null | undefined): string => {
1186
+ if (!str) return '';
1187
+ if (typeof Buffer !== 'undefined') {
1188
+ return Buffer.from(str).toString('base64');
1189
+ }
1190
+
1191
+ if (typeof btoa !== 'undefined') {
1192
+ return btoa(str);
1193
+ }
1194
+
1195
+ throw new RoarkError('Cannot generate b64 string; Expected `Buffer` or `btoa` to be defined');
1196
+ };
1197
+
1198
+ export function isObj(obj: unknown): obj is Record<string, unknown> {
1199
+ return obj != null && typeof obj === 'object' && !Array.isArray(obj);
1200
+ }