@tryfinch/finch-api 3.1.3 → 4.1.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 (105) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +39 -15
  3. package/_shims/ReadableStream.d.ts +38 -0
  4. package/_shims/ReadableStream.js +5 -0
  5. package/_shims/ReadableStream.mjs +7 -0
  6. package/_shims/ReadableStream.node.d.ts +6 -0
  7. package/_shims/ReadableStream.node.d.ts.map +1 -0
  8. package/_shims/ReadableStream.node.js +14 -0
  9. package/_shims/ReadableStream.node.js.map +1 -0
  10. package/_shims/ReadableStream.node.mjs +3 -0
  11. package/_shims/ReadableStream.node.mjs.map +1 -0
  12. package/_shims/fetch.d.ts +8 -1
  13. package/_shims/fetch.node.d.ts +11 -1
  14. package/_shims/formdata.js +9 -1
  15. package/_shims/formdata.mjs +10 -1
  16. package/core.d.ts +78 -29
  17. package/core.d.ts.map +1 -1
  18. package/core.js +186 -62
  19. package/core.js.map +1 -1
  20. package/core.mjs +180 -62
  21. package/core.mjs.map +1 -1
  22. package/index.d.mts +15 -1
  23. package/index.d.ts +15 -1
  24. package/index.d.ts.map +1 -1
  25. package/index.js +17 -3
  26. package/index.js.map +1 -1
  27. package/index.mjs +17 -3
  28. package/index.mjs.map +1 -1
  29. package/package.json +1 -1
  30. package/pagination.d.ts +17 -8
  31. package/pagination.d.ts.map +1 -1
  32. package/pagination.js +26 -26
  33. package/pagination.js.map +1 -1
  34. package/pagination.mjs +26 -26
  35. package/pagination.mjs.map +1 -1
  36. package/resources/account.d.ts +2 -2
  37. package/resources/account.d.ts.map +1 -1
  38. package/resources/ats/applications.d.ts +6 -3
  39. package/resources/ats/applications.d.ts.map +1 -1
  40. package/resources/ats/applications.js.map +1 -1
  41. package/resources/ats/applications.mjs.map +1 -1
  42. package/resources/ats/candidates.d.ts +6 -3
  43. package/resources/ats/candidates.d.ts.map +1 -1
  44. package/resources/ats/candidates.js.map +1 -1
  45. package/resources/ats/candidates.mjs.map +1 -1
  46. package/resources/ats/jobs.d.ts +3 -3
  47. package/resources/ats/jobs.d.ts.map +1 -1
  48. package/resources/ats/offers.d.ts +3 -3
  49. package/resources/ats/offers.d.ts.map +1 -1
  50. package/resources/ats/stages.d.ts +1 -1
  51. package/resources/ats/stages.d.ts.map +1 -1
  52. package/resources/hris/benefits/benefits.d.ts +9 -10
  53. package/resources/hris/benefits/benefits.d.ts.map +1 -1
  54. package/resources/hris/benefits/benefits.js.map +1 -1
  55. package/resources/hris/benefits/benefits.mjs.map +1 -1
  56. package/resources/hris/benefits/individuals.d.ts +6 -6
  57. package/resources/hris/benefits/individuals.d.ts.map +1 -1
  58. package/resources/hris/company.d.ts +1 -1
  59. package/resources/hris/company.d.ts.map +1 -1
  60. package/resources/hris/directory.d.ts +2 -2
  61. package/resources/hris/directory.d.ts.map +1 -1
  62. package/resources/hris/individuals/employment-data.d.ts +1 -1
  63. package/resources/hris/individuals/employment-data.d.ts.map +1 -1
  64. package/resources/hris/individuals/individuals.d.ts +4 -2
  65. package/resources/hris/individuals/individuals.d.ts.map +1 -1
  66. package/resources/hris/individuals/individuals.js.map +1 -1
  67. package/resources/hris/individuals/individuals.mjs.map +1 -1
  68. package/resources/hris/pay-statements.d.ts +1 -1
  69. package/resources/hris/pay-statements.d.ts.map +1 -1
  70. package/resources/hris/payments.d.ts +4 -1
  71. package/resources/hris/payments.d.ts.map +1 -1
  72. package/resources/hris/payments.js.map +1 -1
  73. package/resources/hris/payments.mjs.map +1 -1
  74. package/resources/providers.d.ts +1 -1
  75. package/resources/providers.d.ts.map +1 -1
  76. package/src/_shims/ReadableStream.d.ts +38 -0
  77. package/src/_shims/ReadableStream.js +5 -0
  78. package/src/_shims/ReadableStream.mjs +7 -0
  79. package/src/_shims/ReadableStream.node.ts +6 -0
  80. package/src/_shims/fetch.d.ts +8 -1
  81. package/src/_shims/fetch.node.d.ts +11 -1
  82. package/src/_shims/formdata.js +9 -1
  83. package/src/_shims/formdata.mjs +10 -1
  84. package/src/core.ts +248 -83
  85. package/src/index.ts +16 -2
  86. package/src/pagination.ts +32 -27
  87. package/src/resources/account.ts +2 -2
  88. package/src/resources/ats/applications.ts +7 -4
  89. package/src/resources/ats/candidates.ts +7 -4
  90. package/src/resources/ats/jobs.ts +4 -4
  91. package/src/resources/ats/offers.ts +4 -4
  92. package/src/resources/ats/stages.ts +1 -1
  93. package/src/resources/hris/benefits/benefits.ts +11 -12
  94. package/src/resources/hris/benefits/individuals.ts +8 -8
  95. package/src/resources/hris/company.ts +1 -1
  96. package/src/resources/hris/directory.ts +3 -3
  97. package/src/resources/hris/individuals/employment-data.ts +1 -1
  98. package/src/resources/hris/individuals/individuals.ts +5 -3
  99. package/src/resources/hris/pay-statements.ts +1 -1
  100. package/src/resources/hris/payments.ts +4 -1
  101. package/src/resources/providers.ts +1 -1
  102. package/src/version.ts +1 -1
  103. package/version.d.ts +1 -1
  104. package/version.js +1 -1
  105. package/version.mjs +1 -1
