@absolutejs/engagement-apollo 0.0.14 → 0.0.16

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.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { EngagementProviderError } from "@absolutejs/engagement";
1
2
  import type { EngagementSource } from "@absolutejs/engagement";
2
3
  export type ApolloOptions = {
3
4
  apiKey: string;
@@ -6,4 +7,9 @@ export type ApolloOptions = {
6
7
  /** Inject a fetch (tests, instrumentation). Defaults to global fetch. */
7
8
  fetchImpl?: typeof fetch;
8
9
  };
10
+ /** Safe provider classification; response bodies and billing context stay private. */
11
+ export declare class ApolloProviderError extends EngagementProviderError {
12
+ readonly code: string | null;
13
+ constructor(status: number, operation: string, code: string | null);
14
+ }
9
15
  export declare const apolloSource: (options: ApolloOptions) => EngagementSource;
package/dist/index.js CHANGED
@@ -8,6 +8,18 @@ var APOLLO_BASE_URL = "https://api.apollo.io";
8
8
  var DEFAULT_ACTIVITY_LIMIT = 50;
9
9
  var DEFAULT_SEARCH_LIMIT = 6;
10
10
  var LOCKED_EMAIL = /email_not_unlocked/iu;
11
+
12
+ class ApolloProviderError extends EngagementProviderError {
13
+ code;
14
+ constructor(status, operation, code) {
15
+ super("apollo", status, operation);
16
+ this.code = code;
17
+ this.name = "ApolloProviderError";
18
+ if (code === "BILLING.LIMIT.CREDITS_EXHAUSTED") {
19
+ this.message = "Apollo lead credits are exhausted. An administrator must replenish the connected Apollo account; application credits do not fund Apollo.";
20
+ }
21
+ }
22
+ }
11
23
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12
24
  var str = (value) => typeof value === "string" && value.length > 0 ? value : null;
13
25
  var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
@@ -49,6 +61,12 @@ var reference = (value) => isRecord(value) ? {
49
61
  employeeCount: count(value.estimated_num_employees),
50
62
  industries: strings(value.industries)
51
63
  } : null;
64
+ var providerFailure = async (res, operation) => {
65
+ const value = await res.json().catch(() => null);
66
+ const details = isRecord(value) && isRecord(value.error_details) ? value.error_details : null;
67
+ const code = details && typeof details.code === "string" && /^[A-Z0-9_.]{1,120}$/u.test(details.code) ? details.code : null;
68
+ return new ApolloProviderError(res.status, operation, code);
69
+ };
52
70
  var apolloSource = (options) => {
53
71
  const baseUrl = options.baseUrl ?? APOLLO_BASE_URL;
54
72
  const doFetch = options.fetchImpl ?? fetch;
@@ -60,14 +78,17 @@ var apolloSource = (options) => {
60
78
  const res = await doFetch(`${baseUrl}${path}`, {
61
79
  body: JSON.stringify(body),
62
80
  headers,
63
- method: "POST"
81
+ method: "POST",
82
+ signal: AbortSignal.timeout(30000)
64
83
  });
65
84
  if (res.status === 429)
66
85
  throw new RateLimitError("apollo");
67
86
  if (!res.ok)
68
- return null;
69
- const json = await res.json();
70
- return isRecord(json) ? json : null;
87
+ throw await providerFailure(res, path);
88
+ const json = await res.json().catch(() => null);
89
+ if (!isRecord(json))
90
+ throw new EngagementProviderError("apollo", 502, `invalid ${path} response`);
91
+ return json;
71
92
  };
72
93
  const normalizePerson = (person) => {
73
94
  const org = isRecord(person.organization) ? person.organization : null;
@@ -100,33 +121,42 @@ var apolloSource = (options) => {
100
121
  };
101
122
  };
102
123
  const matchById = async (id) => {
103
- const json = await post("/v1/people/match", { id });
104
- const person = json && isRecord(json.person) ? json.person : null;
124
+ const json = await post("/api/v1/people/match", { id });
125
+ if (json.person !== null && !isRecord(json.person))
126
+ throw new EngagementProviderError("apollo", 502, "invalid person enrichment response");
127
+ const person = isRecord(json.person) ? json.person : null;
105
128
  return person ? normalizePerson(person) : null;
106
129
  };
107
130
  const enrichPerson = async (query) => {
108
- const json = await post("/v1/people/match", {
131
+ const json = await post("/api/v1/people/match", {
109
132
  domain: query.domain,
110
133
  email: query.email,
111
134
  linkedin_url: query.linkedinUrl,
112
135
  name: query.name,
113
136
  organization_name: query.company
114
137
  });
115
- const person = json && isRecord(json.person) ? json.person : null;
138
+ if (json.person !== null && !isRecord(json.person))
139
+ throw new EngagementProviderError("apollo", 502, "invalid person enrichment response");
140
+ const person = isRecord(json.person) ? json.person : null;
116
141
  return person ? normalizePerson(person) : null;
117
142
  };
118
143
  const searchPeople = async (query) => {
119
144
  const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
120
- const search = await post("/v1/mixed_people/api_search", {
145
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100)
146
+ throw new RangeError("Apollo people limit must be an integer from 1 to 100");
147
+ const search = await post("/api/v1/mixed_people/api_search", {
121
148
  page: 1,
122
149
  per_page: limit,
123
150
  ...query.titles && query.titles.length > 0 ? { person_titles: query.titles } : {},
124
151
  ...query.domain ? { q_organization_domains_list: [query.domain] } : {},
125
152
  ...!query.domain && query.company ? { q_keywords: query.company } : {}
126
153
  });
127
- const searchRequests = search ? 1 : 0;
128
- const previews = search && Array.isArray(search.people) ? search.people : [];
129
- const ids = previews.map((person) => isRecord(person) ? str(person.id) : null).filter((id) => id !== null);
154
+ const searchRequests = 1;
155
+ if (!Array.isArray(search.people))
156
+ throw new EngagementProviderError("apollo", 502, "invalid people search response");
157
+ const ids = [
158
+ ...new Set(search.people.map((person) => isRecord(person) ? str(person.id) : null).filter((id) => id !== null))
159
+ ].slice(0, limit);
130
160
  if (ids.length === 0) {
131
161
  return { people: [], revealsAttempted: 0, searchRequests };
132
162
  }
@@ -203,7 +233,7 @@ var apolloSource = (options) => {
203
233
  if (res.status === 404 && !body)
204
234
  return null;
205
235
  if (!res.ok)
206
- throw new EngagementProviderError("apollo", res.status, body ? "company search" : "company enrichment");
236
+ throw await providerFailure(res, body ? "company search" : "company enrichment");
207
237
  const value = await res.json();
208
238
  if (!isRecord(value))
209
239
  throw new EngagementProviderError("apollo", 502, "invalid company response");
@@ -289,5 +319,6 @@ var apolloSource = (options) => {
289
319
  };
290
320
  };
291
321
  export {
322
+ ApolloProviderError,
292
323
  apolloSource
293
324
  };
package/package.json CHANGED
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "type": "module",
46
46
  "types": "./dist/index.d.ts",
47
- "version": "0.0.14",
47
+ "version": "0.0.16",
48
48
  "exports": {
49
49
  ".": {
50
50
  "types": "./dist/index.d.ts",