package/src/core.ts CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  type RequestInit,
10
10
  type Response,
11
11
  } from './_shims/fetch.js';
12
+ export { type Response };
12
13
  import { isMultipartBody } from './uploads';
13
14
  export {
14
15
  maybeMultipartFormRequestOptions,
@@ -21,6 +22,100 @@ const MAX_RETRIES = 2;
21
22
 
22
23
  export type Fetch = (url: RequestInfo, init?: RequestInit) => Promise<Response>;
23
24
 
25
+ type PromiseOrValue<T> = T | Promise<T>;
26
+
27
+ type APIResponseProps = {
28
+ response: Response;
29
+ options: FinalRequestOptions;
30
+ controller: AbortController;
31
+ };
32
+
33
+ async function defaultParseResponse<T>(props: APIResponseProps): Promise<T> {
34
+ const { response } = props;
35
+ const contentType = response.headers.get('content-type');
36
+ if (contentType?.includes('application/json')) {
37
+ const json = await response.json();
38
+
39
+ debug('response', response.status, response.url, response.headers, json);
40
+
41
+ return json as T;
42
+ }
43
+
44
+ // TODO handle blob, arraybuffer, other content types, etc.
45
+ const text = await response.text();
46
+ debug('response', response.status, response.url, response.headers, text);
47
+ return text as T;
48
+ }
49
+
50
+ /**
51
+ * A subclass of `Promise` providing additional helper methods
52
+ * for interacting with the SDK.
53
+ */
54
+ export class APIPromise<T> extends Promise<T> {
55
+ private parsedPromise: Promise<T> | undefined;
56
+
57
+ constructor(
58
+ private responsePromise: Promise<APIResponseProps>,
59
+ private parseResponse: (props: APIResponseProps) => PromiseOrValue<T> = defaultParseResponse,
60
+ ) {
61
+ super((resolve) => {
62
+ // this is maybe a bit weird but this has to be a no-op to not implicitly
63
+ // parse the response body; instead .then, .catch, .finally are overridden
64
+ // to parse the response
65
+ resolve(null as any);
66
+ });
67
+ }
68
+
69
+ _thenUnwrap<U>(transform: (data: T) => U): APIPromise<U> {
70
+ return new APIPromise(this.responsePromise, async (props) => transform(await this.parseResponse(props)));
71
+ }
72
+
73
+ /**
74
+ * Gets the raw `Response` instance instead of parsing the response
75
+ * data.
76
+ *
77
+ * If you want to parse the response body but still get the `Response`
78
+ * instance, you can use {@link withResponse()}.
79
+ */
80
+ asResponse(): Promise<Response> {
81
+ return this.responsePromise.then((p) => p.response);
82
+ }
83
+ /**
84
+ * Gets the parsed response data and the raw `Response` instance.
85
+ *
86
+ * If you just want to get the raw `Response` instance without parsing it,
87
+ * you can use {@link asResponse()}.
88
+ */
89
+ async withResponse(): Promise<{ data: T; response: Response }> {
90
+ const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
91
+ return { data, response };
92
+ }
93
+
94
+ private parse(): Promise<T> {
95
+ if (!this.parsedPromise) {
96
+ this.parsedPromise = this.responsePromise.then(this.parseResponse);
97
+ }
98
+ return this.parsedPromise;
99
+ }
100
+
101
+ override then<TResult1 = T, TResult2 = never>(
102
+ onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
103
+ onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
104
+ ): Promise<TResult1 | TResult2> {
105
+ return this.parse().then(onfulfilled, onrejected);
106
+ }
107
+
108
+ override catch<TResult = never>(
109
+ onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null,
110
+ ): Promise<T | TResult> {
111
+ return this.parse().catch(onrejected);
112
+ }
113
+
114
+ override finally(onfinally?: (() => void) | undefined | null): Promise<T> {
115
+ return this.parse().finally(onfinally);
116
+ }
117
+ }
118
+
24
119
  export abstract class APIClient {
25
120
  baseURL: string;
26
121
  maxRetries: number;
@@ -33,7 +128,7 @@ export abstract class APIClient {
33
128
  constructor({
34
129
  baseURL,
35
130
  maxRetries,
36
- timeout = 60 * 1000, // 60s
131
+ timeout = 60000, // 1 minute
37
132
  httpAgent,
38
133
  fetch: overridenFetch,
39
134
  }: {
@@ -84,27 +179,39 @@ export abstract class APIClient {
84
179
  return `stainless-node-retry-${uuid4()}`;
85
180
  }
86
181
 
87
- get<Req extends {}, Rsp>(path: string, opts?: RequestOptions<Req>): Promise<Rsp> {
88
- return this.request({ method: 'get', path, ...opts });
182
+ get<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
183
+ return this.methodRequest('get', path, opts);
184
+ }
185
+
186
+ post<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
187
+ return this.methodRequest('post', path, opts);
89
188
  }
90
- post<Req extends {}, Rsp>(path: string, opts?: RequestOptions<Req>): Promise<Rsp> {
91
- return this.request({ method: 'post', path, ...opts });
189
+
190
+ patch<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
191
+ return this.methodRequest('patch', path, opts);
92
192
  }
93
- patch<Req extends {}, Rsp>(path: string, opts?: RequestOptions<Req>): Promise<Rsp> {
94
- return this.request({ method: 'patch', path, ...opts });
193
+
194
+ put<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
195
+ return this.methodRequest('put', path, opts);
95
196
  }
96
- put<Req extends {}, Rsp>(path: string, opts?: RequestOptions<Req>): Promise<Rsp> {
97
- return this.request({ method: 'put', path, ...opts });
197
+
198
+ delete<Req extends {}, Rsp>(path: string, opts?: PromiseOrValue<RequestOptions<Req>>): APIPromise<Rsp> {
199
+ return this.methodRequest('delete', path, opts);
98
200
  }
99
- delete<Req extends {}, Rsp>(path: string, opts?: RequestOptions<Req>): Promise<Rsp> {
100
- return this.request({ method: 'delete', path, ...opts });
201
+
202
+ private methodRequest<Req extends {}, Rsp>(
203
+ method: HTTPMethod,
204
+ path: string,
205
+ opts?: PromiseOrValue<RequestOptions<Req>>,
206
+ ): APIPromise<Rsp> {
207
+ return this.request(Promise.resolve(opts).then((opts) => ({ method, path, ...opts })));
101
208
  }
102
209
 
103
210
  getAPIList<Item, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(
104
211
  path: string,
105
212
  Page: new (...args: any[]) => PageClass,
106
213
  opts?: RequestOptions<any>,
107
- ): PagePromise<PageClass> {
214
+ ): PagePromise<PageClass, Item> {
108
215
  return this.requestAPIList(Page, { method: 'get', path, ...opts });
109
216
  }
110
217
 
@@ -140,7 +247,10 @@ export abstract class APIClient {
140
247
  const timeout = options.timeout ?? this.timeout;
141
248
  const httpAgent = options.httpAgent ?? this.httpAgent ?? getDefaultAgent(url);
142
249
  const minAgentTimeout = timeout + 1000;
143
- if ((httpAgent as any)?.options && minAgentTimeout > ((httpAgent as any).options.timeout ?? 0)) {
250
+ if (
251
+ typeof (httpAgent as any)?.options?.timeout === 'number' &&
252
+ minAgentTimeout > ((httpAgent as any).options.timeout ?? 0)
253
+ ) {
144
254
  // Allow any given request to bump our agent active socket timeout.
145
255
  // This may seem strange, but leaking active sockets should be rare and not particularly problematic,
146
256
  // and without mutating agent we would need to create more of them.
@@ -198,14 +308,27 @@ export abstract class APIClient {
198
308
  return APIError.generate(status, error, message, headers);
199
309
  }
200
310
 
201
- async request<Req extends {}, Rsp>(
202
- options: FinalRequestOptions<Req>,
203
- retriesRemaining = options.maxRetries ?? this.maxRetries,
204
- ): Promise<APIResponse<Rsp>> {
311
+ request<Req extends {}, Rsp>(
312
+ options: PromiseOrValue<FinalRequestOptions<Req>>,
313
+ remainingRetries: number | null = null,
314
+ ): APIPromise<Rsp> {
315
+ return new APIPromise(this.makeRequest(options, remainingRetries));
316
+ }
317
+
318
+ private async makeRequest(
319
+ optionsInput: PromiseOrValue<FinalRequestOptions>,
320
+ retriesRemaining: number | null,
321
+ ): Promise<APIResponseProps> {
322
+ const options = await optionsInput;
323
+ if (retriesRemaining == null) {
324
+ retriesRemaining = options.maxRetries ?? this.maxRetries;
325
+ }
326
+
205
327
  const { req, url, timeout } = this.buildRequest(options);
328
+
206
329
  await this.prepareRequest(req, { url });
207
330
 
208
- this.debug('request', url, options, req.headers);
331
+ debug('request', url, options, req.headers);
209
332
 
210
333
  if (options.signal?.aborted) {
211
334
  throw new APIUserAbortError();
@@ -238,42 +361,21 @@ export abstract class APIClient {
238
361
  const errJSON = safeJSON(errText);
239
362
  const errMessage = errJSON ? undefined : errText;
240
363
 
241
- this.debug('response', response.status, url, responseHeaders, errMessage);
364
+ debug('response', response.status, url, responseHeaders, errMessage);
242
365
 
243
366
  const err = this.makeStatusError(response.status, errJSON, errMessage, responseHeaders);
244
367
  throw err;
245
368
  }
246
369
 
247
- const contentType = response.headers.get('content-type');
248
- if (contentType?.includes('application/json')) {
249
- const json = await response.json();
250
-
251
- if (typeof json === 'object' && json != null) {
252
- /** @deprecated – we expect to change this interface in the near future. */
253
- Object.defineProperty(json, 'responseHeaders', {
254
- enumerable: false,
255
- writable: false,
256
- value: responseHeaders,
257
- });
258
- }
259
-
260
- this.debug('response', response.status, url, responseHeaders, json);
261
-
262
- return json as APIResponse<Rsp>;
263
- }
264
-
265
- // TODO handle blob, arraybuffer, other content types, etc.
266
- const text = response.text();
267
- this.debug('response', response.status, url, responseHeaders, text);
268
- return text as Promise<any>;
370
+ return { response, options, controller };
269
371
  }
270
372
 
271
373
  requestAPIList<Item = unknown, PageClass extends AbstractPage<Item> = AbstractPage<Item>>(
272
374
  Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
273
375
  options: FinalRequestOptions,
274
- ): PagePromise<PageClass> {
275
- const requestPromise = this.request(options) as Promise<APIResponse<unknown>>;
276
- return new PagePromise(this, requestPromise, options, Page);
376
+ ): PagePromise<PageClass, Item> {
377
+ const request = this.makeRequest(options, null);
378
+ return new PagePromise<PageClass, Item>(this, request, Page);
277
379
  }
278
380
 
279
381
  buildURL<Req>(path: string, query: Req | undefined): string {
@@ -353,11 +455,11 @@ export abstract class APIClient {
353
455
  return false;
354
456
  }
355
457
 
356
- private async retryRequest<Req extends {}, Rsp>(
357
- options: FinalRequestOptions<Req>,
458
+ private async retryRequest(
459
+ options: FinalRequestOptions,
358
460
  retriesRemaining: number,
359
461
  responseHeaders?: Headers | undefined,
360
- ): Promise<Rsp> {
462
+ ): Promise<APIResponseProps> {
361
463
  retriesRemaining -= 1;
362
464
 
363
465
  // About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
@@ -370,7 +472,7 @@ export abstract class APIClient {
370
472
  const timeout = this.calculateRetryTimeoutSeconds(retriesRemaining, retryAfter, maxRetries) * 1000;
371
473
  await sleep(timeout);
372
474
 
373
- return this.request(options, retriesRemaining);
475
+ return this.makeRequest(options, retriesRemaining);
374
476
  }
375
477
 
376
478
  private calculateRetryTimeoutSeconds(
@@ -401,12 +503,6 @@ export abstract class APIClient {
401
503
  private getUserAgent(): string {
402
504
  return `${this.constructor.name}/JS ${VERSION}`;
403
505
  }
404
-
405
- private debug(action: string, ...args: any[]) {
406
- if (typeof process !== 'undefined' && process.env['DEBUG'] === 'true') {
407
- console.log(`${this.constructor.name}:DEBUG:${action}`, ...args);
408
- }
409
- }
410
506
  }
411
507
 
412
508
  export class APIResource {
@@ -436,9 +532,14 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
436
532
  #client: APIClient;
437
533
  protected options: FinalRequestOptions;
438
534
 
439
- constructor(client: APIClient, response: APIResponse<unknown>, options: FinalRequestOptions) {
535
+ protected response: Response;
536
+ protected body: unknown;
537
+
538
+ constructor(client: APIClient, response: Response, body: unknown, options: FinalRequestOptions) {
440
539
  this.#client = client;
441
540
  this.options = options;
541
+ this.response = response;
542
+ this.body = body;
442
543
  }
443
544
 
444
545
  /**
@@ -495,35 +596,33 @@ export abstract class AbstractPage<Item> implements AsyncIterable<Item> {
495
596
  }
496
597
  }
497
598
 
599
+ /**
600
+ * This subclass of Promise will resolve to an instantiated Page once the request completes.
601
+ *
602
+ * It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
603
+ *
604
+ * for await (const item of client.items.list()) {
605
+ * console.log(item)
606
+ * }
607
+ */
498
608
  export class PagePromise<
499
609
  PageClass extends AbstractPage<Item>,
500
610
  Item = ReturnType<PageClass['getPaginatedItems']>[number],
501
611
  >
502
- extends Promise<PageClass>
612
+ extends APIPromise<PageClass>
503
613
  implements AsyncIterable<Item>
504
614
  {
505
- /**
506
- * This subclass of Promise will resolve to an instantiated Page once the request completes.
507
- */
508
615
  constructor(
509
616
  client: APIClient,
510
- requestPromise: Promise<APIResponse<unknown>>,
511
- options: FinalRequestOptions,
617
+ request: Promise<APIResponseProps>,
512
618
  Page: new (...args: ConstructorParameters<typeof AbstractPage>) => PageClass,
513
619
  ) {
514
- super((resolve, reject) =>
515
- requestPromise.then((response) => resolve(new Page(client, response, options))).catch(reject),
620
+ super(
621
+ request,
622
+ async (props) => new Page(client, props.response, await defaultParseResponse(props), props.options),
516
623
  );
517
624
  }
518
625
 
519
- /**
520
- * Enable subclassing Promise.
521
- * Ref: https://stackoverflow.com/a/60328122
522
- */
523
- static get [Symbol.species]() {
524
- return Promise;
525
- }
526
-
527
626
  /**
528
627
  * Allow auto-paginating iteration on an unawaited list call, eg:
529
628
  *
@@ -610,11 +709,6 @@ export type FinalRequestOptions<Req extends {} = Record<string, unknown> | Reada
610
709
  path: string;
611
710
  };
612
711
 
613
- export type APIResponse<T> = T & {
614
- /** @deprecated - we plan to add a different way to access raw response information shortly. */
615
- responseHeaders: Headers;
616
- };
617
-
618
712
  declare const Deno: any;
619
713
  declare const EdgeRuntime: any;
620
714
  type Arch = 'x32' | 'x64' | 'arm' | 'arm64' | `other:${string}` | 'unknown';
@@ -628,12 +722,13 @@ type PlatformName =
628
722
  | 'Android'
629
723
  | `Other:${string}`
630
724
  | 'Unknown';
725
+ type Browser = 'ie' | 'edge' | 'chrome' | 'firefox' | 'safari';
631
726
  type PlatformProperties = {
632
727
  'X-Stainless-Lang': 'js';
633
728
  'X-Stainless-Package-Version': string;
634
729
  'X-Stainless-OS': PlatformName;
635
730
  'X-Stainless-Arch': Arch;
636
- 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | 'unknown';
731
+ 'X-Stainless-Runtime': 'node' | 'deno' | 'edge' | `browser:${Browser}` | 'unknown';
637
732
  'X-Stainless-Runtime-Version': string;
638
733
  };
639
734
  const getPlatformProperties = (): PlatformProperties => {
@@ -668,7 +763,20 @@ const getPlatformProperties = (): PlatformProperties => {
668
763
  'X-Stainless-Runtime-Version': process.version,
669
764
  };
670
765
  }
671
- // TODO add support for Cloudflare workers, browsers, etc.
766
+
767
+ const browserInfo = getBrowserInfo();
768
+ if (browserInfo) {
769
+ return {
770
+ 'X-Stainless-Lang': 'js',
771
+ 'X-Stainless-Package-Version': VERSION,
772
+ 'X-Stainless-OS': 'Unknown',
773
+ 'X-Stainless-Arch': 'unknown',
774
+ 'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
775
+ 'X-Stainless-Runtime-Version': browserInfo.version,
776
+ };
777
+ }
778
+
779
+ // TODO add support for Cloudflare workers, etc.
672
780
  return {
673
781
  'X-Stainless-Lang': 'js',
674
782
  'X-Stainless-Package-Version': VERSION,
@@ -679,6 +787,44 @@ const getPlatformProperties = (): PlatformProperties => {
679
787
  };
680
788
  };
681
789
 
790
+ type BrowserInfo = {
791
+ browser: Browser;
792
+ version: string;
793
+ };
794
+
795
+ declare const navigator: { userAgent: string } | undefined;
796
+
797
+ // Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
798
+ function getBrowserInfo(): BrowserInfo | null {
799
+ if (!navigator || typeof navigator === 'undefined') {
800
+ return null;
801
+ }
802
+
803
+ // NOTE: The order matters here!
804
+ const browserPatterns = [
805
+ { key: 'edge' as const, pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
806
+ { key: 'ie' as const, pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
807
+ { key: 'ie' as const, pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
808
+ { key: 'chrome' as const, pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
809
+ { key: 'firefox' as const, pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
810
+ { key: 'safari' as const, pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
811
+ ];
812
+
813
+ // Find the FIRST matching browser
814
+ for (const { key, pattern } of browserPatterns) {
815
+ const match = pattern.exec(navigator.userAgent);
816
+ if (match) {
817
+ const major = match[1] || 0;
818
+ const minor = match[2] || 0;
819
+ const patch = match[3] || 0;
820
+
821
+ return { browser: key, version: `${major}.${minor}.${patch}` };
822
+ }
823
+ }
824
+
825
+ return null;
826
+ }
827
+
682
828
  const normalizeArch = (arch: string): Arch => {
683
829
  // Node docs:
684
830
  // - https://nodejs.org/api/process.html#processarch
@@ -760,14 +906,16 @@ export const ensurePresent = <T>(value: T | null | undefined): T => {
760
906
  /**
761
907
  * Read an environment variable.
762
908
  *
763
- * Will return an empty string if the environment variable doesn't exist or cannot be accessed.
909
+ * Will return undefined if the environment variable doesn't exist or cannot be accessed.
764
910
  */
765
911
  export const readEnv = (env: string): string | undefined => {
766
- if (typeof process === 'undefined') {
767
- return undefined;
912
+ if (typeof process !== 'undefined') {
913
+ return process.env?.[env] ?? undefined;
768
914
  }
769
-
770
- return process.env[env] ?? undefined;
915
+ if (typeof Deno !== 'undefined') {
916
+ return Deno.env?.get?.(env);
917
+ }
918
+ return undefined;
771
919
  };
772
920
 
773
921
  export const coerceInteger = (value: unknown): number => {
@@ -823,6 +971,12 @@ export function hasOwn(obj: Object, key: string): boolean {
823
971
  return Object.prototype.hasOwnProperty.call(obj, key);
824
972
  }
825
973
 
974
+ export function debug(action: string, ...args: any[]) {
975
+ if (typeof process !== 'undefined' && process.env['DEBUG'] === 'true') {
976
+ console.log(`Finch:DEBUG:${action}`, ...args);
977
+ }
978
+ }
979
+
826
980
  /**
827
981
  * https://stackoverflow.com/a/2117523
828
982
  */
@@ -834,6 +988,17 @@ const uuid4 = () => {
834
988
  });
835
989
  };
836
990
 
991
+ export const isRunningInBrowser = () => {
992
+ return (
993
+ // @ts-ignore
994
+ typeof window !== 'undefined' &&
995
+ // @ts-ignore
996
+ typeof window.document !== 'undefined' &&
997
+ // @ts-ignore
998
+ typeof navigator !== 'undefined'
999
+ );
1000
+ };
1001
+
837
1002
  export interface HeadersProtocol {
838
1003
  get: (header: string) => string | null | undefined;
839
1004
  }
package/src/index.ts CHANGED
@@ -72,7 +72,7 @@ export interface ClientOptions {
72
72
  clientSecret?: string | null;
73
73
  }
74
74
 
75
- /** Instantiate the API Client. */
75
+ /** API Client for interfacing with the Finch API. */
76
76
  export class Finch extends Core.APIClient {
77
77
  accessToken: string | null;
78
78
  clientId?: string | null;
@@ -80,6 +80,20 @@ export class Finch extends Core.APIClient {
80
80
 
81
81
  private _options: ClientOptions;
82
82
 
83
+ /**
84
+ * API Client for interfacing with the Finch API.
85
+ *
86
+ * @param {string | null} opts.accessToken - The Access Token to send to the API.
87
+ * @param {string} [opts.baseURL] - Override the default base URL for the API.
88
+ * @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
89
+ * @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
90
+ * @param {Core.Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
91
+ * @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
92
+ * @param {Core.Headers} opts.defaultHeaders - Default headers to include with every request to the API.
93
+ * @param {Core.DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API.
94
+ * @param {string | null} [opts.clientId]
95
+ * @param {string | null} [opts.clientSecret]
96
+ */
83
97
  constructor({
84
98
  accessToken = null,
85
99
  clientId = Core.readEnv('FINCH_CLIENT_ID') ?? null,
@@ -98,7 +112,7 @@ export class Finch extends Core.APIClient {
98
112
 
99
113
  super({
100
114
  baseURL: options.baseURL!,
101
- timeout: options.timeout,
115
+ timeout: options.timeout ?? 60000 /* 1 minute */,
102
116
  httpAgent: options.httpAgent,
103
117
  maxRetries: options.maxRetries,
104
118
  fetch: options.fetch,
package/src/pagination.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // File generated from our OpenAPI spec by Stainless.
2
2
 
3
- import { AbstractPage, APIResponse, APIClient, FinalRequestOptions, PageInfo } from './core';
3
+ import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core';
4
4
  import * as HRIS from './resources/hris/index';
5
5
  import * as ATS from './resources/ats/index';
6
6
 
@@ -11,12 +11,13 @@ export class SinglePage<Item> extends AbstractPage<Item> {
11
11
 
12
12
  constructor(
13
13
  client: APIClient,
14
- response: APIResponse<SinglePageResponse<Item>>,
14
+ response: Response,
15
+ body: SinglePageResponse<Item>,
15
16
  options: FinalRequestOptions,
16
17
  ) {
17
- super(client, response, options);
18
+ super(client, response, body, options);
18
19
 
19
- this.items = response || [];
20
+ this.items = body || [];
20
21
  }
21
22
 
22
23
  getPaginatedItems(): Item[] {
@@ -46,12 +47,13 @@ export class ResponsesPage<Item> extends AbstractPage<Item> implements Responses
46
47
 
47
48
  constructor(
48
49
  client: APIClient,
49
- response: APIResponse<ResponsesPageResponse<Item>>,
50
+ response: Response,
51
+ body: ResponsesPageResponse<Item>,
50
52
  options: FinalRequestOptions,
51
53
  ) {
52
- super(client, response, options);
54
+ super(client, response, body, options);
53
55
 
54
- this.responses = response.responses;
56
+ this.responses = body.responses;
55
57
  }
56
58
 
57
59
  getPaginatedItems(): Item[] {
@@ -106,13 +108,14 @@ export class IndividualsPage
106
108
 
107
109
  constructor(
108
110
  client: APIClient,
109
- response: APIResponse<IndividualsPageResponse>,
111
+ response: Response,
112
+ body: IndividualsPageResponse,
110
113
  options: FinalRequestOptions,
111
114
  ) {
112
- super(client, response, options);
115
+ super(client, response, body, options);
113
116
 
114
- this.paging = response.paging;
115
- this.individuals = response.individuals;
117
+ this.paging = body.paging;
118
+ this.individuals = body.individuals;
116
119
  }
117
120
 
118
121
  getPaginatedItems(): HRIS.IndividualInDirectory[] {
@@ -172,13 +175,14 @@ export class CandidatesPage extends AbstractPage<ATS.Candidate> implements Candi
172
175
 
173
176
  constructor(
174
177
  client: APIClient,
175
- response: APIResponse<CandidatesPageResponse>,
178
+ response: Response,
179
+ body: CandidatesPageResponse,
176
180
  options: FinalRequestOptions,
177
181
  ) {
178
- super(client, response, options);
182
+ super(client, response, body, options);
179
183
 
180
- this.paging = response.paging;
181
- this.candidates = response.candidates;
184
+ this.paging = body.paging;
185
+ this.candidates = body.candidates;
182
186
  }
183
187
 
184
188
  getPaginatedItems(): ATS.Candidate[] {
@@ -238,13 +242,14 @@ export class ApplicationsPage extends AbstractPage<ATS.Application> implements A
238
242
 
239
243
  constructor(
240
244
  client: APIClient,
241
- response: APIResponse<ApplicationsPageResponse>,
245
+ response: Response,
246
+ body: ApplicationsPageResponse,
242
247
  options: FinalRequestOptions,
243
248
  ) {
244
- super(client, response, options);
249
+ super(client, response, body, options);
245
250
 
246
- this.paging = response.paging;
247
- this.applications = response.applications;
251
+ this.paging = body.paging;
252
+ this.applications = body.applications;
248
253
  }
249
254
 
250
255
  getPaginatedItems(): ATS.Application[] {
@@ -302,11 +307,11 @@ export class JobsPage extends AbstractPage<ATS.Job> implements JobsPageResponse
302
307
 
303
308
  jobs: Array<ATS.Job>;
304
309
 
305
- constructor(client: APIClient, response: APIResponse<JobsPageResponse>, options: FinalRequestOptions) {
306
- super(client, response, options);
310
+ constructor(client: APIClient, response: Response, body: JobsPageResponse, options: FinalRequestOptions) {
311
+ super(client, response, body, options);
307
312
 
308
- this.paging = response.paging;
309
- this.jobs = response.jobs;
313
+ this.paging = body.paging;
314
+ this.jobs = body.jobs;
310
315
  }
311
316
 
312
317
  getPaginatedItems(): ATS.Job[] {
@@ -364,11 +369,11 @@ export class OffersPage extends AbstractPage<ATS.Offer> implements OffersPageRes
364
369
 
365
370
  offers: Array<ATS.Offer>;
366
371
 
367
- constructor(client: APIClient, response: APIResponse<OffersPageResponse>, options: FinalRequestOptions) {
368
- super(client, response, options);
372
+ constructor(client: APIClient, response: Response, body: OffersPageResponse, options: FinalRequestOptions) {
373
+ super(client, response, body, options);
369
374
 
370
- this.paging = response.paging;
371
- this.offers = response.offers;
375
+ this.paging = body.paging;
376
+ this.offers = body.offers;
372
377
  }
373
378
 
374
379
  getPaginatedItems(): ATS.Offer[] {
@@ -10,14 +10,14 @@ export class Account extends APIResource {
10
10
  * associated with the employer. We require applications to implement the
11
11
  * Disconnect endpoint for billing and security purposes.
12
12
  */
13
- disconnect(options?: Core.RequestOptions): Promise<Core.APIResponse<DisconnectResponse>> {
13
+ disconnect(options?: Core.RequestOptions): Core.APIPromise<DisconnectResponse> {
14
14
  return this.post('/disconnect', options);
15
15
  }
16
16
 
17
17
  /**
18
18
  * Read account information associated with an `access_token`
19
19
  */
20
- introspect(options?: Core.RequestOptions): Promise<Core.APIResponse<Introspection>> {
20
+ introspect(options?: Core.RequestOptions): Core.APIPromise<Introspection> {
21
21
  return this.get('/introspect', options);
22
22
  }
23
23
  }